Skip to content

Web Fuzzing

Fuzzing de directorios

c
❯ ffuf -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://172.16.68.10:80/FUZZ

Fuzzing de archivos

c
❯ ffuf -w /usr/share/seclists/Discovery/Web-Content/common.txt -u http://172.16.68.10:80/w2ksvrus/FUZZ.html -e .php,.html,.txt,.bak,.js -v
  • -e : agrega automáticamente cada extensión listada a cada palabra del diccionario, probando varias variantes en una sola pasada.

Fuzzing recursivo

c
❯ ffuf -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -ic -v -u http://172.16.68.10:80/FUZZ -e .html -recursion
c
❯ ffuf -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -ic -u http://172.16.68.10:80/FUZZ -e .html -recursion -recursion-depth 2 -rate 500
  • -recursion : cuando ffuf encuentra un directorio, automáticamente vuelve a fuzzear dentro de él.
  • -recursion-depth 2 : limita cuántos niveles de profundidad explora (evita que el escaneo se vuelva infinito o excesivamente largo en sitios con muchos subdirectorios).
  • -rate 500 : limita la velocidad a 500 peticiones por segundo (ver sección de control de velocidad más abajo).

Fuzzing de parámetros y valores

Parámetros GET

c
https://example.com/search?query=fuzzing&category=security
c
❯ ffuf -X GET -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -ic -u http://172.16.68.10:80/get.php?x=FUZZ -t 200
  • -t 200 : usa 200 hilos concurrentes, acelerando el escaneo (ajustar según la tolerancia del objetivo/red).

Parámetros POST

c
POST /login HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded

username=your_username&password=your_password
c
❯ wenum -w /usr/share/seclists/Discovery/Web-Content/common.txt --hc 404 -u "http://172.16.68.10:80/get.php?x=FUZZ"

...
 Code    Lines     Words        Size  Method   URL 
...
 200       1 L       1 W        25 B  GET      http://172.16.68.10:80/get.php?x=OA... 

Total time: 0:00:02
Processed Requests: 4731
Filtered Requests: 4730
Requests/s: 1681
c
❯ ffuf -u http://172.16.68.10:80/post.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "y=FUZZ" -w /usr/share/seclists/Discovery/Web-Content/common.txt -mc 200 -v

Enumeración de nombres de usuario válidos abusando de un mensaje de error distintivo ("username already exists"):

c
❯ ffuf -w /usr/share/wordlists/seclists/Usernames/Names/names.txt -X POST -d "username=FUZZ&email=x&password=x&cpassword=x" -H "Content-Type: application/x-www-form-urlencoded" -u http://172.16.68.11:80/customers/signup -mr "username already exists"

Fuerza bruta combinando dos wordlists (usuario + contraseña) con variables nombradas:

c
❯ ffuf -w valid_usernames.txt:W1,/usr/share/wordlists/seclists/Passwords/Common-Credentials/10-million-password-list-top-10000.txt:W2 -X POST -d "username=W1&password=W2" -H "Content-Type: application/x-www-form-urlencoded" -u http://172.16.68.11:80/customers/login -fc 200

Ocultar respuestas fallidas por texto (-fr) — clave en fuerza bruta de login

Cuando se ataca un formulario de login, casi siempre hay una única respuesta de "credenciales inválidas" que se repite en el 99% de los intentos. En vez de filtrar por código de estado (que a menudo es 200 tanto para éxito como para fallo), conviene ocultar directamente las respuestas que contengan ese texto de error:

c
❯ ffuf -X POST -u http://172.16.68.10/admin.php -H "Content-Type: application/x-www-form-urlencoded" -d "username=admin&password=FUZZ" -w wordlist.txt -fr 'Login Failed!'
  • -fr : filtra (oculta) por regex sobre el cuerpo de la respuesta — cualquier respuesta que contenga el texto/patrón indicado (aquí, Login Failed!) no se muestra en los resultados.
  • El resultado neto es que solo se muestran los intentos que no contienen el mensaje de error, es decir, los candidatos a contraseña válida.
  • Es el complemento natural de -mr (que muestra solo lo que coincide con un patrón): -fr muestra todo excepto lo que coincide.
  • Útil también contra APIs: si una respuesta de error siempre trae "success":false en el JSON, se puede usar -fr '"success":false' para quedarse solo con las respuestas exitosas.
