Discussione Risolto [HackTheBox] Challenge WAFfle-y Order

0xbro

Super Moderatore
24 Febbraio 2017
4,461
179
3,762
1,825
Ultima modifica:
Ciao a tutti, cerco aiuto per la risoluzione della challenge WAFfle-y Order su HackTheBox.
La challenge è già stata ritirata ed ha difficoltà Medium.

Di seguito il download (password hackthebox):








1    Summary

​La challenge si presenta come un sito web che permette di fare degli ordini relativi a gelati o waffles.
Di seguito l'interfaccia:

1650718980523.png

Il front-end comunica con il backend tramite richieste in JSON e utilizzano un cookie serializzato (che verrà poi deserializzato) per definire il nome dell'utente:
HTTP:
POST /api/order HTTP/1.1
Host: localhost:1337
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://localhost:1337/
Content-Type: application/json
Origin: http://localhost:1337
Content-Length: 37
Connection: close
Cookie: PHPSESSID=Tzo5OiJVc2VyTW9kZWwiOjE6e3M6ODoidXNlcm5hbWUiO3M6MTA6Imd1ZXN0XzYyNWQiO30%3D
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

{"table_num":"test","food":"WAFfles"}

Il contenuto del cookie è
1650528785827.png


Di seguito trovate il link alle mie note con i miei appunti:

2    Set up

​La challenge fornisce un docker, per cui è necessario installare docker sulla propria macchina ed aggiungere l'utente al gruppo docker.
Una volta fatto ciò basta eseguire il comando chmod +x build_docker.sh ed eseguire lo script ./build_docker.sh

2.1    build_docker.sh

Bash:
#!/bin/bash
docker build -t web_waffley_order .
docker run --name=web_waffley_order --rm -p1337:80 -it web_waffley_order

2.2    Dockerfile

Bash:
FROM alpine:edge

# Setup usr
RUN adduser -D -u 1000 -g 1000 -s /bin/sh www

# Install system packages
RUN apk add --no-cache --update supervisor nginx

# Install PHP dependencies
RUN apk add --no-cache --update php7-fpm php7-xml php7-simplexml php7-json

# Configure php-fpm and nginx
COPY config/fpm.conf /etc/php7/php-fpm.d/www.conf
COPY config/supervisord.conf /etc/supervisord.conf
COPY config/nginx.conf /etc/nginx/nginx.conf

# Copy challenge files
COPY challenge /www

COPY flag /flag

# Setup permissions
RUN chown -R www:www /var/lib/nginx

# Expose the port nginx is listening on
EXPOSE 80

# Populate database and start supervisord
CMD /usr/bin/supervisord -c /etc/supervisord.conf

3    File interessanti dell'applicazione

​I file che presentano informazioni o utilità interessanti sono Router.php (la classe che imposta le rotte dell'aaplicazione), index.php (entry point della challenge), XmlParserModel.php (parser XML), UserModel.php (la classe che imposta il valore dell'username) e OrderController.php (la classe che si occupa di elaborare gli ordini).

Di seguito i rispettivi codici sorgente:

3.1    Router.php



Vedi: https://pastebin.com/Cy0pPyi4

3.2    index.php



Vedi: https://pastebin.com/ecPkUgzr

3.3    XmlParserModel.php



Vedi: https://pastebin.com/n1r0pqxw

3.4    UserModel.php



Vedi: https://pastebin.com/LdyDCr2J

3.5    OrderController.php



Vedi: https://pastebin.com/K8Eg0xVX


4    Osservazioni

​Da quel che posso vedere e presupporre, l'entry point per l'exploitation chain è la classe OrderController.php tramite la funzione unserialize(). E' possibile passare un cookie serializzato arbitrariamente in modo da fare PHP Object Injection ed eseguire i Magic Methods della classe XmlParserModel.php (nello specifico, la funzione __wakeup() in modo da abilitare la variabile d'ambiente $_ENV["debug"] e poter quindi scrivere dentro /tmp/orders.log)

Potrebbe anche essere interessante questa porzione di codice, che però non mi è ancora chiaro cosa faccia esattamente:
PHP:
spl_autoload_register(function ($name){
    if (preg_match('/Controller$/', $name))
    {
        $name = "controllers/${name}";
    }
    if (preg_match('/Model$/', $name))
    {
        $name = "models/${name}";
    }
    include_once "${name}.php";
});

I problemi però sono molteplici:
  • Come bypassiamo il controllo della funzione safe_object()?
  • Una volta bypassato il controllo, come otteniamo la flag?
  • La strada della PHP Object Injection è giusta o ci sono altre vie che mi sono sfuggite?
Al momento abbiamo il controllo arbitrario di $user->username e potenzialmente anche di ${body} (php extended syntax che potrebbe condurre a RCE in caso riuscissimo a bypassare il controllo su $order = json_decode($body); )...

