Skip to content

Commit e0a5f2e

Browse files
AngelFQCclaude
andcommitted
Security: enforce CSRF tokens on state-changing admin endpoints
Several admin endpoints read $_POST and executed privileged state changes (course export/import, user-to-URL assignment, course-to-HR-manager assignment, session-to-promotion subscription) without any anti-CSRF token validation, so forged cross-site POSTs were accepted. Add the standard Chamilo legacy idiom to each: reject the request with api_not_allowed() when Security::check_token('post') fails, and emit a per-session token on the form (sec_token hidden via FormValidator, or Security::get_HTML_token() inside the hand-written forms). Refs GHSA-cxxm-wpv8-fwh9 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 996c4f1 commit e0a5f2e

5 files changed

Lines changed: 20 additions & 0 deletions

File tree

public/main/admin/access_url_add_users_to_url.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
Display::page_subheader2($tool_name);
4848

4949
if (!empty($_POST['form_sent'])) {
50+
if (!Security::check_token('post')) {
51+
api_not_allowed(true);
52+
}
5053
$form_sent = $_POST['form_sent'];
5154
$users = isset($_POST['user_list']) && is_array($_POST['user_list']) ? array_map('intval', $_POST['user_list']) : [];
5255
$url_list = isset($_POST['url_list']) && is_array($_POST['url_list']) ? $_POST['url_list'] : [];
@@ -99,6 +102,7 @@
99102

100103
<form name="formulaire" method="post" action="<?php echo api_get_self(); ?>" class="space-y-6" onsubmit="return confirmSubmission(event)">
101104
<input type="hidden" name="form_sent" value="1" />
105+
<?php echo Security::get_HTML_token(); ?>
102106
<div class="flex flex-col sm:flex-row items-center justify-between gap-4">
103107
<div>
104108
<label class="block text-sm font-medium text-gray-700 mb-1">

public/main/admin/add_sessions_to_promotion.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ function validate_filter() {
7171
$promotion = new Promotion();
7272
$id = (int) ($_GET['id']);
7373
if (isset($_POST['form_sent']) && $_POST['form_sent']) {
74+
if (!Security::check_token('post')) {
75+
api_not_allowed(true);
76+
}
7477
$form_sent = $_POST['form_sent'];
7578
$session_in_promotion_posted = $_POST['session_in_promotion_name'];
7679
if (!is_array($session_in_promotion_posted)) {
@@ -135,6 +138,7 @@ function validate_filter() {
135138
echo Display::input('hidden', 'id', $id);
136139
echo Display::input('hidden', 'form_sent', '1');
137140
echo Display::input('hidden', 'add_type', null);
141+
echo Security::get_HTML_token();
138142
?>
139143

140144
<table border="0" cellpadding="5" cellspacing="0" width="100%">

public/main/admin/course_export.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
$courses = $selected_courses = [];
3131

3232
if (isset($_POST['formSent']) && $_POST['formSent']) {
33+
if (!Security::check_token('post')) {
34+
api_not_allowed(true);
35+
}
3336
$formSent = $_POST['formSent'];
3437
$select_type = (int) ($_POST['select_type']);
3538
$file_type = $_POST['file_type'];
@@ -125,6 +128,7 @@
125128
$form = new FormValidator('export', 'post', api_get_self());
126129
$form->addHeader($tool_name);
127130
$form->addHidden('formSent', 1);
131+
$form->addHidden('sec_token', Security::get_token());
128132
$form->addElement(
129133
'radio',
130134
'select_type',

public/main/admin/course_import.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ function parse_csv_courses_data($file)
187187
Display::display_header($tool_name);
188188

189189
if (isset($_POST['formSent']) && $_POST['formSent']) {
190+
if (!Security::check_token('post')) {
191+
api_not_allowed(true);
192+
}
190193
if (empty($_FILES['import_file']['tmp_name'])) {
191194
$error_message = get_lang('The file upload has failed.');
192195
echo Display::return_message($error_message, 'error', false);
@@ -239,6 +242,7 @@ function parse_csv_courses_data($file)
239242
$form->addElement('checkbox', 'add_me_as_teacher', null, get_lang('Add me as teacher in the imported courses.'));
240243
$form->addButtonImport(get_lang('Import'), 'save');
241244
$form->addElement('hidden', 'formSent', 1);
245+
$form->addHidden('sec_token', Security::get_token());
242246
$form->display();
243247

244248
$content = '

public/main/admin/dashboard_add_courses_to_user.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ function remove_item(origin) {
162162

163163
$msg = '';
164164
if (isset($_POST['formSent']) && 1 == (int) ($_POST['formSent'])) {
165+
if (!Security::check_token('post')) {
166+
api_not_allowed(true);
167+
}
165168
$courses_list = isset($_POST['CoursesList']) ? $_POST['CoursesList'] : [];
166169
$affected_rows = CourseManager::subscribeCoursesToDrhManager($user_id, $courses_list);
167170
if ($affected_rows) {
@@ -225,6 +228,7 @@ function remove_item(origin) {
225228
?>
226229
<form name="formulaire" method="post" action="<?php echo api_get_self(); ?>?user=<?php echo $user_id; ?>" style="margin:0px;">
227230
<input type="hidden" name="formSent" value="1" />
231+
<?php echo Security::get_HTML_token(); ?>
228232
<?php
229233
if (!empty($msg)) {
230234
echo Display::return_message($msg, 'normal'); //main API

0 commit comments

Comments
 (0)