eb8dbf66a1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
69 lines
2.4 KiB
Python
69 lines
2.4 KiB
Python
"""Enumeracion OSINT pasiva de subdominios via Certificate Transparency (crt.sh).
|
|
|
|
Funcion IMPURA: consulta los logs publicos de Certificate Transparency a
|
|
traves de crt.sh y extrae los subdominios que han aparecido en certificados
|
|
emitidos para el dominio. Es OSINT pasivo: no toca al dominio objetivo, solo
|
|
consulta registros CT publicos.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(
|
|
0, os.path.join(os.path.dirname(__file__), "..", "..", "functions")
|
|
)
|
|
|
|
from infra.http_get_json import http_get_json # noqa: E402
|
|
|
|
|
|
def enum_subdomains_crtsh(dominio: str, timeout_s: float = 20.0) -> list:
|
|
"""Enumera subdominios de un dominio desde Certificate Transparency.
|
|
|
|
Consulta ``https://crt.sh/?q=%25.<dominio>&output=json`` (``%25`` = ``%``,
|
|
el wildcard de crt.sh) usando ``http_get_json`` del registry. crt.sh
|
|
devuelve un array JSON de certificados; de cada uno se toma el campo
|
|
``name_value`` (que puede contener varios nombres separados por saltos de
|
|
linea, uno por SAN). Se separan, deduplican, se filtran los wildcards
|
|
(``*.``) y se devuelve la lista ordenada de subdominios unicos.
|
|
|
|
Args:
|
|
dominio: Dominio base a enumerar (ej. ``"organic-machine.com"``).
|
|
timeout_s: Segundos maximo de espera de la peticion HTTP (default 20).
|
|
|
|
Returns:
|
|
Lista ordenada de subdominios unicos (sin wildcards). Lista vacia si
|
|
crt.sh no devuelve resultados.
|
|
|
|
Raises:
|
|
RuntimeError: Si el dominio esta vacio o la peticion HTTP falla.
|
|
"""
|
|
if not dominio or not dominio.strip():
|
|
raise RuntimeError("enum_subdomains_crtsh: dominio vacio")
|
|
|
|
dom = dominio.strip().lower()
|
|
# %25 es el wildcard '%' de crt.sh: busca cualquier nombre que termine en .<dominio>
|
|
url = f"https://crt.sh/?q=%25.{dom}&output=json"
|
|
|
|
data = http_get_json(url, timeout=timeout_s)
|
|
|
|
if not isinstance(data, list):
|
|
raise RuntimeError(
|
|
f"enum_subdomains_crtsh: respuesta crt.sh inesperada "
|
|
f"(tipo {type(data).__name__})"
|
|
)
|
|
|
|
found: set = set()
|
|
for cert in data:
|
|
if not isinstance(cert, dict):
|
|
continue
|
|
name_value = cert.get("name_value", "")
|
|
for raw_name in name_value.split("\n"):
|
|
name = raw_name.strip().lower()
|
|
if not name:
|
|
continue
|
|
if name.startswith("*."):
|
|
continue
|
|
found.add(name)
|
|
|
|
return sorted(found)
|