Chamilo LMS — Unauthenticated data injection (AJAX in LP) | CVE-2024–51142

Vulnerabilities in Chamilo LMS
The Chamilo LMS platform, widely used in educational environments, has been identified with an authentication bypass vulnerability that allows attackers to circumvent access control mechanisms. This vulnerability facilitates the exploitation of a second Cross-Site Scripting (XSS) vulnerability, which can have serious implications for its users. The XSS vulnerability affects Chamilo LMS versions <= 1.11.26, which is the latest available version, and enables attackers to store malicious scripts that execute in other users’ browsers, thus compromising the security and integrity of information on the platform.
Security Issues
Chamilo 1.11.28 is a security fix release on top of 1.11.26. See security issues at https://github.com/chamilo/chamilo-lms/wiki/security-issues#issue-175---2024-09-11---moderate-impact-moderate-risk---unauthenticated-data-injection-ajax-in-lp
Chamilo Exposure in Advanced and Common Search Engines
The search for the Chamilo framework using online service detection engines such as ZoomEye, Shodan, and Censys, as well as common search engines like Google, Bing and Yahoo, has revealed a significant exposure of publicly accessible servers. These results highlight a broad potential attack surface and a considerable number of possible attack vectors, underscoring the need to implement stronger security measures to protect Chamilo implementations and mitigate the risks associated with this online exposure.


It’s time for a ‘hacker coffee’ and let’s get to work!
Analysis Methodology
Execution of StaticCodex for PHP File Analysis
We use StaticCodex (SAST), a proprietary tool developed in-house, designed to quickly identify vulnerable endpoints in various languages such as PHP, ASP, JSP, among others. This tool analyzes the static code for specific patterns associated with vulnerabilities such as SQL injections, XSS, RCE, and Command Injection, among others, which could represent attack vectors. In this case, the tool is executed specifically on the .php files of the project, focusing on the detection of critical paths that contain SQL queries.
Extraction of Unique SQL Paths
Once StaticCodex generates the output_sql.txt file with the paths where the $SQL queries are found, the results are processed to retain only the unique paths and avoid redundant analysis:
cat output_sql.txt | cut -d ":" -f1 | sort -u |tee -a output_sql_uniq.txt
Validation of Directory and File Existence on the Server
Next, we proceed to validate whether the extracted paths correspond to directories and files that actually exist on the target server by sending HTTP requests to log their respective response codes. Subsequently, we exclude any paths that return a status code of 404 (not found), ensuring that only the paths that actually exist on the server are retained.
As a result, we obtain status codes 500 and 200.
for i in $(cat output_sql_uniq.txt); do sta=$(curl -o /dev/null -s -w "%{http_code}" "https://example.com/$i"); [ "$sta" != "404" ] && echo "$sta - $i" | tee -a output_sql_status.txt; done
Analysis of Files with Status 200
With the valid files identified, we proceed to focus on those that have returned a status 200 (exist and are accessible). These will be the candidates for in-depth analysis in search of vulnerabilities.
cat output_sql_status.txt | grep -v "500 - " | cut -d " " -f3 | cut -d "." -f2-10
Access Validation Using Burp Suite
Using Burp Suite (Intruder), we load the extracted paths to validate which of them require authentication or authorization to access. Those files that prevent access due to lack of permissions are excluded from the analysis, optimizing the review time and focusing on the most vulnerable ones.
We configure the path in Intruder to be automatically replaced by the list obtained in output_sql_status.txt, which contains the paths with a status code of 200.
GET §§ HTTP/1.1
Host: aulavirtual.friend.com
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0
Connection: close
Cache-Control: max-age=0
We add the list of directories and files in the payloads option, which includes a total of 320 payloads.

Finally, we add a column using a regular expression to search for the string “sufficient privileges” which allows us to identify which of the paths can be accessed without the need to log in.

