Bypassear rate-limits

Muchas de los ataques que pueden perpetrarse en una web parten de escaneos que requieren el uso de fuerza bruta con wordlists. Es decir, necesitan realizar decenas de miles de solicitudes al servidor para encontrar usuarios válidos. Hemos visto varios ejemplos de esto en la sección de Enumeración de usuarios, pero también en muchas otras.

Un servidor mínimamente configurado tendrá medidas de rate-limit que impedirán realizar demasiadas solicitudes de forma muy seguida. Esto lo puede lograr, por ejemplo, bloqueando nuestra dirección IP durante unos minutos tras un número de intentos de autenticación fallidos determinado.

Aunque parezca que esta medida es definitiva para impedir ataques de fuerza bruta en un sitio web, también es susceptible si se implementa de manera incorrecta.

1. Bypass de rate-limit intercalando credenciales correctas

Imagina que el servidor tiene un rate-limit que bloquea nuestra IP tras 3 intentos de autenticación fallidos, pero que este contador se resetea cuando iniciamos sesión con unas credenciales correctas. Esto parece lógico, pues no queremos acumular los intentos fallidos para siempre. En esta situación, un atacante que disponga de una cuenta válida en el sistema puede automatizar un ataque que pruebe con 2 combinaciones de usuario y clave de una wordlist y al tercer intento ingrese las credenciales correctas de su cuenta para resetear el contador, y así sucesivamente. Esta simple lógica es capaz de sortear este sistema de rate-limit y posibilitar ataques de fuerza bruta.

Supongamos que deseamos averiguar la contraseña del usuario pepe y contamos con unas credenciales válidas jose:patata1234. Lo primero que necesitamos es crear dos wordlists a medida de modo que nos queden de la siguiente manera:

    usernames.txt |  passwords.txt
  ----------------------------------
    pepe          |  pass-1
    pepe          |  pass-2
    jose          |  patata1234
    pepe          |  pass-3
    pepe          |  pass-4
    jose          |  patata1234
    pepe          |  pass-5
    pepe          |  pass-6
    ...           |  ...

Estas wordlists las podemos generar fácilmente a partir de una wordlist de contraseñas como /usr/share/wordlists/rockyou.txt. No obstante, por abreviar el código, supondremos que la wordlist es siimplemente ./passwords.txt.

# Primero calculamos el tamaño de la wordlist
wc -l ./passwords.txt

# Supongamos que obtenemos que hay 1.000.000 de contraseñas. Entre 2 son 500.000.
for i in $(seq 1 500000); do; echo -e "pepe\npepe\njose" >> usernames.txt; done

# Ahora hacemos lo mismo con las contraseñas usando el valor de la wordlist
cat passwords.txt | xargs -I {} echo -e "{}\n{}\npatata1234" >> passwords2.txt

Una vez tenemos nuestras dos worlists preparadas, se lanza el ataque usando el modo pitchfork. Este modo lo que hace es ir cogiendo el usuario_1 y password_1, luego el usuario_2 y password_2, etc. Es decir, se selecciona el valor en la misma posición en ambas wordlists. También es importante lanzarlo usando un solo hilo con -t 1, ya que lanzar varios hilos concurrentes puede ocasionar que se prueben más solicitudes incorrectas de la cuenta antes de resetear el contador. Por último, en este ejemplo supondremos que cuando se prueba una combinación correcta obtenemos una redirección (código 302) y cuando es una combinación incorrecta se obtiene un código 200. Esto lo usaremos como filtro para obtener solo las combinaciones correctas.

ffuf -X POST -u http://10.10.10.10 \
  -d "username=FUZZUSER&password=FUZZPASS" \
  -w usernames.txt:FUZZUSER \
  -w passwords2.txt:FUZZPASS \
  -mode pitchfork -t 1 \
  -mc 302

2. Bypass de rate-limit de cuente mediante Password Spray Attack

