feat(recon): modo CDP en fingerprint_web_stack para detectar SPAs
Añade fetch_http_fingerprint_cdp_py_browser (domain browser): recoge el HTML renderizado tras ejecutar JavaScript usando un Chrome remoto via CDP, componiendo cdp_open_url_and_wait + cdp_eval. Devuelve la misma estructura que el fetch estático para que detect_web_tech lo consuma sin cambios. Integra use_cdp en el pipeline fingerprint_web_stack (v1.1.0): combina los headers reales del fetch estático con el HTML post-JS del CDP. Detecta frameworks de SPA (React/Vue/Angular/Next) que el fetch estático no ve porque montan el DOM en runtime. Si no hay Chrome en cdp_port, degrada al fetch estático con un warning (no rompe). cdp_port=9333 (Chrome aislado) recomendado para terceros, 9222 diario. Verificado en vivo (Chrome 9333): sobre una SPA cuyo marcador de framework solo aparece tras ejecutar JS, el estático detecta solo nginx; con use_cdp=True detecta además Next.js, React y Node.js. Tests: 48 verdes (error path sin Chrome + happy path mockeado + degradación). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,15 +3,16 @@ name: fingerprint_web_stack
|
||||
kind: pipeline
|
||||
lang: py
|
||||
domain: pipelines
|
||||
version: "1.0.0"
|
||||
version: "1.1.0"
|
||||
purity: impure
|
||||
signature: "def fingerprint_web_stack(url: str, timeout_s: float = 15.0, verify_tls: bool = True, max_html_bytes: int = 500_000, save: bool = True) -> dict"
|
||||
description: "One-shot que detecta la tecnologia web (stack tecnologico estilo Wappalyzer) de una URL: hace el fetch HTTP de las senales (fetch_http_fingerprint) y matchea las firmas (detect_web_tech), devolviendo las tecnologias detectadas — servidor, lenguaje, CMS, framework web, frameworks JS, librerias, analytics, CDN, e-commerce, WAF — con categoria, version y confidence. Reemplaza el patron fetch_http_fingerprint -> detect_web_tech por una sola llamada. El equivalente registry de Wappalyzer / whatweb / un fingerprint de stack de una url. Opcionalmente archiva la evidencia (tabla TECNOLOGIA/CATEGORIA/VERSION/CONFIDENCE) en OSINT. Util para reconocimiento web, auditoria de superficie y averiguar que CMS framework servidor usa un sitio."
|
||||
tags: [recon, web-recon, pipelines, cybersecurity, fingerprint, wappalyzer, web-tech, sink]
|
||||
signature: "def fingerprint_web_stack(url: str, timeout_s: float = 15.0, verify_tls: bool = True, max_html_bytes: int = 500_000, save: bool = True, use_cdp: bool = False, cdp_port: int = 9222, wait_render_s: float = 2.0) -> dict"
|
||||
description: "One-shot que detecta la tecnologia web (stack tecnologico estilo Wappalyzer) de una URL: hace el fetch HTTP de las senales (fetch_http_fingerprint) y matchea las firmas (detect_web_tech), devolviendo las tecnologias detectadas — servidor, lenguaje, CMS, framework web, frameworks JS, librerias, analytics, CDN, e-commerce, WAF — con categoria, version y confidence. Reemplaza el patron fetch_http_fingerprint -> detect_web_tech por una sola llamada. El equivalente registry de Wappalyzer / whatweb / un fingerprint de stack de una url. Con use_cdp=True ademas analiza el HTML RENDERIZADO tras ejecutar JavaScript (fetch_http_fingerprint_cdp via Chrome remoto) para detectar SPAs (React/Vue/Angular/Next) que el fetch estatico no ve; si no hay Chrome degrada a estatico con un warning. Opcionalmente archiva la evidencia (tabla TECNOLOGIA/CATEGORIA/VERSION/CONFIDENCE) en OSINT. Util para reconocimiento web, auditoria de superficie y averiguar que CMS framework servidor usa un sitio, incluidas single-page applications."
|
||||
tags: [recon, web-recon, pipelines, cybersecurity, fingerprint, wappalyzer, web-tech, sink, cdp, spa, render]
|
||||
uses_functions:
|
||||
- fetch_http_fingerprint_py_cybersecurity
|
||||
- detect_web_tech_py_cybersecurity
|
||||
- save_scan_to_osint_py_cybersecurity
|
||||
- fetch_http_fingerprint_cdp_py_browser
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
@@ -28,9 +29,15 @@ params:
|
||||
desc: "Corta el HTML leido a este tamano para no descargar megas. Default 500_000 (500 KB). Se pasa a fetch_http_fingerprint."
|
||||
- name: save
|
||||
desc: "Si True (default) archiva la evidencia en OSINT via save_scan_to_osint con scan_type='web_tech' (target = host de la URL); si False solo ejecuta el fetch + matching y no toca el vault ni el service osint_db. Politica recon: todo scan se archiva. Si el sink falla, el resultado degrada sin romper (saved.status='error')."
|
||||
output: "dict con status ('ok'|'error'), url, final_url (tras redirects), status_code (int), server (cabecera Server o ''), title (titulo de la pagina o ''), technologies (lista de dicts con name, category, version, confidence, evidence — tal cual de detect_web_tech), by_category (dict categoria -> lista de nombres), count (int), saved (dict de save_scan_to_osint con note_path/registered/scan_id, o None si save=False) y raw (tabla legible TECNOLOGIA/CATEGORIA/VERSION/CONFIDENCE con cabecera de url/status/server/title). Si el fetch HTTP falla (host no resuelve, conexion rechazada, timeout) -> {status:error, stage:fetch, url:..., fetch:<dict>}. Nunca lanza."
|
||||
- name: use_cdp
|
||||
desc: "Si True, ademas del fetch estatico hace un fetch via Chrome DevTools Protocol (fetch_http_fingerprint_cdp) para analizar el HTML RENDERIZADO tras ejecutar JavaScript y detectar SPAs (React/Vue/Angular/Next) que el HTML inicial vacio no revela. Requiere un Chrome con remote debugging en cdp_port. Si el CDP no esta disponible, DEGRADA al HTML estatico con un warning (no falla). Default False (comportamiento estatico clasico, sin regresion)."
|
||||
- name: cdp_port
|
||||
desc: "Puerto de remote debugging del Chrome a usar cuando use_cdp=True. Default 9222 (navegador diario, activado global — mezcla tu sesion personal). Para recon de terceros sin contaminar tu sesion, usar 9333 (Chrome aislado del browser_mcp)."
|
||||
- name: wait_render_s
|
||||
desc: "Segundos de espera tras el load event para que la SPA pinte el DOM (solo aplica con use_cdp=True). Default 2.0. Subir (4.0-6.0) para SPAs lentas con mucho data-fetching; un valor corto puede dejar el HTML incompleto."
|
||||
output: "dict con status ('ok'|'error'), url, final_url (tras redirects), status_code (int), server (cabecera Server o ''), title (titulo de la pagina o ''), technologies (lista de dicts con name, category, version, confidence, evidence — tal cual de detect_web_tech), by_category (dict categoria -> lista de nombres), count (int), html_source ('static'|'cdp' — fuente del HTML analizado), rendered (bool, True si html_source=='cdp'), warnings (lista de avisos, p.ej. degradacion CDP->estatico; vacia si no hubo), saved (dict de save_scan_to_osint con note_path/registered/scan_id, o None si save=False) y raw (tabla legible TECNOLOGIA/CATEGORIA/VERSION/CONFIDENCE con cabecera de url/status/server/title/html_source). Si el fetch HTTP estatico falla y use_cdp=False (o ambos fallan) -> {status:error, stage:fetch, url:..., fetch:<dict>}. Nunca lanza."
|
||||
tested: true
|
||||
tests: ["test_golden_fingerprint_servidor_local_wordpress_nginx", "test_save_false_no_archiva_osint", "test_fetch_fallido_propaga_error_sin_red"]
|
||||
tests: ["test_golden_fingerprint_servidor_local_wordpress_nginx", "test_save_false_no_archiva_osint", "test_fetch_fallido_propaga_error_sin_red", "test_use_cdp_sin_chrome_degrada_a_estatico"]
|
||||
test_file_path: "python/functions/pipelines/fingerprint_web_stack_test.py"
|
||||
file_path: "python/functions/pipelines/fingerprint_web_stack.py"
|
||||
---
|
||||
@@ -56,6 +63,20 @@ print(r["by_category"]) # {"cms": ["WordPress"], "web-server": ["nginx"], ...}
|
||||
```python
|
||||
from pipelines.fingerprint_web_stack import fingerprint_web_stack
|
||||
|
||||
# Modo CDP: analiza el HTML RENDERIZADO tras el JS (detecta SPAs React/Vue/Angular).
|
||||
# Requiere Chrome con remote debugging en cdp_port (9222 diario / 9333 aislado).
|
||||
r = fingerprint_web_stack("https://react.dev/", use_cdp=True, cdp_port=9222, save=False)
|
||||
print(r["status"]) # "ok"
|
||||
print(r["html_source"]) # "cdp" si habia Chrome; "static" (con warning) si no
|
||||
print(r["rendered"]) # True si se uso el HTML renderizado
|
||||
print(r["warnings"]) # [] si CDP ok; ["cdp no disponible: ...; usando fetch estatico"] si degrado
|
||||
for t in r["technologies"]:
|
||||
print(t["name"], t["category"]) # React javascript-framework, etc.
|
||||
```
|
||||
|
||||
```python
|
||||
from pipelines.fingerprint_web_stack import fingerprint_web_stack
|
||||
|
||||
# Con archivado en OSINT (default): deja una nota en el vault + POST al osint_db.
|
||||
r = fingerprint_web_stack("https://midominio.example")
|
||||
print(r["saved"]["note_path"]) # dominios/midominio.example/recon/web_tech-....md
|
||||
@@ -66,6 +87,8 @@ print(r["saved"]["note_path"]) # dominios/midominio.example/recon/web_tech-....
|
||||
./fn run fingerprint_web_stack https://example.com
|
||||
# Flags: --no-save (no archiva OSINT), --no-verify-tls (cert self-signed, inseguro).
|
||||
./fn run fingerprint_web_stack https://example.com --no-save
|
||||
# Modo CDP (HTML renderizado tras JS): --cdp [--cdp-port 9333].
|
||||
./fn run fingerprint_web_stack https://react.dev/ --cdp --no-save
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
@@ -80,11 +103,26 @@ enriquecer una investigacion OSINT con el stack de un host.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **Fetch estatico: NO ejecuta JavaScript.** Solo ve el HTML inicial que devuelve
|
||||
el servidor. Las SPAs que montan el framework (React/Vue/Angular/Svelte) en
|
||||
runtime suelen servir un HTML casi vacio, asi que esos frameworks pueden NO
|
||||
detectarse. Para sitios JS-pesados, un fingerprint con navegador real (CDP)
|
||||
veria mas; este pipeline es la version sin navegador.
|
||||
- **Fetch estatico (use_cdp=False): NO ejecuta JavaScript.** Solo ve el HTML
|
||||
inicial que devuelve el servidor. Las SPAs que montan el framework
|
||||
(React/Vue/Angular/Svelte) en runtime suelen servir un HTML casi vacio, asi que
|
||||
esos frameworks pueden NO detectarse. Para sitios JS-pesados usa `use_cdp=True`
|
||||
(analiza el HTML renderizado tras el JS via Chrome remoto).
|
||||
- **`use_cdp=True` requiere Chrome con remote debugging** escuchando en `cdp_port`:
|
||||
9222 (navegador diario, activado global) o 9333 (Chrome aislado del browser_mcp).
|
||||
Si no hay Chrome, el pipeline NO falla: DEGRADA al HTML estatico, marca
|
||||
`html_source="static"` y rellena `warnings` con `"cdp no disponible: ...; usando
|
||||
fetch estatico"`. Comprueba siempre `result["warnings"]` para saber si el CDP se
|
||||
aplico o si caiste al estatico.
|
||||
- **Con `cdp_port=9222` se abre un tab en tu navegador PERSONAL** (mezcla cookies e
|
||||
historial de tu sesion diaria). Para fingerprint de TERCEROS sin contaminar ni
|
||||
filtrar tu sesion, usa `cdp_port=9333` (el Chrome aislado del browser_mcp).
|
||||
- **`wait_render_s` puede ser corto para SPAs lentas**: el load event NO garantiza
|
||||
el DOM pintado. Si el `html` renderizado sale incompleto (faltan frameworks que
|
||||
deberian aparecer), sube `wait_render_s` a 4.0-6.0.
|
||||
- **CDP no expone headers ni status_code**: con `use_cdp=True`, `server`,
|
||||
`status_code` y `headers` siguen viniendo del fetch estatico (que siempre se
|
||||
ejecuta); el CDP solo aporta el `html` renderizado y los nombres de cookie no-httponly.
|
||||
- **La tabla de firmas es un subconjunto de Wappalyzer**, no exhaustiva. Un
|
||||
tecnologia no listada en `detect_web_tech` no aparecera aunque este presente.
|
||||
Para ampliar cobertura, anade entradas a `SIGNATURES` en `detect_web_tech`.
|
||||
@@ -119,3 +157,7 @@ entero ni valores de cookie (las cookies de `fetch_http_fingerprint` ya son solo
|
||||
nombres). El `target` para el archivado OSINT se deriva del host de la URL
|
||||
(`urllib.parse.urlparse(...).hostname`). Nunca lanza excepciones: todo fallo se
|
||||
refleja en la clave `status` del dict devuelto.
|
||||
|
||||
## Capability growth log
|
||||
|
||||
- v1.1.0 (2026-06-14) — anade modo use_cdp: usa fetch_http_fingerprint_cdp para analizar el HTML renderizado tras JS y detectar SPAs (React/Vue/Angular) que el fetch estatico no ve; degrada a estatico si no hay Chrome.
|
||||
|
||||
Reference in New Issue
Block a user