c
❯ wfuzz -X POST -z file,/path/to/wordlist.txt -d "username=FUZZ&password=password123" -H "Content-Type: application/x-www-form-urlencoded" https://example.com/login

Fuzzing de Virtual Hosts y subdominios

Gobuster

c
❯ gobuster dns -d domain.com -w /usr/share/wordlists/dirb/common.txt -i
c
❯ gobuster vhost -u domain.com -w /usr/share/wordlists/dirb/common.txt
c
❯ gobuster vhost -u domain.com -w /usr/share/wordlists/dirb/common.txt -v
c
❯ gobuster vhost -u http://domain.local:81 -w /usr/share/seclists/Discovery/Web-Content/common.txt --append-domain
c
❯ gobuster vhost -u http://domain.local:81 -w /usr/share/seclists/Discovery/Web-Content/common.txt --append-domain

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:             http://domain.local:81
[+] Method:          GET
[+] Threads:         10
[+] Wordlist:        /usr/share/SecLists/Discovery/Web-Content/common.txt
[+] User Agent:      gobuster/3.6
[+] Timeout:         10s
[+] Append Domain:   true
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
Found: .git/logs/.domain.local:81 Status: 400 [Size: 157]
...
Found: admin.domain.local:81 Status: 200 [Size: 100]
Found: android/config.domain.local:81 Status: 400 [Size: 157]
...
Progress: 4730 / 4730 (100.00%)
===============================================================
Finished
===============================================================

Apuntando directamente a una IP:puerto con el dominio especificado por separado (útil cuando aún no hay DNS configurado para el objetivo):

c
❯ gobuster vhost -u http://172.16.68.12:8080 --domain domain.local -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt --append-domain

Filtrado de resultados

c
# Busca directorios con código de estado 200, excluyendo por cantidad de palabras y con tamaño de respuesta superior a 500 bytes.
❯ ffuf -u http://example.com/FUZZ -w wordlist.txt -mc 200 -fw 427 -ms >500

# Filtra las respuestas con códigos de estado 404, 401 y 302.
❯ ffuf -u http://example.com/FUZZ -w wordlist.txt -fc 404,401,302

# Busca archivos de copia de seguridad con la extensión .bak y un tamaño entre 10 KB y 100 KB.
❯ ffuf -u http://example.com/FUZZ.bak -w wordlist.txt -fs 0-10239 -ms 10240-102400

# Descubre endpoints que tardan más de 500 ms en responder (indicativo de lógica pesada, consultas a BD, o posible inyección de tiempo).
❯ ffuf -u http://example.com/FUZZ -w wordlist.txt -mt >500
c
# Muestra solo solicitudes exitosas y redireccionamientos:
❯ wenum -w wordlist.txt --sc 200,301,302 -u https://example.com/FUZZ

# Oculta respuestas con códigos de error comunes:
❯ wenum -w wordlist.txt --hc 404,400,500 -u https://example.com/FUZZ

# Muestra solo respuestas cortas (5-10 palabras) — útil para detectar mensajes de error breves y distintivos:
❯ wenum -w wordlist.txt --sw 5-10 -u https://example.com/FUZZ

# Oculta archivos grandes para centrarse en respuestas más pequeñas:
❯ wenum -w wordlist.txt --hs 10000 -u https://example.com/FUZZ

# Filtra las respuestas que contengan información específica (regex):
❯ wenum -w wordlist.txt --sr "admin\|password" -u https://example.com/FUZZ
c
❯ feroxbuster --url http://example.com -w wordlist.txt -s 200 -S 10240 -X "error"
c
❯ ffuf -u http://172.16.68.10/post.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "y=FUZZ" -w /usr/share/seclists/Discovery/Web-Content/common.txt -v

Mostrar absolutamente todo, sin filtrar por código de estado (útil para una primera pasada exploratoria antes de decidir qué filtro aplicar):

c
❯ ffuf -u http://172.16.68.10/post.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "y=FUZZ" -w /usr/share/seclists/Discovery/Web-Content/common.txt -v -mc all

Control de velocidad y comportamiento de las peticiones

Al fuzzear un objetivo real (no un laboratorio), es importante controlar la velocidad para no saturar el servicio, evitar activar un WAF/rate-limiter, o simplemente respetar lo acordado en el alcance del pentest.