Otra de las formas de aplicar filtros de rate-limit es bloquear por cuenta, en lugar de por IP. Esto quiere decir que el bypass anterior ya no funcionará, porque al tercer intento de contraseña que se pruebe con el usuario pepe el sistema bloqueará a dicho usuario y ya no podremos seguir probando.

En estos casos, el enfoque de un atacante puede cambiar. En lugar de probar todas las contraseñas posibles en un usuario, ¿por qué no probar solo un pequeño conjunto de contraseñas más comunes en todos los usuarios? Por estadística, algún usuario tendrá alguna contraseña débil dentro de ese pequeño conjunto. Esto es lo que se conoce como un ataque Password Spray Attack, y es muy sencillo realizarlo.

Partiremos de una wordlist de usuarios como /usr/share/wordlists/Usernames/xato-net-10-million-usernames.txt, por abreviar aquí supondremos que la wordlist se llama usernames.txt. También necesitamos una wordlist de contraseñas como /usr/share/wordlists/rockyou.txt, por abreviar la llamaremos passwords.txt.

En este escenario tenemos un sistema que bloquea a un usuario tras 10 intentos de inicio de sesión fallidos, por lo que solo podremos probar, como máximo, 10 contraseñas con cada usuario (pero mejor probamos 9, así no los bloqueamos). De nuevo, supondremos que cuando tenemos una autenticación exitosa recibimos un código 302 de redirección y si es fallida obtenemos un código 200.

# Primero obtenemos un subconjunto de las 9 contraseñas más comunes
head -n 9 passwords.txt > sub.txt

# Ahora lanzamos el ataque para que se prueben todas las posibles combinaciones
ffuf -X POST -u http://10.10.10.10 \
  -d "username=FUZZUSER&password=FUZZPASS" \
  -w usernames.txt:FUZZUSER \
  -w sub.txt:FUZZPASS \
  -mc 302

3. Bypass de rate-limit de cuenta mediante el envío de múltiples credenciales por solicitud

Otra técnica de bypass especialmente utilizada en APIs REST consiste en el envío de múltiples contrasñeas en los datos enviados. Las APIs REST reciben normalmente datos en formato JSON, lo que significa que podremos ver en el body de la request algo parecido a esto:

{
  "username": "pepe",
  "password": "calamaresFritos"
}

Una característica del formato JSON es que permite declarar listas. Esto abre la posibilidad de enviar una solicitud como la siguiente:

{
  "username": "pepe",
  "password": ["abcdef", "password", "pass1234", "pa$$w0rd!", "111111", "..."]
}

De esta manera, en lugar de un ataque de fuerza bruta donde se envían miles de solicitudes, en este caso solo se envía una, por lo que ningún sistema de rate-limit lo va a bloquear. Sin embargo, esto solo funcionará si la lógica de la aplicación no está bien implementada. No obstane, es un vector de ataque que debemos conocer y probar en nuestros sistemas para protegerlos.

Para este ejemplo no se provee ninguna prueba de concepto ya que puede realizarse manualmente cogiendo una wordlist, aplicando filtros de reemplazo con expresiones regulares o caracteres de comodín en cualquier editor de texto para generar el formato correcto y pegando manualmente las contraseñas en la resquest.

Un ejemplo en un editor cualquiera, consistiría en activar el modo de buscar y reemplazar (en muchos editores se activa con CTRL+R) y activar la opción de caracteres comodín. A continuación, para convertir una wordlist con cada palabra en una linea distinta en una lista separada por comas donde cada palabra está entrecomilladas podemos aplicar los siguientes filtros:

FIND    : \r\n      # Nota: Si este no funciona, prueba solo con \n
REPLACE : ","

Solo faltará, como habrás observado, agregar una comilla " al comienzo y otra al final del resultado. De esta manera tendremos nuestra lista preparada para pegarla en la request.

Usa el conocimiento para construir, no para destruir 🎓
Copiado al portapapeles
menu