Discussione Validare arbitrariamente un JWT exploitando una vulnerabilità SSRF (HackTheBox - TheNotebook)

0xbro

Super Moderatore
24 Febbraio 2017
4,464
179
3,755
1,825
Ultima modifica:

HackTheBox - TheNotebook [Medium]​

TheNotebook è una macchina Linux di difficoltà "Medium" di HackTheBox. Esegue un'applicazione web vulnerabile ad Authorization Bypass causato da una vulnerabilità Server Side Request Forgery che permette la convalida di Json Web Token arbitrari. Una volta autenticati, l'applicazione soffre di un bug che ci permette di eseguire codice PHP, con conseguente possibilità di Remote Command Execution. I permessi eccessivi assegnati al file di backup della home di Noah permettono di copiare ed estrarre il suo contenuto, permettendo di rubare la chiave privata SSH di Noah. Infine, l'utente è autorizzato a eseguire comandi all'interno di un docker container utilizzando privilegi elevati. Poiché la versione di runc utilizzata nelle versioni precedenti a Docker 18.09.2 soffre di un file-descriptor mishandling, è possibile sfruttare la CVE-2019-5736 per iniettare un utente root malevolo e ottenere privilegi elevati sul target.

Writeup della macchina:​


Video:​

 
@0xbro: nel video mostri alcuni comandi ma poi provi a spiegarli e mi sembra che tu non sappia totalmente cosa facciano. Hai preso spunto da qualche sito o è tutta farina del tuo sacco?

causato da una vulnerabilità Server Side Request Forgery
Decisamente no, stai facendo confusione. Si parla di vulnerabilità Server-Side Request Forgery quando questa consente ad un utente di stimolare un'applicazione da lato server ad effettuare richieste HTTP ad un dominio arbitrario scelto da lui.

Quello che hai fatto (senza saperlo):
  1. Hai avviato un server Python per hostare il file contenente la chiave privata.
  2. Hai ricaricato la pagina con il nuovo valore (l’URL del tuo file) iniettato nel parametro kid del JWT.
  3. Il server di autenticazione verifica i dati e ritorna un JWT firmato con la chiave privata.
  4. Il client dell'utente (il tuo browser) utilizza il JWT per accedere alla risorsa protetta passando il JWT nell’header Authorization.
  5. La risorsa verifica l’autenticità del token con la chiave pubblica.
Sei tu a fare la richiesta con il JWT che hai hackerato e non il server dell’applicazione (rileggi il punto 4), quindi non c’è un SSRF.

205FCDF7-C741-474F-8AE0-60978C35897A.jpeg


PS. Ti consiglio di utilizzare il forum (fai una write-up) e non YouTube se vuoi pubblicare una guida.
 
@0xbro: nel video mostri alcuni comandi ma poi provi a spiegarli e mi sembra che tu non sappia totalmente cosa facciano. Hai preso spunto da qualche sito o è tutta farina del tuo sacco?
Ovviamente è tutta farina del mio sacco, se i comandi a cui ti riferisci sono quelli per creare il JWT semplicemente li ho cercati online, come farebbe ogni persona che non ha mai firmato un token. Si poteva fare direttamente anche tramite JWT.io, ma ho preferito così

Quello che hai fatto (senza saperlo):
  1. Hai avviato un server Python per hostare il file contenente la chiave privata.
  2. Hai ricaricato la pagina con il nuovo valore (l’URL del tuo file) iniettato nel parametro kid del JWT.
  3. Il server di autenticazione verifica i dati e ritorna un JWT firmato con la chiave privata.
  4. Il client dell'utente (il tuo browser) utilizza il JWT per accedere alla risorsa protetta passando il JWT nell’header Authorization.
  5. La risorsa verifica l’autenticità del token con la chiave pubblica.
Sei tu a fare la richiesta con il JWT che hai hackerato e non il server dell’applicazione (rileggi il punto 4), quindi non c’è un SSRF.
La SSRF avviene al momento della validazione del JWT siccome posso stimolare l'applicazione da lato server ad effettuare richieste HTTP (vedi minuto 2:19) ad un dominio arbitrario scelto da me (10.10.14.5 che non è un dominio ma un IP, ma ci siamo capiti). L'applicazione di default utilizza la propria chiave privata (ricavata in localhost:7070) per verificare l'integrità del token, dando per scontato che un JWT verificato tramite quella chiave sia valido. Siccome è possibile fare in modo che il server utilizzi una chiave differente (ndr. SSRF poichè istruiamo il server a contattare un host esterno) per velidare il token, e inoltre dà per scontato che se tale verifica ha successo allora il token è valido, basta utilizzare una coppia di chiavi lecite.

L'architettura che hai messo te è quella standard, ma questa è un'applicazione custom (errore mio darlo per scontato), non utilizza nemmeno l'header Authorization ma gestisce tutto tramite Cookies.
Questa è la funzione vulnerabile:
Python:
def checkSession(authCookie):
    default = False, False, None
    if authCookie is None:
        return default
    else:
        try:
            header = authCookie.split('.')[0]
            header = json.loads(base64.b64decode(header+'='*(len(header)%4)))
            privKey = RSA.import_key(requests.get(header['kid']).text.encode())    # SSRF, kid può essere controllato
            pubKey = privKey.publickey().export_key()
            payload = jwt.decode(authCookie, key=pubKey, verify=True, algorithms='RS256')
        except Exception as e:
            print(e)
            return default
        username = payload['username']
        admin_cap = payload['admin_cap']
        user = User.query.filter_by(username=username).first()
        if user is None:
            return default
        else:
            if admin_cap:
                return True, True, user
            else:
                return True, False, user
 
In questo caso l'implementazione non dovrebbe recuperare la chiave privata dal client perchè è come non utilizzare nessuna chiave.

L'applicazione di default utilizza la propria chiave privata per verificare l'integrità del token
Il codice che hai postato non contiene alcuna indicazione riguardo alla chiave privata dell'applicazione. Inoltre le chiavi private sono utilizzate per firmare, non per verificare. Le chiavi pubbliche sono utilizzate per verificare.

L'applicazione potrebbe verificare o non verificare nulla, ma se riesci a manipolare la payload senza conoscere la chiave privata del server allora non stai effettivamente verificando.

Python:
pubKey = privKey.publickey().export_key() # verifica authCookie, che equivale a non verificare nulla dato che la chiave può essere fornita da chiunque

Il design corretto sarebbe dovuto essere preparato in modo che il server usi la sua chiave privata per firmare il JWT (prevenendo la modifica da lato client) così che dopo il server possa verificare la firma quando rivede il token. Il client non dovrebbe mai sapere la chiave privata, difatti da qui prende il nome di chiave "privata". Insomma, altrimenti si possono pure saltare tutte le prassi concernenti chiavi se si lascia che il client selezioni la chiave con cui verificare la firma.

Riguardo all'SSRF, ora che vedo il codice (dove l'hai preso?), c'è. È fatto di proposito, non avendo il codice davanti pensavo ad un pessimo meccanismo in cui il contenuto della pagina di quell'URL (la chiave privata) veniva prima scaricata dal client (niente SSRF) e successivamente passata al server. Questo è un altro motivo per evitare delle confusioni: utilizzare il forum correttamente (fare una write-up) e quindi evitare di postare per poi linkare i video su YouTube.