Descubrimiento de contenido
La Web enlaza documentos, pero nunca ha exigido que todos los recursos estén enlazados. Un servidor entrega una respuesta para la URL solicitada aunque ningún HTML público apunte hacia ella. En los sitios estáticos, esa diferencia dejaba fuera del grafo páginas, directorios, copias y paneles cuyo nombre todavía podía adivinarse. En las aplicaciones actuales ocurre lo mismo detrás de routers, controladores, APIs, buckets y reglas de proxy. La implementación ha cambiado. La pregunta permanece.
El crawling recorre el grafo que la aplicación publica. El descubrimiento de contenido explora nombres que podrían existir fuera de ese grafo. Para ello coloca un marcador en una posición de la petición, lo sustituye por entradas de una wordlist y compara las respuestas. OWASP utiliza el término forced browsing para la interacción directa con recursos no referenciados que pueden quedar accesibles por conocimiento o predicción de su URL.
La técnica parece una búsqueda de códigos 200 hasta que la aplicación responde 200 para
cualquier ruta, devuelve 403 de forma general, redirige todo al login o refleja el nombre
solicitado dentro de una plantilla. A partir de ahí, el problema real ya no es enviar muchas
peticiones. Es construir un modelo de la respuesta negativa y demostrar por qué un candidato
se aparta de él.
También hay un límite conceptual. Una wordlist solo encuentra nombres que contiene o que la herramienta deriva. Un resultado vacío puede significar que no existe contenido, que los nombres no eran predecibles, que se consultó el host virtual incorrecto o que un filtro eliminó las respuestas válidas. La ejecución debe conservar suficiente contexto para distinguir esas explicaciones.
Convertir una búsqueda en un experimento
Sección titulada «Convertir una búsqueda en un experimento»Antes de lanzar la herramienta, fija:
- origen completo, incluido esquema, hostname y puerto
- ruta base y posición exacta del marcador
- sesión, rol, cookies y cabeceras
- wordlist y transformaciones aplicadas
- extensiones relevantes para la tecnología observada
- códigos, tamaños, palabras, líneas o patrones que distinguen resultados
- concurrencia, rate, timeout, duración máxima y reintentos
- formato de output y directorio de evidencia
Una lista de rutas sobre / no responde a la misma pregunta que una lista de nombres bajo
/api/v2/. Tampoco debe reutilizarse automáticamente el mismo filtro entre hosts virtuales,
roles o idiomas. Cada cambio de autoridad, ruta base, identidad o posición del marcador
crea un experimento distinto.
Aprender primero cómo responde la ausencia
Sección titulada «Aprender primero cómo responde la ausencia»Solicita varias rutas aleatorias que no existan y compara código, longitud, palabras, líneas, título, Location y fragmentos estables del cuerpo:
for path in not-found-a7f31 not-found-b9c42 not-found-c2e63; do curl --silent \ --show-error \ --dump-header "baseline-$path.headers" \ --output "baseline-$path.body" \ "BASE_URL/$path"done--silent oculta la barra de progreso. --show-error conserva los errores. Cada respuesta se guarda por separado para que la comparación no dependa de lo que muestra una herramienta.
Comprueba también una ruta conocida y, si existe una sesión, repite ambas muestras con el estado anónimo y autenticado. Las diferencias pueden proceder de:
- página de error fija
- token, timestamp o identificador reflejado
- redirección común al login
- CDN o WAF que alterna plantillas
- idioma o geolocalización
- rate limiting
- balanceadores con backends distintos
Un solo tamaño no define una respuesta de referencia. Si las respuestas negativas varían, combina varios atributos o filtra un patrón estable. El objetivo de esta fase es poder explicar qué descarta el filtro antes de pedir a una herramienta que lo aplique miles de veces.
Elegir nombres que tengan una razón para existir
Sección titulada «Elegir nombres que tengan una razón para existir»La wordlist debe corresponder a la pregunta y a la aplicación. Una lista corta de nombres web comunes ofrece una primera cobertura. La tecnología, el idioma, los nombres observados en HTML, JavaScript, sitemaps, repositorios y archivos históricos permiten crear una lista contextual.
Mantén separados:
- nombres de directorio
- nombres de archivo
- extensiones
- rutas históricas confirmadas
- palabras derivadas de la organización
- parámetros y valores
Registra la ruta, versión o hash de la lista. Una actualización de SecLists o de una lista propia cambia el conjunto de peticiones y debe poder explicar diferencias entre ejecuciones.
El número de entradas multiplicado por extensiones, niveles recursivos, hosts e identidades determina las peticiones. Una lista de 50 000 palabras con cinco extensiones sobre diez hosts ya puede producir millones de solicitudes antes de la recursión. Una lista contextual pequeña puede ofrecer mejor cobertura útil si procede de nombres observados en el propio objetivo.
Primera pasada con ffuf
Sección titulada «Primera pasada con ffuf»En ffuf, FUZZ indica dónde insertar cada entrada. -w selecciona la wordlist y -u define la URL. -mc all conserva todos los códigos antes de filtrar. -rate limita las peticiones por segundo. -t controla el número máximo de tareas concurrentes. -timeout limita la espera de cada petición. -maxtime pone un techo a toda la ejecución. -of json y -o guardan un resultado estructurado:
ffuf -w WORDLIST \ -u 'BASE_URL/FUZZ' \ -mc all \ -rate 20 \ -t 10 \ -timeout 10 \ -maxtime 900 \ -of json \ -o evidence/ffuf-root.jsonEmpieza con un rate que permita observar el comportamiento estable del servicio. Si aparecen 429, timeouts crecientes, cierres o cambios masivos de plantilla, detén la ejecución y conserva el punto en el que comenzó la desviación. Aumentar hilos no recupera fidelidad frente a un limitador.
-ac intenta calibrar filtros automáticamente. Puede acelerar una primera revisión, pero un filtro calculado también puede ocultar un recurso válido. Conserva una ejecución pequeña sin autocalibración o revisa qué atributos excluyó antes de aceptar el resultado.
Hilos, frecuencia y pausa resuelven problemas distintos. -t 10 permite hasta diez peticiones en vuelo. -rate 20 impide superar veinte peticiones por segundo, aunque haya hilos libres. -p 0.1-0.4 añade una pausa aleatoria de entre 100 y 400 milisegundos entre peticiones. La pausa reduce sincronía y ráfagas, pero no garantiza por sí sola un máximo por segundo.
Los errores también necesitan una política. -sa detiene la ejecución ante cualquier condición que ffuf considera crítica. Incluye la parada por errores espurios de -se y cuando más del 95 % de las respuestas son 403, que corresponde a -sf. Conviene activarlo en una ejecución no atendida para impedir que una caída del backend convierta el resto de la wordlist en miles de falsos resultados:
ffuf -w WORDLIST \ -u 'BASE_URL/FUZZ' \ -mc all \ -t 10 \ -rate 20 \ -p 0.1-0.4 \ -timeout 10 \ -maxtime 900 \ -sa \ -audit-log evidence/ffuf-root.audit \ -json > evidence/ffuf-root.jsonl-json escribe un objeto JSON por línea en stdout. La redirección lo conserva como JSONL sin confundirlo con -o, que utiliza el formato seleccionado mediante -of. El formato por líneas permite procesar resultados parciales aunque la ejecución termine antes de completar la lista. -audit-log conserva peticiones, respuestas y configuración en el formato propio de auditoría de ffuf 2.2.1. Puede contener cookies, tokens y cuerpos. Debe almacenarse con el mismo control que el resto de la evidencia sensible.
Aplicar filtros que puedan explicarse
Sección titulada «Aplicar filtros que puedan explicarse»Los matchers deciden qué respuestas entran como candidatas. Los filtros eliminan respuestas ya aceptadas. Las parejas principales son:
| Atributo | Matcher | Filtro | Uso habitual |
|---|---|---|---|
| Código | -mc |
-fc |
separar estados de una respuesta negativa estable |
| Tamaño | -ms |
-fs |
excluir una plantilla con longitud fija |
| Palabras | -mw |
-fw |
manejar contenido reflejado con tamaño variable |
| Líneas | -ml |
-fl |
diferenciar estructuras de texto estables |
| Patrón | -mr |
-fr |
buscar o excluir una cadena o expresión regular |
| Tiempo | -mt |
-ft |
investigar desviaciones temporales, no existencia |
En ffuf 2.2.1, el filtro regular concatena primero las cabeceras de respuesta y después el cuerpo. Por tanto, -fr '^missing:' ancla la expresión al principio de las cabeceras y no al principio del cuerpo. Si la señal pertenece al cuerpo, usa un patrón suficientemente específico sin ese ancla y confirma el filtro con un caso positivo y otro negativo.
Si la respuesta negativa tiene 1 248 bytes, -fs 1248 la excluye:
ffuf -w WORDLIST \ -u 'BASE_URL/FUZZ' \ -mc all \ -fs 1248 \ -rate 20 \ -of json \ -o evidence/ffuf-filtered.jsonAntes de confiar en el filtro, vuelve a solicitar manualmente una muestra descartada y otra aceptada. Un 403 con tamaño distinto puede revelar una ruta protegida. Un 200 con el mismo tamaño que el error puede ser un recurso real pequeño. El filtro reduce candidatos, no demuestra inexistencia.
Extensiones y recursión
Sección titulada «Extensiones y recursión»-e añade extensiones a cada entrada. Deben proceder del fingerprint o de recursos ya observados:
ffuf -w WORDLIST \ -u 'BASE_URL/FUZZ' \ -e .php,.txt,.bak \ -mc all \ -rate 15 \ -of json \ -o evidence/ffuf-extensions.jsonCada extensión multiplica solicitudes. Una copia .bak o .old puede ser relevante, pero probar decenas de sufijos sin evidencia empeora el tiempo, el volumen y la interpretación.
La recursión crea nuevas tareas cuando aparece un directorio. En ffuf, -recursion requiere que la URL termine en FUZZ. -recursion-depth limita niveles y -maxtime-job limita cada rama:
ffuf -w WORDLIST \ -u 'BASE_URL/FUZZ' \ -recursion \ -recursion-depth 2 \ -maxtime-job 120 \ -rate 15 \ -of json \ -o evidence/ffuf-recursive.jsonRevisa primero los directorios de la pasada inicial. La recursión indiscriminada amplifica falsos positivos de soft 404, redirecciones y calendarios de URLs.
Controlar Gobuster sin depender de valores predeterminados
Sección titulada «Controlar Gobuster sin depender de valores predeterminados»Gobuster 3.8.2 ofrece un modo dir sencillo y predecible. -u fija la URL, -w la wordlist, -x las extensiones, -l muestra la longitud y -o conserva los resultados. El ejemplo mínimo sirve para comprobar la URL, el diccionario y la respuesta de referencia:
gobuster dir \ -u BASE_URL/ \ -w WORDLIST \ -x php,txt,bak \ -l \ -o evidence/gobuster-root.txtEn una ejecución más larga, -t controla hilos concurrentes. --delay hace que cada hilo espere el intervalo indicado entre peticiones. --timeout limita cada espera HTTP. --retry habilita reintentos ante timeout y --retry-attempts fija cuántos. Un reintento repite tráfico y puede duplicar una acción si la ruta no es idempotente, por lo que en descubrimiento se mantiene GET y se registra el número efectivo.
--headers puede repetirse para reproducir cabeceras. --cookies fija la cabecera Cookie completa. --proxy admite HTTP(S) o SOCKS5. --exclude-length descarta uno o varios tamaños, incluidos rangos. El filtro se deriva de respuestas negativas observadas, no del primer tamaño que aparece:
gobuster dir \ --url BASE_URL/ \ --wordlist WORDLIST \ --threads 12 \ --delay 150ms \ --timeout 10s \ --retry \ --retry-attempts 1 \ --headers 'Accept: text/html' \ --cookies 'SESSION=COOKIE_VALUE' \ --exclude-length 1248 \ --output evidence/gobuster-authenticated.txtEl perfil produce como máximo doce peticiones simultáneas y cada hilo espera 150 milisegundos después de su petición. No equivale a un límite global exacto. Si la respuesta conocida comienza a devolver 429, aumenta --delay, reduce --threads y conserva una muestra manual antes de reanudar.
--wordlist-offset permite continuar desde una posición conocida de la wordlist. No reconstruye por sí mismo los resultados anteriores ni el estado del servidor. Registra hash de la lista, offset y filtros. --debug muestra decisiones internas y detalles de petición útiles cuando el resultado difiere de cURL. Puede revelar cabeceras y cookies:
gobuster dir \ --url BASE_URL/ \ --wordlist WORDLIST \ --wordlist-offset OFFSET \ --threads 4 \ --delay 500ms \ --proxy http://127.0.0.1:8080 \ --debug \ --output evidence/gobuster-resumed.txtLa escucha del proxy debe estar preparada antes de ejecutar el comando. Si la herramienta falla durante el precheck, averigua qué respuesta lo provocó. --force continúa pese a ese fallo, pero también puede convertir un soft 404 no resuelto en resultados inútiles. Solo se usa después de reproducir el precheck manualmente.
Recorrer con Feroxbuster
Sección titulada «Recorrer con Feroxbuster»Feroxbuster 2.13.1 convierte los directorios confirmados en nuevas raíces. --threads limita las peticiones concurrentes por directorio y --scan-limit limita cuántos directorios se procesan al mismo tiempo. La multiplicación de ambos valores define la concurrencia potencial. --rate-limit añade un máximo global por segundo y --timeout limita cada petición. --time-limit pone un techo a toda la ejecución.
feroxbuster \ --url BASE_URL/ \ --wordlist WORDLIST \ --depth 2 \ --threads 8 \ --scan-limit 2 \ --rate-limit 15 \ --timeout 10 \ --time-limit 15m \ --json \ --output evidence/feroxbuster.json--filter-size elimina tamaños de referencia. Puede repetirse para varios valores. --replay-proxy envía al proxy únicamente las peticiones que Feroxbuster considera resultados, lo que evita capturar toda la wordlist. --debug-log conserva diagnóstico interno y --resume-from continúa desde el estado guardado por una ejecución interrumpida:
feroxbuster \ --url BASE_URL/ \ --wordlist WORDLIST \ --threads 8 \ --scan-limit 1 \ --rate-limit 10 \ --timeout 10 \ --filter-size 1248 \ --replay-proxy http://127.0.0.1:8080 \ --debug-log evidence/feroxbuster.debug \ --json \ --output evidence/feroxbuster.jsonLa reanudación depende del fichero generado por la misma versión y no demuestra que la aplicación conserve su estado. Antes de usar --resume-from STATE_FILE, repite una ruta conocida y una ausente. Si las respuestas han cambiado, comienza una ejecución nueva o documenta el cambio de referencia.
No ejecutes tres herramientas con la misma lista y los mismos valores predeterminados esperando tres evidencias independientes. Si discrepan, reduce el caso a una URL y compara petición, redirecciones, normalización, versión HTTP, proxy, TLS, filtro y reintentos.
Reproducir una petición compleja con ffuf
Sección titulada «Reproducir una petición compleja con ffuf»Cuando la aplicación necesita cabeceras, cookies o un cuerpo que resulta incómodo reconstruir con flags, -request REQUEST_FILE lee una petición HTTP sin procesar. -request-proto fija el esquema porque el fichero no contiene esa información. El marcador FUZZ puede aparecer en el destino, las cabeceras o el cuerpo. Revisa el fichero y elimina secretos que no participen en la prueba.
-replay-proxy reenvía al proxy únicamente los resultados aceptados. -x envía toda la ejecución por un proxy. Son funciones distintas. La primera facilita validación selectiva. La segunda permite observar cada petición y reduce el rendimiento al de la cadena de proxy:
ffuf -request request.raw \ -request-proto https \ -w WORDLIST \ -t 8 \ -rate 10 \ -timeout 10 \ -replay-proxy http://127.0.0.1:8080 \ -of json \ -o evidence/ffuf-raw-request.json-config FILE carga valores predeterminados desde un fichero. Registra ese fichero junto al comando, porque una opción de línea de comandos puede estar combinándose con filtros, cabeceras o timeouts invisibles en el historial. Empieza las investigaciones nuevas sin configuración global y añade un fichero revisado cuando el perfil de ejecución sea estable.
Descubrir parámetros y otras posiciones
Sección titulada «Descubrir parámetros y otras posiciones»El marcador también puede ocupar el nombre de un parámetro, una cabecera o parte del cuerpo. Esas pruebas deben separarse del descubrimiento de rutas porque cambian el caso de prueba y la forma de interpretar la respuesta:
ffuf -w PARAMETER_LIST \ -u 'BASE_URL/search?FUZZ=test' \ -mc all \ -rate 10 \ -of json \ -o evidence/ffuf-parameters.jsonUn cambio de tamaño puede deberse a que el parámetro se refleja, no a que la aplicación lo procese. Confirma el comportamiento modificando un solo parámetro y observando una diferencia semántica, como una validación nueva, un cambio de resultados o un error de tipo.
Los valores, cuerpos JSON, cabeceras y métodos pertenecen después a pruebas específicas de la aplicación. No deben mezclarse en una pasada genérica que impida reconstruir qué entrada generó cada efecto.
Validar candidatos
Sección titulada «Validar candidatos»Para cada resultado relevante:
- reproduce la URL con la interacción manual
- conserva primera respuesta y cadena de redirecciones
- compara sesión anónima y autenticada cuando corresponda
- prueba la variante con y sin barra final
- registra tipo de contenido, título, tamaño, hash y comportamiento
- añade enlaces, scripts, parámetros y nombres nuevos al inventario
- clasifica el candidato como confirmado, falso positivo, bloqueado o pendiente
Los códigos requieren contexto. 301 puede canonicalizar una ruta. 401 confirma un recurso que exige autenticación. 403 puede proceder del recurso, del servidor frontal o de una regla general. 404 puede ocultar contenido por diseño. 500 puede indicar una ruta real con una dependencia ausente o una petición que activó una excepción.
Falsos negativos y troubleshooting
Sección titulada «Falsos negativos y troubleshooting»Cuando no aparecen resultados, revisa:
- hostname, SNI, puerto y ruta base
- slash final y normalización de dobles barras
- cabeceras, cookies, proxy y rol
- extensiones y mayúsculas
- respuesta de referencia obtenida con varias rutas aleatorias
429,503, timeouts, cierres y bloqueos temporales- filtros automáticos o manuales
- codificación del marcador
- redirecciones no seguidas
- diferencias entre HTTP/1.1 y HTTP/2
- wordlist y posición del marcador
Repite una petición conocida antes, durante y después de la ejecución. Si su respuesta cambia, el problema ya no es solo la wordlist. Puede haber rate limiting, un backend inestable o una política adaptativa.
Telemetría, estado y cierre
Sección titulada «Telemetría, estado y cierre»Esta técnica genera una petición por candidato y por cada variante. Las extensiones, la recursión, las redirecciones, los reintentos y las herramientas que repiten resultados aumentan ese número. Los componentes frontales pueden registrar el destino de la petición, la autoridad, la IP de origen, el agente, la sesión, el código, los bytes y el tiempo. Una aplicación también puede crear sesiones, entradas de caché o registros de auditoría.
Conserva el comando, las versiones, la wordlist, los filtros, la tasa, el intervalo de ejecución y el output original. Retira cookies y ficheros con secretos al cerrar. Si una petición creó un objeto o disparó una acción con estado, documenta y verifica su limpieza mediante la misma aplicación.