Per ora le primitive interessanti che ho trovato sono:
  • __wakeup() su XmlParserModule.php --> permette di parsare un XML arbitrario e valorizzare diverse variabili in $_ENV
  • file_put_contents('/tmp/orders.log', "[${date}] ${body} by {$user->username}\n", FILE_APPEND); --> scrittura su un file noto
  • 'message' => "Hello {$user->username}, your {$order->food} order has been submitted successfully." --> $user->username è controllabile tramite il cookie serializzato
  • if ($_ENV['debug']) --> bypassabile con un cookie serializzato con la classe XmlParserModule
  • include __DIR__."/views/${view}.php"; --> la funzione view() di Router.php utilizza un include che potrebbe fare comodo, ma non ho idea se si possa realmente utilizzare


Qualche idea o qualcuno che abbia voglia di sbatterci la testa con me? :)
 
Giusto un'idea: la callback in spl_autoload_register potrebbe essere vulnerabile ad LFI. Potrebbe non essere exploitabile nel modo in cui penso ma potresti provare: se nella unserialize dal cookie passi nel nome della classe una stringa che punta al file flag potrebbe includerlo (e quindi mostrarne il contenuto), ho fatto una prova veloce ma nel mio ambiente non tronca la path e quindi non trova flag perché ci aggiunge .php. Non so inoltre che versione di PHP usa la challange.
 
  • Grazie
Reazioni: 0xbro
Dal dockerfile vedo che installa diverse dipendenze riferite a php7-fpm, per cui suppongo usi PHP 7.

Effettivamente non ho ancora cercato di utilizzare spl_autoload_register() nell'exploitation chain, provo e faccio sapere come va.
Grazie per il tip!
 
Ultima modifica:
Ok dopo molte ore di test e di ricerca ho finalmente capito il path da intraprendere e dove risiedano le varie vulnerabilità.

Primo ostacolo: bypassare la funzione safe_object() che previene di sfruttare la PHP deserialization.
Ho scoperto che regex e oggetti PHP serializzati non vanno molto d'accordo e che la regex $num_matches = preg_match_all('/(^|;)O:\d+:"([^"]+)"/', $serialized_data, $matches); può essere bypassata tramite un parser differential.
L'articolo è il seguente:

Non l'ho ancora provato ma mi sembra un'ottima strada da intraprendere, soprattutto perché dà modo di proseguire con l'exploit chain che ho già verificato e che mostrerò di seguito.

Secondo ostacolo: dato per vero che è possibile bypassare il controllo sulla funzione safe_object(), possiamo a questo punto sfruttare la PHP deserialization per iniettare un oggetto XmlParserModel e sfruttare il magic method __wakeup(). Anche in questo caso viene effettuato un controllo tramite regex, in questo caso sulla sicurezza del file XML passato:
Codice:
public function __wakeup()
    {
        if (preg_match_all("/<!(?:DOCTYPE|ENTITY)(?:\s|%|&#[0-9]+;|&#x[0-9a-fA-F]+;)+[^\s]+\s+(?:SYSTEM|PUBLIC)\s+[\'\"]/im", $this->data))
        {
            die('Unsafe XML');
        }
        $env = @simplexml_load_string($this->data, 'SimpleXMLElement', LIBXML_NOENT);

        if (!$env)
        {
            die('Malformed XML');
        }

        foreach ($env as $key => $value)
        {
            $_ENV[$key] = (string)$value;
        }
    }
La funzione simplexml_load_string() assieme all'attributo LIBXML_NOENT rendono la funzione vulnerabile a XML External Entity Injection, verificabile commentando i vari die(). Il problema è che però la regex inibisce l'utilizzo di qualsiasi external entity che faccia utilizzo delle parole SYSTEM o PUBLIC... o quasi.
Nell'articolo linkato sotto ho trovato un modo per bypassare anche questa regex ed essere in grado di leggere qualsiasi file contenuto all'interno della macchina.

Tramite una combinazione di due payload, uno hostato in remoto (che verrà richiamato dal primo payload) e uno iniettato nell'oggetto PHP arbitrario, (encodato con le HTML entities), è possibile bypassare la regex, eseguire una XXE ed exfiltrare qualunque file, compresa la flag.

File .env dentro cui scrivere il primo payload:
1650718365483.png


dtd hostata in remoto che eseguirà l'exfiltration:
1650718429702.png


Script per serializzare il payload:
PHP:
<?php

class XmlParserModel
{
    private string $data;
    private array $env;
    public $username = 'test';
    public function __construct($data)
    {
        $this->data = $data;
    }
}

$obj = new XmlParserModel(file_get_contents('.env'));
$serializedObj = serialize($obj);

print_r($serializedObj);

print("\n");
print(base64_encode($serializedObj));
?>

1650718832291.png


Nei prossimi giorni cercherò di portare a conclusione la challenge e scrivere un exploit funzionante, cercando di sfruttare il parser differential e dare il via dunque alla chain completa.
 
La strada del parser differential era corretta, è possibile creare un oggetto serializzato arbitrario che bypassi il controllo tramite la struttura C: anzichè O: e che dia quindi il via all'exploitation chain.

Seguirà nei prossimi giorni un writeup ufficiale con delle spiegazioni più dettagliate e un exploit funzionante ;)