Weitere Veröffentlichungen

In unserem Labor schreiben wir regelmäßig in Form von Blogbeiträgen über identifizierte Sicherheitslücken, welche nach dem Responsible Disclosure Verfahren veröffentlicht werden.

Veröffentlichung (CVE-2024-XXXXX)

Remote Code Execution and Bypassing .htaccess Rule in ILIAS eLearning platform

19.02.2024 - Rene Rehme

Im Zuge eines Penetrationstests haben wir mehrere Sicherheitslücken in ILIAS Version 7.27, 8.8 und 9.0 Beta 2 identifiziert. In dieser Veröffentlichung geht es um eine Remote Code Execution (RCE) Sicherheitslücke, welche mit Hilfe einer weiteren Arbitrary File Deletion Sicherheitslücke bewerkstelligt werden konnte. Eine RCE erlaubt einem Angreifer, serverseitig Code auszuführen.

Responsible Disclosure

18. Jan. 2024 - Die Sicherheitslücke wurde identifiziert.
23. Jan. 2024 - Die Sicherheitslücke wurde an ILIAS gemeldet.
29. Jan. 2024 - ILIAS bestätigt den Erhalt des Reports.
16. Feb. 2024 - ILIAS bestätigt, dass die Sicherheitslücke geschlossen wurde.
16. Feb. 2024 - Patched ILIAS-Release: 8.9, 7.28

Allgemeine Beschreibung

Vulnerabel ist das SCORM-Lernmodul, welches dafür verwendet wird, Lernmaterial zur Verfügung zu stellen. Ein potenzieller Angreifer kann bei einer Ausnutzung dieser Sicherheitslücke serverseitig Code ausführen. Die Voraussetzung einer Ausnutzung ist ein User-Account mit den Rechten write und read, für die Erstellung und das Einsehen eines SCORM-Lernmoduls.

 

Die Remote Code Execution (RCE) ergibt sich durch zwei Faktoren:

  1. Ein Bug in der File-Upload Verarbeitung während eines SCORM-Lernmodul Imports
  2. Der Möglichkeit Dateien aus dem data Verzeichnis direkt aufzurufen (durch Arbitrary File Deletion)

Proof of Concept (PoC)

Bei der Verarbeitung eines Uploads während eines Imports kann über die Option SCORM-Lernmodul importieren ein Fehler im Programm-Code verursacht werden. Dabei wird in einem Fall eine Exception generiert, welche essenziell für eine erfolgreiche Ausnutzung ist (mehr dazu im späteren Textverlauf).

 

Zunächst muss eine shell.php Datei mit dem PHP-Code, welcher später ausgeführt werden soll, gepackt in einem .zip File über die genannten Import-Option hochgeladen werden.

 

Der Programm-Code sieht vor, das hochgeladene .zip File im Verzeichnis /data/lm_data  [1] innerhalb des documentRoot zu entpacken [2]. Hierfür ist ein temporärer Ordner $lmTempDir vorgesehen, welcher erstellt und mit einem aktuellen Timestamp betitelt wird. 

 

Normalerweise werden in ILIAS bei Import Funktionalitäten hochgeladene Dateien in einem Verzeichnis entpackt, welches außerhalb des documentRoot liegt. Hier wurde es nicht so gemacht.

class.ilObjSAHSLearningModuleGUI.php
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;

Da das hier nicht passiert, eröffnet sich ein Teil des Angriffvektors, da der ilScormAiccImporter [3] über die Methode importXmlRepresentation [4] aufgrund einer nicht existenten Manifestdatei false zurückgibt, wodurch $importFromXml mit false deklariert bleibt. Folgender Logeintrag wird hierbei generiert:

ILIAS 7 - Errorlog
[496da] [2024-02-18 23:36:52.162907] myilias_root.INFO: ilScormAiccImporter::importXmlRepresentation:473 error file lost while importing

Anschließend wird der else-Zweig der If-Bedingung für die weitere Verarbeitung durchlaufen. Dabei wird das hochgeladene File ein weiteres Mal final entpackt. Es ist wichtig zu beachten, dass das temporäre Verzeichnis $lmTempDir (siehe oben) nicht gelöscht wird. Die Methode renameExecutables() [3] sollte außerdem sicherstellen, dass in unserem Fallbeispiel die entpackte .php-Datei von shell.php in shell.php.sec umbenannt wird. Durch diese Umbenennung der Dateiendung wäre die Datei bei direktem Aufruf nicht mehr ausführbar und der PHP-Code könnte nicht mehr als solcher interpretiert werden. Das Problem besteht darin, dass diese Funktion nicht auf das temporäre Verzeichnis angewendet wird, sondern nur für das durch $newObj->createDataDirectory(); [1] erstelle Verzeichnis.

class.ilObjSAHSLearningModuleGUI.php
[...]
// 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 höheren ILIAS Versionen wird entgegen der Version 7 bei dieser Aktion eine Exception [1] generiert, wodurch die Ausführung des Programm-Codes nach dem Entpacken der Import Datei unterbrochen wird und renameExecutables() ebenfalls nicht mehr ausgeführt wird:

