feat(recon): grupo de reconocimiento de red + servicios + fingerprint web
Añade el capability group `recon` (dominio cybersecurity + pipelines, Python),
con la política de archivado OSINT y página madre docs/capabilities/recon.md.
Lookups y sondeo (wrappers de CLI):
- whois_lookup, rdap_lookup, dns_records, ping_host, traceroute_host, nmap_scan
- save_scan_to_osint (sink común) + recon_osint (pipeline one-shot scan+archivado)
Escaneo de puertos/servicios nativo (stdlib, sin nmap ni sudo):
- scan_tcp_ports: connect-scan TCP concurrente (open/closed/filtered)
- grab_service_banner: banner grab + identificación de servicio/versión real
- identify_port_service: puro, puerto -> servicio IANA esperado (~120 puertos)
- scan_port_services: pipeline one-shot (scan -> identify + banner por puerto abierto)
Fingerprint de tecnología web (estilo Wappalyzer), patrón pura/impura:
- fetch_http_fingerprint: GET stdlib, recoge headers/html/cookies (solo nombres)
- detect_web_tech: puro, matchea ~50 firmas regex -> tecnologías por categoría
- fingerprint_web_stack: pipeline one-shot url -> tecnologías
Todas devuelven dict {status} sin lanzar. Tests: 43 verdes, sin red externa.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
---
|
||||
name: fingerprint_web_stack
|
||||
kind: pipeline
|
||||
lang: py
|
||||
domain: pipelines
|
||||
version: "1.0.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]
|
||||
uses_functions:
|
||||
- fetch_http_fingerprint_py_cybersecurity
|
||||
- detect_web_tech_py_cybersecurity
|
||||
- save_scan_to_osint_py_cybersecurity
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_py_core"
|
||||
imports: []
|
||||
params:
|
||||
- name: url
|
||||
desc: "URL del sitio objetivo (ej. https://example.com). Sin esquema se asume https:// con fallback a http://, igual que fetch_http_fingerprint."
|
||||
- name: timeout_s
|
||||
desc: "Timeout de la peticion HTTP en segundos. Default 15.0. Se pasa tal cual a fetch_http_fingerprint."
|
||||
- name: verify_tls
|
||||
desc: "Si False, no verifica el certificado TLS (inseguro, solo para hosts propios con cert self-signed). Default True. Se pasa a fetch_http_fingerprint."
|
||||
- name: max_html_bytes
|
||||
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."
|
||||
tested: true
|
||||
tests: ["test_golden_fingerprint_servidor_local_wordpress_nginx", "test_save_false_no_archiva_osint", "test_fetch_fallido_propaga_error_sin_red"]
|
||||
test_file_path: "python/functions/pipelines/fingerprint_web_stack_test.py"
|
||||
file_path: "python/functions/pipelines/fingerprint_web_stack.py"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
from pipelines.fingerprint_web_stack import fingerprint_web_stack
|
||||
|
||||
# Fingerprint del stack tecnologico de un sitio, en 1 paso (sin archivar).
|
||||
r = fingerprint_web_stack("https://example.com", save=False)
|
||||
print(r["status"]) # "ok"
|
||||
print(r["server"]) # "nginx/1.24.0"
|
||||
print(r["count"]) # 5
|
||||
for t in r["technologies"]:
|
||||
print(t["name"], t["category"], t["version"], t["confidence"])
|
||||
# WordPress cms 6.4 high
|
||||
# nginx web-server 1.24.0 high
|
||||
# PHP programming-language medium
|
||||
print(r["by_category"]) # {"cms": ["WordPress"], "web-server": ["nginx"], ...}
|
||||
```
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
```bash
|
||||
# Por CLI: detecta el stack de un sitio.
|
||||
./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
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Cuando quieras en UN solo paso saber que tecnologia usa un sitio web (servidor,
|
||||
CMS, frameworks JS, lenguaje, analytics, CDN, WAF) — el equivalente registry de
|
||||
Wappalyzer. Reemplaza el patron `fetch_http_fingerprint` -> `detect_web_tech`
|
||||
(un fetch + un matching). Tipico para: reconocimiento web inicial de un
|
||||
objetivo, averiguar el CMS/framework de un sitio antes de un pentest
|
||||
autorizado, auditar la superficie tecnologica de tus propios dominios, o
|
||||
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.
|
||||
- **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`.
|
||||
- **`verify_tls=False` es inseguro**: desactiva la verificacion del certificado
|
||||
(MITM posible). Usalo solo contra hosts propios con cert self-signed.
|
||||
- **Un WAF/anti-bot puede devolver un challenge** (Cloudflare, Imperva...) en vez
|
||||
del sitio real: en ese caso las tecnologias detectadas seran las del WAF y el
|
||||
HTML del challenge, no las del sitio de fondo.
|
||||
- **save=True escribe en el vault OSINT** (`~/Obsidian/osint`) y hace POST al
|
||||
service `osint_db` (`http://127.0.0.1:8771`). Si el service esta caido,
|
||||
`save_scan_to_osint` degrada a solo-nota (`saved.registered=False` con
|
||||
`register_warning`); el pipeline no falla por eso.
|
||||
- **Autorizacion legal**: hacer fingerprint de hosts ajenos puede entrar en
|
||||
reconocimiento no autorizado. Respeta el scope y la autorizacion explicita;
|
||||
usalo sobre objetivos propios o consentidos.
|
||||
- **Pipeline impuro**: hace red (fetch HTTP) y, con `save=True`, FS/HTTP (vault +
|
||||
service). No es determinista entre ejecuciones.
|
||||
- Si el fetch HTTP falla del todo (`status != "ok"`: host no resuelve, conexion
|
||||
rechazada, timeout), el pipeline devuelve `{"status":"error","stage":"fetch",...}`
|
||||
y **no** intenta matchear firmas ni archivar nada. Un 403/404/500 NO es fallo
|
||||
de fetch: sigue siendo senal de fingerprint y se procesa con su status_code.
|
||||
|
||||
## Notas
|
||||
|
||||
Pipeline que compone 3 funciones atomicas del dominio `cybersecurity`. No
|
||||
reimplementa logica de fetch, matching de firmas ni persistencia: solo orquesta
|
||||
`fetch_http_fingerprint` (recoleccion de senales, impura) + `detect_web_tech`
|
||||
(matching de firmas, pura) y delega el guardado en `save_scan_to_osint`. El
|
||||
`raw` de evidencia incluye una cabecera con url/final_url/status_code/server/
|
||||
title y una tabla TECNOLOGIA/CATEGORIA/VERSION/CONFIDENCE; nunca embebe el HTML
|
||||
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.
|
||||
Reference in New Issue
Block a user