Finally, once we have obtained the valid and accessible paths, we begin the detailed analysis of vulnerabilities, which involves a thorough review of the attack points in the selected PHP files. Applying the aforementioned methodology, we have identified critical vulnerabilities in the files storageapi.php and api.lib.php, located in main/lp/ and main/inc/lib/, respectively. The storageapi.php file plays a fundamental role in sanitizing user inputs in $_REQUEST, which is key to preventing SQL injections and ensuring the correct typing of data. During our analysis, we focused on identifying flaws that could compromise the integrity of the processed data. Below, we highlight the most relevant lines of code from the storageapi.php file.
String Escaping
foreach (["svkey", "svvalue"] as $key) {
$_REQUEST[$key] = Database::escape_string($_REQUEST[$key]);
}
The function Database::escape_string() is used to escape special characters in strings, ensuring that the input data does not alter the structure of the SQL queries when inserted into a statement.
Conversion to Integers
foreach (["svuser", "svcourse", "svsco", "svlength", "svasc"] as $key) {
$_REQUEST[$key] = intval($_REQUEST[$key]);
}
- This conversion ensures that the values are treated as integers, converting any non-numeric value to 0.
- Type Validation: Ensures that the variables expected to be numbers are indeed numeric, preventing errors in mathematical or logical operations.
Control Structure (Switch)
The following code snippet uses a switch control structure to handle different actions requested by the user through $_REQUEST. Depending on the value of the action variable, different functions are executed.
switch ($_REQUEST['action']) {
case "set":
if (storage_can_set($_REQUEST['svuser'])) {
echo storage_set($_REQUEST['svuser'], $_REQUEST['svcourse'], $_REQUEST['svsco'], $_REQUEST['svkey'], $_REQUEST['svvalue']);
}
break;
case "getall":
print storage_getall($_REQUEST['svuser'], $_REQUEST['svcourse'], $_REQUEST['svsco']);
break;
.
. //More code here
.
case "usersgetall":
print "NOT allowed, security issue, see sources";
break;
default:
}
- Function
storage_can_set()
: Checks if the user has permissions to modify the data, which is essential to ensure that only authorized users can make changes. - Function
storage_set()
: Performs data storage in the database after verifying permissions. - Function
storage_getall()
: Retrieves all stored values for a specific user.
First Vulnerability — Authentication Bypass
Before analyzing the JavaScript injection vulnerability (XSS), it is essential to understand how to bypass the authentication process. The function storage_can_set()
is designed to determine whether a user can modify data based on whether they are an administrator or if they are modifying their own data.
- Function
api_is_platform_admin()
: Checks if the user is a platform administrator, in which case they can modify the values of any user. - Function
api_get_user_id()
: Verifies if the current user is the same as the one attempting to make the change, allowing modification if they are the same.
function storage_can_set($sv_user)
{
$allowed = ((api_is_platform_admin()) || ($sv_user == api_get_user_id()));
if (!$allowed) {
echo "ERROR : Not allowed";
}
return $allowed;
}

