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

18 KiB

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.

cd /home/enmanuel/fn_registry
./fn run recon_osint ejemplo.com whois

Equivalente desde Python (cuando necesitas el dict de resultado):

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.

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.

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.

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.

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.

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/rdapgo 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.