ILIAS Errorlog
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:24

Letztendlich wird unabhängig der ILIAS Version eine ausführbare PHP Datei durch einen Import (Upload) innerhalb des documentRoots unverändert abgelegt.

content image
content image

Das durch den Import erstellte Verzeichnis bleibt nach aktuellem timestamp benannt und existent. Der Pfad zur Datei shell.php is somit bekannt bzw. kann leicht herausgefunden werden. In unserem PoC wird der korrekte Pfad z.B. wie folgt ermittelt:

Part of PoC code
// 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)

Wer sich schon einmal mit ILIAS beschäftigt hat, wird berechtigterweise sagen, dass der direkte Aufruf von Dateien aus dem data Verzeichnis durch den WAC Check überhaupt nicht möglich sein sollte.

 

Der direkte Aufruf der Datei wird normalerweise über eine Regelung via .htaccess durch den WebAccessChecker blockiert:

 

.htaccess
RewriteRule ^data/.*/.*/.*$ ./Services/WebAccessChecker/wac.php [L]

Wenn das .htaccess file entfernt werden würde, greift diese Regel nicht mehr. Wir haben bei unserer Ausforschung eine Arbitrary File Deletion Sicherheitslücke identifizieren können, welche genau das bewerkstelligen kann. Der Angriffsvektor kann also in Kombination mit dieser Sicherheitslücke erfolgreich durchgeführt werden (die Bedingungen einer Ausnutzung sind gegeben).

 

Dabei wird die Datei .htaccess via Direcory Traversalaus dem documentRoot der ILIAS Instanz durch eine unlink() Funktion entfernt.

 

Der direkte Aufruf der Datei shell.php kann somit stattfinden. Die RCE wird durchgeführt.

Klassifikation

CWE-20 Improper Input Validation

CWE-434 Unrestricted Upload of File with Dangerous Type

Bewertung

Exploitability Metrics

Attack Vector (AV): N
Es handelt sich um einen Netzwerkangriff. Die verwundbare Komponente ist "aus der Ferne ausnutzbar" und wird als ein Angriff betrachtet, der auf Protokollebene über das Internet stattfindet.
 

Attack Complexity (AC): L
Besondere Zugangsbedingungen oder mildernde Umstände bestehen nicht.
 

Attack Requirements (AT): N
Es wird davon ausgehen, dass die Payload in den meisten Fällen erfolgreich ausgeführt wird. Es werden keine besonderen Einstellungen oder Konfigurationen vorrausgesetzt.
 

Privileges Required (PR): H
Der Angreifer benötigt Privilegien, die Benutzerfunktionen eines Users bereitstellen, welche genau genommen das Lesen und Schreiben von SCORN Lernmaterial erlaubt. Für eine Ausnutzung reicht i.d.R ein Tutoren-Account.

User Interaction (UI): N
Es ist keine User-Interaktion durch ein Opfer notwendig.

Vulnerable System Impact Metrics

Confidentiality (VC): H
Bei einen erfolgreichen Angriff kommt es zu einem totalen Verlust der Vertraulichkeit. Ressourcen innerhalb der betroffenen Komponente werden dem Angreifer bei gegebenem Lesezugriff und entsprechender Berechtigung durch den Webserver-User www-data offengelegt.
 

Integrity (VI): H
Bei einem erfolgreichen Angriff kommt es zu einem totalen Verlust der Integrität. Der Angreifer ist in der Lage Dateien der betroffenen Anwendung zu verändern.
 

Availability (VA): H
Bei einem erfolgreichen Angriff ist der Angreifer ist in der Lage, den Zugang zu den Ressourcen der betroffenen Applikation vollständig zu verweigern, indem z.B. Dateien oder Daten gelöscht werden.

Subsequent System Impact Metrics

Confidentiality (SC): H
Bei einem erfolgreichen Angriff kommt es zu einem totalen Verlust der Vertraulichkeit. Ressourcen und Komponenten des Betriebssystems werden dem Angreifer bei gegebenem Lesezugriff und entsprechender Berechtigung durch den Webserver-User www-data offengelegt. Der Zugriff auf MySQL, sonstige Komponenten oder andere Applikationen wird tendenziell ermöglicht.
 

Integrity (SI): H
Bei einem erfolgreichen Angriff kommt es zu einem totalen Verlust der Integrität. Der Angreifer ist in der Lage Dateien des Betriebssystems oder anderen Applikationen zu verändern.
 

Availability (SA): H
Bei einem erfolgreichen Angriff ist der Angreifer ist in der Lage, den Zugang zu den Ressourcen aus Komponenten des Betriebssystems oder ggf. anderen Applikationen vollständig zu verweigern, indem z.B. Dateien oder Daten mit den Rechten des Webserver-Users gelöscht werden.

Externe Informationen