Evasion Analysis
Since we are neither administrators nor authenticated, the analysis of the api_get_user_id() function is key. This function attempts to read the user’s session information:
function api_get_user_id()
{
$userInfo = Session::read('_user');
if ($userInfo && isset($userInfo['user_id'])) {
return (int) $userInfo['user_id'];
}
return 0;
}
- The function attempts to retrieve the user information stored in the session using Session::read(‘_user’). The _user variable is an array that contains the data of the authenticated user.
- It checks if $userInfo is not empty and if it contains the index user_id.
- If both conditions are met, the value of user_id is returned as an integer.
- In case there is no user information in the session or the user_id index is missing, the function returns 0, indicating that no authenticated user exists.
Confirming that the function returns the ID of the authenticated user or 0 if there is no active session, when comparing $sv_user with api_get_user_id(), the function will return 0 if the user is not authenticated, which can lead to a vulnerability. If an attacker inputs a value of 0 as $sv_user, the comparison will be true, allowing them to bypass authentication and modify data without the appropriate permissions. This logical error allows for the exploitation of a gap in user management, facilitating authentication bypass and the injection of malicious code, such as XSS scripts, into the Chamilo LMS platform.
Second Vulnerability — Cross-site Scripting Injection
Now that we have understood the evasion of the implemented security measures, we can proceed to analyze the insertion of malicious code into the database, which enables the exploitation of a stored XSS vulnerability. This vulnerability can be exploited through certain code snippets, where the lack of adequate filtering allows an attacker to inject malicious scripts, which are then executed when other users view the stored data.
A clear example of how this type of vulnerability manifests is found in the storage_set() function, which is responsible for storing or replacing a value (sv_key) associated with a specific user.
function storage_set($sv_user, $sv_course, $sv_sco, $sv_key, $sv_value)
{
$sv_value = Database::escape_string($sv_value);
$sql = "replace into ".Database::get_main_table(TABLE_TRACK_STORED_VALUES)."
(user_id, sco_id, course_id, sv_key, sv_value)
values
('$sv_user','$sv_sco','$sv_course','$sv_key','$sv_value')";
$res = Database::query($sql);
return Database::affected_rows($res);
}
Reproducing the Vulnerability
1) Using a web browser, we proceed to load the following specific URL, where the malicious JavaScript code will be injected.
https://example.com/main/lp/storageapi.php?action=set&svuser=0&svcourse=0&svcourse=0&svkey=<b/onpointerup=alert(document.domain)>Click-XSS-Here!</b>&svvalue=0
2) Next, the stored code injection must be verified by accessing the following URL, as illustrated in the provided image. This step is crucial to confirm that the vulnerability has been successfully exploited and that the malicious code persists in the target system.
https://example.com/main/lp/storageapi.php?action=getall&svuser=0&svcourse=0&svsco=0

Automating Detection with Nuclei and Python
After detailing the methodology applied during the investigation and thoroughly describing the identified vulnerability, the next step is to create a template for Nuclei that allows for easy and effective detection of the vulnerability. Additionally, an exploit has been developed in Python that not only facilitates the detection of the vulnerability but also generates the appropriate path to visualize the executed injection.

https://github.com/s1kr10s/Exploit-Scripts/blob/main/chamilo-auth-bypass-xss.yaml

https://github.com/s1kr10s/Exploit-Scripts/blob/main/Exploit_auth-xss.py
Video Proof-of-concept
Vendor Security Patch: Auth Bypass and XSS
Security: Fix logical flaw allowing unauthenticated users to send data to a specific table
function storage_can_set($sv_user)
{
// $allowed = ((api_is_platform_admin()) || ($sv_user == api_get_user_id())); ---> Before vulnerable
$allowed = ((api_is_platform_admin()) || (!empty($sv_user) && $sv_user == api_get_user_id())); // Fix code
if (!$allowed) {
echo "ERROR : Not allowed";
}
}
Security: Filter variables for XSS before returning in LP’s storageapi
function storage_get($sv_user, $sv_course, $sv_sco, $sv_key)
{
$sql = "select sv_value
from ".Database::get_main_table(TABLE_TRACK_STORED_VALUES)."
where user_id= '$sv_user'
and sco_id = '$sv_sco'
and course_id = '$sv_course'
and sv_key = '$sv_key'";
$res = Database::query($sql);
if (Database::num_rows($res) > 0) {
$row = Database::fetch_assoc($res);
// if (get_magic_quotes_gpc()) { ---> Before vulnerable
// return stripslashes($row['sv_value']); ---> Before vulnerable
// }
return Security::remove_XSS($row['sv_value']); // Fix code
} else {
return null;
}
}
Advisory Timeline
- September 11, 2024: Initial Contact with the Vendor
- September 20, 2024: Vendor Confirms Vulnerabilities
- September 20, 2024: Vendor Patch (Commit: Auth Bypass and XSS)
- September 21, 2024: Request for CVE ID Submitted
- October 21, 2024: Advisory Released by the Vendor (v1.11.28)
- November 13, 2024: CVE-2024–51142 Assigned
Some Google Dork
inurl:“/main/auth/lostPassword.php” site:*
inurl:“index.php?language=spanish” site:*
intext:“Powered by Chamilo” site:*
inurl:”main/auth/inscription.php” site:*
inurl:”main/auth/inscription.php” site:*
Bye, see you soon…