Limitar la tasa de peticiones por segundo:

c
❯ ffuf -u http://172.16.68.10/FUZZ -w wordlist.txt -rate 50

Agregar un delay entre peticiones (fijo o aleatorio dentro de un rango, para un patrón de tráfico menos predecible/detectable):

c
❯ ffuf -u http://172.16.68.10/FUZZ -w wordlist.txt -p 0.5
❯ ffuf -u http://172.16.68.10/FUZZ -w wordlist.txt -p 0.1-0.5
  • -p : delay en segundos entre cada petición (un solo valor = fijo; rango min-max = aleatorio dentro de ese intervalo).

Reducir el número de hilos concurrentes (comportamiento más "silencioso" que -t alto):

c
❯ ffuf -u http://172.16.68.10/FUZZ -w wordlist.txt -t 5

Definir un timeout por petición, para no colgar el escaneo completo si el servidor tarda en responder a ciertas rutas:

c
❯ ffuf -u http://172.16.68.10/FUZZ -w wordlist.txt -timeout 5

Reintentar automáticamente ante errores de conexión (útil en redes inestables o servidores con rate-limiting agresivo que a veces cierra conexiones):

c
❯ ffuf -u http://172.16.68.10/FUZZ -w wordlist.txt -maxtime-job 60 -sa

Dirsearch — Enumeración recursiva de directorios

dirsearch es una alternativa a ffuf/gobuster especializada en descubrimiento de contenido web, con recursión y detección de extensiones incorporadas por defecto.

Instalación:

c
❯ git clone https://github.com/maurosoria/dirsearch.git && cd dirsearch
❯ pip3 install -r requirements.txt

Escaneo básico:

c
❯ python3 dirsearch.py -u http://172.16.68.10

Escaneo recursivo (equivalente a -recursion de ffuf, pero activado con un simple flag):

c
❯ python3 dirsearch.py -u http://172.16.68.10 -r

Especificar extensiones y wordlist propia:

c
❯ python3 dirsearch.py -u http://172.16.68.10 -e php,html,js,bak -w /usr/share/seclists/Discovery/Web-Content/raft-medium-files.txt

Controlar concurrencia y delay entre peticiones (mismo objetivo que con ffuf: no saturar el objetivo):

c
❯ python3 dirsearch.py -u http://172.16.68.10 -t 10 --delay=0.2
  • -t : número de hilos concurrentes.
  • --delay : segundos de espera entre cada petición.

Excluir códigos de estado específicos (equivalente a -fc en ffuf):

c
❯ python3 dirsearch.py -u http://172.16.68.10 -x 404,400,500

Guardar resultados en distintos formatos para procesarlos después:

c
❯ python3 dirsearch.py -u http://172.16.68.10 -r --format=json -o resultados.json

Crawling (rastreo de la aplicación)

A diferencia del fuzzing (que prueba palabras de un diccionario contra rutas que podrían existir), el crawling recorre la aplicación siguiendo enlaces, formularios y recursos reales ya presentes en el HTML/JS, para mapear su estructura real.

Crawling básico con ffuf (modo experimental, sigue enlaces relativos encontrados en las respuestas):

c
❯ ffuf -u http://172.16.68.10/FUZZ -w wordlist.txt -recursion -recursion-depth 3

Usando hakrawler para extraer URLs, endpoints y parámetros directamente del HTML/JS de una aplicación:

c
❯ echo "http://172.16.68.10" | hakrawler -depth 3 -plain

Usando gospider, que combina crawling con extracción de enlaces desde JS, sitemaps y robots.txt:

c
❯ gospider -s "http://172.16.68.10" -o output/ -c 10 -d 3
  • -c : concurrencia (número de crawlers en paralelo).
  • -d : profundidad máxima de rastreo.

Combinar crawling con fuzzing: usar la salida de un crawler para alimentar diccionarios dinámicos, descubriendo parámetros y rutas reales de la aplicación antes de fuzzear con un diccionario genérico — esto suele dar mejores resultados que un diccionario estático puro, especialmente en aplicaciones con rutas no convencionales.

Fuzzing de APIs

c
❯ git clone https://github.com/PandaSt0rm/webfuzz_api.git
❯ cd webfuzz_api
❯ pip3 install -r requirements.txt