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:
2026-06-14 15:12:07 +02:00
parent d89da1292d
commit 935008ec3f
49 changed files with 6659 additions and 302 deletions
@@ -0,0 +1,206 @@
"""Mapeo puro de número de puerto a servicio IANA well-known.
Tabla estática embebida que asocia (puerto, protocolo) con el servicio que la
convención IANA / nmap-services espera típicamente en ese puerto, más una
descripción corta. Sin red, sin I/O: dado un puerto abierto detectado por un
scanner, esta función dice qué servicio se ESPERA ahí por convención, no lo
verifica en vivo.
"""
# (port, proto) -> (service, description)
WELL_KNOWN: dict[tuple[int, str], tuple[str, str]] = {
(20, "tcp"): ("ftp-data", "FTP Data Transfer"),
(21, "tcp"): ("ftp", "File Transfer Protocol (control)"),
(22, "tcp"): ("ssh", "Secure Shell"),
(23, "tcp"): ("telnet", "Telnet"),
(25, "tcp"): ("smtp", "Simple Mail Transfer Protocol"),
(37, "tcp"): ("time", "Time Protocol"),
(43, "tcp"): ("whois", "WHOIS Directory Service"),
(53, "tcp"): ("dns", "Domain Name System (zone transfer)"),
(53, "udp"): ("dns", "Domain Name System"),
(67, "udp"): ("dhcp", "DHCP Server (BOOTP)"),
(68, "udp"): ("dhcp", "DHCP Client (BOOTP)"),
(69, "udp"): ("tftp", "Trivial File Transfer Protocol"),
(79, "tcp"): ("finger", "Finger Protocol"),
(80, "tcp"): ("http", "Hypertext Transfer Protocol"),
(88, "tcp"): ("kerberos", "Kerberos Authentication"),
(110, "tcp"): ("pop3", "Post Office Protocol v3"),
(111, "tcp"): ("rpcbind", "ONC RPC / Portmapper"),
(111, "udp"): ("rpcbind", "ONC RPC / Portmapper"),
(113, "tcp"): ("ident", "Ident / Auth Service"),
(119, "tcp"): ("nntp", "Network News Transfer Protocol"),
(123, "udp"): ("ntp", "Network Time Protocol"),
(135, "tcp"): ("msrpc", "Microsoft RPC Endpoint Mapper"),
(137, "udp"): ("netbios-ns", "NetBIOS Name Service"),
(138, "udp"): ("netbios-dgm", "NetBIOS Datagram Service"),
(139, "tcp"): ("netbios-ssn", "NetBIOS Session Service"),
(143, "tcp"): ("imap", "Internet Message Access Protocol"),
(161, "udp"): ("snmp", "Simple Network Management Protocol"),
(162, "udp"): ("snmptrap", "SNMP Trap"),
(177, "udp"): ("xdmcp", "X Display Manager Control Protocol"),
(179, "tcp"): ("bgp", "Border Gateway Protocol"),
(389, "tcp"): ("ldap", "Lightweight Directory Access Protocol"),
(427, "tcp"): ("svrloc", "Service Location Protocol"),
(443, "tcp"): ("https", "HTTP over TLS/SSL"),
(445, "tcp"): ("smb", "SMB / Microsoft Directory Services"),
(464, "tcp"): ("kpasswd", "Kerberos Password Change"),
(465, "tcp"): ("smtps", "SMTP over TLS/SSL"),
(500, "udp"): ("isakmp", "ISAKMP / IKE (IPsec)"),
(512, "tcp"): ("exec", "Remote Process Execution (rexec)"),
(513, "tcp"): ("login", "Remote Login (rlogin)"),
(514, "tcp"): ("shell", "Remote Shell (rsh)"),
(514, "udp"): ("syslog", "Syslog"),
(515, "tcp"): ("printer", "Line Printer Daemon (LPD)"),
(520, "udp"): ("rip", "Routing Information Protocol"),
(523, "tcp"): ("ibm-db2", "IBM DB2"),
(548, "tcp"): ("afp", "Apple Filing Protocol"),
(554, "tcp"): ("rtsp", "Real Time Streaming Protocol"),
(587, "tcp"): ("submission", "SMTP Mail Submission"),
(623, "udp"): ("ipmi", "IPMI / RMCP"),
(631, "tcp"): ("ipp", "Internet Printing Protocol"),
(636, "tcp"): ("ldaps", "LDAP over TLS/SSL"),
(873, "tcp"): ("rsync", "rsync File Synchronization"),
(902, "tcp"): ("vmware", "VMware ESXi / Authentication"),
(989, "tcp"): ("ftps-data", "FTP Data over TLS/SSL"),
(990, "tcp"): ("ftps", "FTP over TLS/SSL"),
(993, "tcp"): ("imaps", "IMAP over TLS/SSL"),
(995, "tcp"): ("pop3s", "POP3 over TLS/SSL"),
(1080, "tcp"): ("socks", "SOCKS Proxy"),
(1194, "udp"): ("openvpn", "OpenVPN"),
(1352, "tcp"): ("lotusnotes", "IBM Lotus Notes / Domino"),
(1433, "tcp"): ("mssql", "Microsoft SQL Server"),
(1434, "udp"): ("mssql-m", "Microsoft SQL Monitor"),
(1521, "tcp"): ("oracle", "Oracle Database Listener"),
(1723, "tcp"): ("pptp", "Point-to-Point Tunneling Protocol"),
(1883, "tcp"): ("mqtt", "MQTT Message Broker"),
(2049, "tcp"): ("nfs", "Network File System"),
(2082, "tcp"): ("cpanel", "cPanel"),
(2083, "tcp"): ("cpanel-ssl", "cPanel over TLS/SSL"),
(2181, "tcp"): ("zookeeper", "Apache ZooKeeper"),
(2375, "tcp"): ("docker", "Docker API (unencrypted)"),
(2376, "tcp"): ("docker-ssl", "Docker API over TLS"),
(2483, "tcp"): ("oracle-db", "Oracle DB (insecure)"),
(2484, "tcp"): ("oracle-db-ssl", "Oracle DB over TLS/SSL"),
(3000, "tcp"): ("dev-http", "Development HTTP / Grafana"),
(3128, "tcp"): ("squid", "Squid HTTP Proxy"),
(3268, "tcp"): ("globalcat", "LDAP Global Catalog"),
(3306, "tcp"): ("mysql", "MySQL / MariaDB"),
(3389, "tcp"): ("rdp", "Remote Desktop Protocol"),
(3690, "tcp"): ("svn", "Subversion"),
(4369, "tcp"): ("epmd", "Erlang Port Mapper Daemon"),
(4444, "tcp"): ("metasploit", "Metasploit Default Listener"),
(4505, "tcp"): ("saltstack", "SaltStack Publish"),
(4506, "tcp"): ("saltstack", "SaltStack Request"),
(5000, "tcp"): ("upnp", "UPnP / Flask Dev Server"),
(5060, "udp"): ("sip", "Session Initiation Protocol"),
(5061, "tcp"): ("sips", "SIP over TLS"),
(5432, "tcp"): ("postgresql", "PostgreSQL Database"),
(5601, "tcp"): ("kibana", "Kibana"),
(5672, "tcp"): ("amqp", "Advanced Message Queuing (RabbitMQ)"),
(5900, "tcp"): ("vnc", "Virtual Network Computing"),
(5984, "tcp"): ("couchdb", "Apache CouchDB"),
(5985, "tcp"): ("winrm", "Windows Remote Management (HTTP)"),
(5986, "tcp"): ("winrm-ssl", "Windows Remote Management (HTTPS)"),
(6379, "tcp"): ("redis", "Redis Key-Value Store"),
(6443, "tcp"): ("kubernetes", "Kubernetes API Server"),
(6660, "tcp"): ("irc", "Internet Relay Chat"),
(6667, "tcp"): ("irc", "Internet Relay Chat"),
(7001, "tcp"): ("weblogic", "Oracle WebLogic"),
(8000, "tcp"): ("http-alt", "HTTP Alternate / Dev Server"),
(8008, "tcp"): ("http-alt", "HTTP Alternate"),
(8080, "tcp"): ("http-proxy", "HTTP Proxy / Alternate"),
(8086, "tcp"): ("influxdb", "InfluxDB"),
(8088, "tcp"): ("http-alt", "HTTP Alternate / Hadoop"),
(8443, "tcp"): ("https-alt", "HTTPS Alternate"),
(8500, "tcp"): ("consul", "HashiCorp Consul"),
(8888, "tcp"): ("http-alt", "HTTP Alternate / Jupyter"),
(9000, "tcp"): ("http-alt", "HTTP Alternate / PHP-FPM / SonarQube"),
(9042, "tcp"): ("cassandra", "Apache Cassandra (CQL)"),
(9092, "tcp"): ("kafka", "Apache Kafka Broker"),
(9200, "tcp"): ("elasticsearch", "Elasticsearch HTTP"),
(9300, "tcp"): ("elasticsearch", "Elasticsearch Transport"),
(9418, "tcp"): ("git", "Git Protocol"),
(9999, "tcp"): ("http-alt", "HTTP Alternate / Admin"),
(10000, "tcp"): ("webmin", "Webmin Admin Panel"),
(11211, "tcp"): ("memcached", "Memcached"),
(15672, "tcp"): ("rabbitmq-mgmt", "RabbitMQ Management UI"),
(27017, "tcp"): ("mongodb", "MongoDB Database"),
(27018, "tcp"): ("mongodb", "MongoDB Shard"),
(50000, "tcp"): ("sap", "SAP / DB2 DRDA"),
}
def identify_port_service(port: int, proto: str = "tcp") -> dict:
"""Identifica el servicio IANA well-known esperado en un puerto.
Función pura: consulta una tabla estática embebida, sin red ni I/O. Indica
qué servicio se ESPERA por convención en ese puerto, no verifica que sea el
que realmente corre allí.
Args:
port: número de puerto (0-65535).
proto: protocolo, "tcp" o "udp" (default "tcp"). Se normaliza a minúsculas.
Returns:
dict con claves:
- port (int): el puerto consultado.
- proto (str): el protocolo normalizado.
- service (str): nombre del servicio; "unknown" si no está en la
tabla; "invalid" si el puerto está fuera de rango o el protocolo
no es tcp/udp.
- description (str): descripción corta; "" cuando no se conoce.
- known (bool): True solo si hay match en la tabla.
Ejemplos de retorno:
identify_port_service(22)
-> {"port": 22, "proto": "tcp", "service": "ssh",
"description": "Secure Shell", "known": True}
identify_port_service(99999)
-> {"port": 99999, "proto": "tcp", "service": "invalid",
"description": "", "known": False}
"""
proto_norm = str(proto).strip().lower()
if not isinstance(port, int) or isinstance(port, bool):
return {
"port": port,
"proto": proto_norm,
"service": "invalid",
"description": "",
"known": False,
}
if port < 0 or port > 65535 or proto_norm not in ("tcp", "udp"):
return {
"port": port,
"proto": proto_norm,
"service": "invalid",
"description": "",
"known": False,
}
match = WELL_KNOWN.get((port, proto_norm))
if match is None:
return {
"port": port,
"proto": proto_norm,
"service": "unknown",
"description": "",
"known": False,
}
service, description = match
return {
"port": port,
"proto": proto_norm,
"service": service,
"description": description,
"known": True,
}
if __name__ == "__main__":
import json
for p, pr in [(22, "tcp"), (443, "tcp"), (53, "udp"), (3306, "tcp"), (99999, "tcp")]:
print(json.dumps(identify_port_service(p, pr)))