Files
fn_registry/docs/capabilities/recon.md
T
egutierrez 1430039688 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>
2026-06-14 15:31:28 +02:00

196 lines
18 KiB
Markdown

# Capability: recon
Reconocimiento de red para OSINT desde el registry: lookups de registro (WHOIS/RDAP), DNS, sondeo de disponibilidad y ruta (ping/traceroute), escaneo de puertos y servicios, y fingerprint de la tecnologia web de un sitio (estilo Wappalyzer). El escaneo de puertos tiene dos caminos: el wrapper pesado de `nmap` (perfiles, scripts NSE, versiones), y un **camino nativo en Python puro** (`scan_tcp_ports` + `grab_service_banner` + `identify_port_service`, solo stdlib, sin nmap ni sudo) para escaneo rapido y portable. El fingerprint web sigue el mismo patron pura/impura: `fetch_http_fingerprint` recoge las señales (headers, html, cookies) y `detect_web_tech` (pura) matchea firmas para identificar servidor, CMS, frameworks JS, analytics y CDN. La mayoria de funciones son Python impuras, wrappean CLIs del sistema (`whois`, `rdap`, `dig`, `ping`, `traceroute`, `nmap`) o usan sockets/urllib stdlib, y devuelven siempre un dict `{status: ok|error}` sin lanzar excepciones. El grupo cierra el bucle con un **sink comun** que archiva cada escaneo en el ecosistema OSINT (nota Obsidian + registro DuckDB) y pipelines one-shot que escanean y guardan en una sola llamada.
Comparte tag y dominio (`cybersecurity`) con el grupo `osint-passive` (recoleccion no intrusiva desde fuentes publicas), del que reutiliza primitivas. La regla de operacion es la misma del project `osint`: **todo escaneo se archiva en OSINT**.
## Funciones
| ID | Firma | Que hace |
|---|---|---|
| `whois_lookup_py_cybersecurity` | `whois_lookup(target, timeout_s=30) -> dict` | Lookup WHOIS via el CLI `whois`. Captura el `raw` completo y parsea best-effort registrar, registrant_country, creation_date, expiry_date, updated_date, name_servers. Acepta dominio o IP. |
| `rdap_lookup_py_cybersecurity` | `rdap_lookup(target, timeout_s=30) -> dict` | Lookup RDAP (reemplazo JSON moderno de WHOIS) via el CLI openrdap `rdap`. Devuelve `data` (dict JSON), `handle`, `ldhName` y el `raw`. Acepta dominio, IP o ASN (`AS15169`). |
| `dns_records_py_cybersecurity` | `dns_records(domain, record_types=None, timeout_s=20) -> dict` | Registros DNS via `dig +short` (default A, AAAA, MX, NS, SOA, TXT, CNAME). Devuelve `records` (dict por tipo) y `raw` legible por bloque para el vault. |
| `ping_host_py_cybersecurity` | `ping_host(host, count=4, timeout_s=30) -> dict` | Sondeo ICMP via `ping`. Devuelve `loss_pct`, `rtt_avg_ms` (y min/max), `packets_sent`/`recv`, `raw`. Host filtrado = `status:ok` con `loss_pct=100`, no error. |
| `traceroute_host_py_cybersecurity` | `traceroute_host(host, max_hops=30, timeout_s=60) -> dict` | Traza la ruta via `traceroute`. Devuelve `hops` (lista de `{hop, hosts:[{name, ip, rtt_ms}]}`) y `raw`. Hops filtrados (`* * *`) = `hosts: []`. |
| `nmap_scan_py_cybersecurity` | `nmap_scan(target, profile="quick", ports=None, extra_args=None, out_dir=None, timeout_s=1800) -> dict` | Escaneo de puertos/servicios via `nmap` por perfiles (salida XML parseada). Devuelve `open_ports`, `hosts_up`, `xml_path`, `raw`, `elapsed_s`. Funcion estrella del grupo. |
| `scan_tcp_ports_py_cybersecurity` | `scan_tcp_ports(host, ports="common", timeout_s=1.0, workers=100) -> dict` | **Connect-scan TCP nativo (stdlib, sin nmap ni sudo).** Escanea puertos en paralelo con threads y clasifica cada uno en open/closed/filtered. `ports` acepta lista, preset `"common"`, rango `"1-1024"` o CSV. Devuelve `open` (lista de ints), `ip`, `raw`. NO detecta version de servicio. |
| `grab_service_banner_py_cybersecurity` | `grab_service_banner(host, port, timeout_s=3.0, send_probe=True) -> dict` | **Banner grab nativo (stdlib, sin nmap -sV).** Abre socket TCP, lee el banner e identifica el servicio real (ssh, http, ftp, smtp, mysql, redis, pop3, imap, telnet...) extrayendo `product` y `version` best-effort. Dice QUE habla detras de un puerto abierto. TLS/HTTPS no da banner plano. |
| `identify_port_service_py_cybersecurity` | `identify_port_service(port, proto="tcp") -> dict` | **Pure.** Mapea un puerto a su servicio IANA well-known esperado por convencion (`{service, description, known}`) desde una tabla embebida (~120 puertos). No sondea en vivo: dice que se ESPERA, no que hay. |
| `save_scan_to_osint_py_cybersecurity` | `save_scan_to_osint(target, scan_type, raw, summary=None, vault_dir="~/Obsidian/osint", service_url="http://127.0.0.1:8771", tool=None) -> dict` | **Sink OSINT.** Archiva un scan: nota Markdown tipada en el vault (capa critica) + POST a `osint_db` para registro DuckDB (best-effort). Devuelve `note_path`, `registered`, `scan_id`. |
| `recon_osint_py_pipelines` | `recon_osint(target, scan_type="whois", save=True, profile="quick", ...) -> dict` | **Pipeline one-shot.** Ejecuta un scan del tipo pedido y lo archiva en OSINT en una sola llamada (compone la funcion de scan + `save_scan_to_osint`). El camino canonico para recon + archivado. |
| `scan_port_services_py_pipelines` | `scan_port_services(host, ports="common", timeout_s=1.0, workers=100, grab_banners=True, banner_timeout_s=3.0, save=True) -> dict` | **Pipeline one-shot nativo.** Escanea puertos y, por cada abierto, devuelve servicio esperado (IANA) + servicio/version real del banner. Compone `scan_tcp_ports` + `identify_port_service` + `grab_service_banner` (+ sink OSINT). Reemplaza el patron scan→identify→grab sin nmap. |
| `fetch_http_fingerprint_py_cybersecurity` | `fetch_http_fingerprint(url, timeout_s=15.0, verify_tls=True, max_html_bytes=500000, user_agent=None) -> dict` | **Fetch de señales web (stdlib).** GET con UA de navegador, sigue redirects, descomprime gzip. Devuelve `headers` (lowercase), `cookies` (solo NOMBRES, sin valores), `html`, `title`, `server`, `status_code`, `final_url`, `raw`. Capa impura del fingerprint web. |
| `detect_web_tech_py_cybersecurity` | `detect_web_tech(headers, html="", cookies=None, final_url="") -> dict` | **Pure. Detector de tecnologia web estilo Wappalyzer.** Matchea ~50 firmas embebidas (regex) contra headers/html/cookies → `technologies[{name, category, version, confidence, evidence}]`, `by_category`, `count`. Cubre server, lenguaje, CMS, frameworks JS, librerias, analytics, CDN, e-commerce, WAF. |
| `fetch_http_fingerprint_cdp_py_browser` | `fetch_http_fingerprint_cdp(url, *, port=9222, wait_render_s=2.0, timeout_s=30.0, close_tab=True) -> dict` | **Fetch del HTML RENDERIZADO (post-JS) via CDP.** Navega en un Chrome remoto (compone `cdp_open_url_and_wait` + `cdp_eval`), espera el render y devuelve el `html` con el DOM ya montado por JS → detecta SPAs (React/Vue/Angular/Next) que el fetch estatico no ve. Mismo shape que `fetch_http_fingerprint` (headers={}, status_code=None: la red la aporta el estatico). |
| `fingerprint_web_stack_py_pipelines` | `fingerprint_web_stack(url, timeout_s=15.0, verify_tls=True, max_html_bytes=500000, save=True, use_cdp=False, cdp_port=9222, wait_render_s=2.0) -> dict` | **Pipeline one-shot = Wappalyzer del registry.** url → tecnologias detectadas. Compone `fetch_http_fingerprint` + `detect_web_tech` (+ sink OSINT). Con `use_cdp=True` añade `fetch_http_fingerprint_cdp`: headers reales del estatico + HTML renderizado del CDP (detecta SPAs); degrada a estatico con warning si no hay Chrome. El camino canonico para fingerprint web. |
### OSINT pasivo relacionado
Estas funciones llevan tambien el tag `recon` (y `osint-passive`): recoleccion no intrusiva desde fuentes publicas, sin tocar al objetivo. Utiles antes o junto al escaneo de red. Pagina madre completa: `docs/capabilities/osint-passive.md`.
| ID | Firma | Que hace |
|---|---|---|
| `build_search_dorks_py_cybersecurity` | `build_search_dorks(target, tipo="persona", extra_domains=None) -> list` | **Pure.** Genera dorks de buscador (frase exacta, `site:`, `filetype:`, leaks/pastebin) segun el tipo de target. Sin red. |
| `enum_subdomains_crtsh_py_cybersecurity` | `enum_subdomains_crtsh(dominio, timeout_s=20.0) -> list` | Enumera subdominios desde Certificate Transparency (crt.sh). Dedup, ordenado, sin wildcards. |
| `enumerate_username_sites_py_cybersecurity` | `enumerate_username_sites(username, timeout_s=8.0, sites=None) -> list` | Comprueba si un username existe en ~12 sitios publicos (estilo sherlock ligero) por codigo HTTP. |
| `guess_email_formats_py_cybersecurity` | `guess_email_formats(nombre, apellidos, dominio) -> list` | **Pure.** Genera candidatos de email comunes (nombre.apellido, inicial+apellido, ...). Sin red. |
| `enrich_org_passive_py_cybersecurity` | `enrich_org_passive(dominio) -> dict` | Orquestador: perfil pasivo de una organizacion componiendo whois + dns + subdominios crt.sh. |
## Ejemplo canonico end-to-end
**1. One-shot (preferido): escanear y archivar en una llamada.** El pipeline corre el scan y lo guarda en OSINT (nota + registro DuckDB) por ti.
```bash
cd /home/enmanuel/fn_registry
./fn run recon_osint ejemplo.com whois
```
Equivalente desde Python (cuando necesitas el dict de resultado):
```bash
python/.venv/bin/python3 - <<'PYEOF'
import sys
sys.path.insert(0, "python/functions")
from pipelines.recon_osint import recon_osint
res = recon_osint("ejemplo.com", scan_type="whois", save=True)
print(res["status"], res.get("note_path"), res.get("registered"))
PYEOF
```
**2. Manual atomico + sink.** Cuando quieres controlar el scan (perfil, puertos, summary propio) y guardarlo aparte. La funcion de scan se importa, no se reescribe.
```bash
cd /home/enmanuel/fn_registry
python/.venv/bin/python3 - <<'PYEOF'
import sys
sys.path.insert(0, "python/functions")
from cybersecurity import dns_records
from cybersecurity.save_scan_to_osint import save_scan_to_osint
scan = dns_records("ejemplo.com") # 1. escanear
if scan["status"] == "ok":
saved = save_scan_to_osint( # 2. archivar en OSINT
"ejemplo.com",
"dns",
scan["raw"],
summary={"A": scan["records"].get("A"), "MX": scan["records"].get("MX")},
tool="dig",
)
print(saved["note_path"], "registered:", saved["registered"])
PYEOF
```
**3. nmap largo en segundo plano.** Los perfiles pesados tardan de minutos a horas: lanzalos en background con `out_dir` (conserva el XML) y `timeout_s` alto, y archiva al terminar.
```bash
cd /home/enmanuel/fn_registry
# El pipeline one-shot tambien sirve para nmap; lanzar en background por la duracion:
nohup ./fn run recon_osint scanme.nmap.org nmap --profile full-tcp --timeout-s 7200 \
> /tmp/recon-fulltcp.log 2>&1 &
```
> `scanme.nmap.org` es el host oficial de pruebas de nmap (legal escanear). Cualquier otro objetivo de terceros exige autorizacion.
**4. Escaneo nativo de servicios de puertos (sin nmap), one-shot.** Cuando no quieres depender de `nmap`/sudo o buscas un barrido rapido y portable: el pipeline `scan_port_services` escanea los puertos y, por cada abierto, dice el servicio esperado por convencion (IANA) y el servicio/version real leido del banner.
```bash
cd /home/enmanuel/fn_registry
python/.venv/bin/python3 - <<'PYEOF'
import sys
sys.path.insert(0, "python/functions")
from pipelines.scan_port_services import scan_port_services
res = scan_port_services("scanme.nmap.org", ports="common", save=True)
print(res["status"], "abiertos:", res.get("open_ports"))
for s in res.get("services", []):
print(f" {s['port']}: esperado={s['expected_service']} real={s.get('actual_service')} version={s.get('version')}")
PYEOF
```
Las primitivas tambien sirven sueltas: `scan_tcp_ports(host, ports)` para solo el estado de los puertos, `grab_service_banner(host, port)` para identificar un servicio concreto, e `identify_port_service(port)` (pura) para el servicio esperado por convencion.
**5. Fingerprint de tecnologia web (Wappalyzer del registry), one-shot.** Identifica el stack de un sitio — servidor, lenguaje, CMS, frameworks JS, analytics, CDN — desde el HTML + cabeceras + cookies, sin ejecutar JS. El pipeline `fingerprint_web_stack` hace fetch + matching de firmas en una llamada.
```bash
cd /home/enmanuel/fn_registry
python/.venv/bin/python3 - <<'PYEOF'
import sys
sys.path.insert(0, "python/functions")
from pipelines.fingerprint_web_stack import fingerprint_web_stack
res = fingerprint_web_stack("https://example.com", save=True)
print(res["status"], "->", res.get("count"), "tecnologias")
for t in res.get("technologies", []):
print(f" {t['name']} [{t['category']}] v={t['version']!r} ({t['confidence']})")
PYEOF
```
Las dos capas tambien sueltas: `fetch_http_fingerprint(url)` para inspeccionar cabeceras+html+cookies crudos de una URL, y `detect_web_tech(headers, html, cookies)` (pura) para matchear firmas sobre señales ya recogidas (testeable sin red).
**Modo CDP (SPAs): detectar mas eficientemente el HTML renderizado.** Un fetch estatico NO ejecuta JavaScript: una SPA (React/Vue/Angular/Next con HTML inicial casi vacio) monta su DOM en runtime y el estatico la pierde. Con `use_cdp=True` el pipeline usa `fetch_http_fingerprint_cdp` (Chrome remoto via CDP) para analizar el DOM ya renderizado, combinando los headers reales del estatico con el HTML post-JS.
```bash
cd /home/enmanuel/fn_registry
python/.venv/bin/python3 - <<'PYEOF'
import sys
sys.path.insert(0, "python/functions")
from pipelines.fingerprint_web_stack import fingerprint_web_stack
# cdp_port=9333 = Chrome aislado del browser_mcp (recomendado para terceros); 9222 = navegador diario.
res = fingerprint_web_stack("https://una-spa.com", use_cdp=True, cdp_port=9333, save=False)
print(res["html_source"], "->", [t["name"] for t in res["technologies"]])
PYEOF
```
Ganancia verificada en vivo: sobre una SPA cuyo marcador de framework solo aparece tras ejecutar JS, el estatico detecta solo `nginx`; con `use_cdp=True` detecta ademas `Next.js`, `React`, `Node.js`. Si no hay Chrome en `cdp_port`, degrada al fetch estatico con un `warning` (no falla).
## Integracion OSINT
Cada escaneo guardado acaba en **dos sitios**, y por eso `save_scan_to_osint` (y el pipeline `recon_osint`) son el cierre obligatorio del grupo:
1. **Nota Markdown en el vault** `~/Obsidian/osint` bajo
`dominios/<slug>/recon/<scan_type>-<YYYYMMDD-HHMM>.md`. Frontmatter tipado
(`tipo: scan-red`, `scan_tipo`, `target`, `slug`, `fecha`, `herramienta`,
`tags: [scan-red, <scan_type>, recon]`) y el `raw` del scan en un bloque de
codigo. Es la **capa critica**: si falla, el sink devuelve `status:error`.
2. **Fila en la tabla DuckDB `network_scans`** (schema `main`) del service
`osint_db`, via `POST http://127.0.0.1:8771/api/scan`. Columnas:
`id, target, target_slug, scan_type, tool, scan_ts, note_path, summary(JSON),
created_at`. Es la **capa best-effort**: si el service esta caido o no expone
el endpoint, el sink degrada a solo-nota con `registered=False` +
`register_warning`, sin romper. El re-ingest del vault NO borra esta tabla.
**REGLA: todo escaneo se guarda en OSINT.** No hay scans "sueltos". O usas el
pipeline `recon_osint` (scan + archivado en 1 call), o llamas la funcion de scan
atomica y a continuacion `save_scan_to_osint` con su `raw`. El slug del target se
deriva con `re.sub(r"[^a-z0-9._-]+", "-", target.lower())`.
## Escaneos nmap utiles para segundo plano
Los perfiles pesados de `nmap_scan` deben lanzarse en background (`&` / `nohup` / `run_in_background`) por su duracion. Pasa `out_dir` para conservar el XML y sube `timeout_s`.
| Perfil | Flags nmap | Cuando usarlo | Duracion |
|---|---|---|---|
| `full-tcp` | `-p- -T4` | Mapear los 65535 puertos TCP (no solo el top 1000). Cuando buscas servicios en puertos no estandar. | Minutos a horas → background |
| `vuln` | `-sV --script vuln -T4` | Correr los scripts NSE de vulnerabilidades sobre los servicios detectados. Fase posterior a un service scan. | Largo, ruidoso → background |
| `udp-top` | `-sU --top-ports 100 -T4` | Descubrir servicios UDP (DNS, SNMP, NTP...). UDP es lento y suele requerir sudo. | Largo → background |
| `service` | `-sV -sC -T4` | Deteccion de version + scripts default sobre puertos abiertos. A veces tolerable en primer plano. | Medio (puede ir a background) |
| `aggressive` | `-A -T4` | OS + version + scripts + traceroute de golpe. Muy detectable; el `-O` interno puede pedir sudo. | Largo, ruidoso → background |
Perfiles ligeros que SI corren bien en primer plano: `quick` (`-T4 -F`, top 100), `top1000` (`-T4`), `discovery` (`-sn`, ping sweep de una subred → puebla `hosts_up`), `os` (`-O`, requiere sudo).
## Prerequisitos
- **CLIs instaladas** en el PATH: `whois` (`apt install whois`), `rdap` (openrdap, normalmente en `~/go/bin/rdap``go install github.com/openrdap/rdap/cmd/rdap@latest`), `dig` (`dnsutils`/`bind-utils`), `ping` (`iputils-ping`), `traceroute`, `nmap`. Si falta el binario, la funcion devuelve `status:error` con la instruccion de instalacion, nunca lanza.
- **Privilegios**: los perfiles de nmap `os` (-O), `udp-top` (-sU) y parte de `aggressive` requieren sudo/root; sin privilegios nmap cae a connect-scan TCP y esos modos quedan incompletos (estas funciones no usan sudo).
- **Service `osint_db` vivo** en `http://127.0.0.1:8771` para el registro estructurado en `network_scans`. Si esta caido, los scans siguen guardandose como nota (solo se pierde la fila DuckDB hasta el siguiente re-registro). Ver memoria `osint-duckdb-stack`.
## Fronteras (que NO cubre)
- **No es un framework de explotacion.** Es reconocimiento: identifica superficie (puertos, servicios, versiones, registro, ruta). No explota vulnerabilidades, no hace fuerza bruta de credenciales, no entrega payloads. Para eso, herramientas dedicadas fuera del registry.
- **Solo hosts autorizados o propios.** Escanear infraestructura de terceros sin permiso explicito puede ser delito. `scanme.nmap.org` es el unico host de terceros legal por defecto (es el host oficial de pruebas de nmap).
- **No evade deteccion.** No implementa tecnicas de evasion de IDS/WAF, fragmentacion, decoys ni timing de sigilo; `-T4` es ruidoso a proposito. Un objetivo que defienda activamente puede detectar y filtrar el escaneo.
- **No cubre OSINT pasivo de personas** (dorks, usernames, emails) mas alla de listar las funciones afines: esas viven en el grupo `osint-passive`. El render BD→nota y el grafo del vault son de `obsidian`/`duckdb`.