Análisis de Transparencia de Certificados para Phishing
Resumen
La Transparencia de Certificados (CT) es un estándar de seguridad de Internet que crea un registro público, de solo adición, de todos los certificados SSL/TLS emitidos. Monitorear los registros CT permite la detección temprana de dominios de phishing que registran certificados imitando marcas legítimas, emisión de certificados no autorizados para dominios propios e infraestructura de ataques basada en certificados. Esta habilidad cubre la consulta de registros CT a través de crt.sh, monitoreo en tiempo real con Certstream, la creación de alertas automatizadas para certificados sospechosos y la integración de hallazgos en flujos de trabajo de inteligencia de amenazas.
Cuándo Usarlo
- Al investigar incidentes de seguridad que requieran analizar la transparencia de certificados para phishing
- Al construir reglas de detección o consultas de búsqueda de amenazas para este dominio
- Cuando los analistas del SOC necesitan procedimientos estructurados para este tipo de análisis
- Al validar la cobertura de monitoreo de seguridad para técnicas de ataque relacionadas
Prerrequisitos
- Python 3.9+ con las bibliotecas
requests,certstream,tldextract,Levenshtein - Acceso a crt.sh (https://crt.sh/) para consultas históricas de registros CT
- Certstream (https://certstream.calidog.io/) para monitoreo en tiempo real
- Lista de dominios de la organización y palabras clave de marca a monitorear
- Comprensión de la estructura de certificados SSL/TLS y el proceso de emisión
Conceptos Clave
Registros de Transparencia de Certificados
Los registros CT son registros criptográficamente asegurados, auditables públicamente y de solo adición de la emisión de certificados TLS. Las AC principales (Let's Encrypt, DigiCert, Sectigo, Google Trust Services) envían todos los certificados emitidos a múltiples registros CT. A partir de 2025, Chrome y Safari requieren CT para todos los certificados de confianza pública.
Detección de Phishing mediante CT
Los atacantes registran dominios similares y obtienen certificados gratuitos (a menudo de Let's Encrypt) para que los sitios de phishing parezcan legítimos con HTTPS. El monitoreo CT detecta esto temprano porque el certificado aparece en los registros antes de que se lance la campaña de phishing, proporcionando una ventana para el bloqueo proactivo.
Base de Datos crt.sh
crt.sh es una interfaz web gratuita y una base de datos PostgreSQL operada por Sectigo que indexa registros CT. Admite búsquedas con comodín (%.example.com), consultas SQL directas y respuestas API JSON. Realiza un seguimiento de la emisión, vencimiento y revocación de certificados en todos los registros CT principales.
Flujo de Trabajo
Paso 1: Consultar crt.sh para el Historial de Certificados
import requests
import json
from datetime import datetime
import tldextract
class CTLogMonitor:
CRT_SH_URL = "https://crt.sh"
def __init__(self, monitored_domains, brand_keywords):
self.monitored_domains = monitored_domains
self.brand_keywords = [k.lower() for k in brand_keywords]
def query_crt_sh(self, domain, include_expired=False):
"""Consultar crt.sh para certificados que coincidan con un dominio."""
params = {
"q": f"%.{domain}",
"output": "json",
}
if not include_expired:
params["exclude"] = "expired"
resp = requests.get(self.CRT_SH_URL, params=params, timeout=30)
if resp.status_code == 200:
certs = resp.json()
print(f"[+] crt.sh: {len(certs)} certificados para *.{domain}")
return certs
return []
def find_suspicious_certs(self, domain):
"""Encontrar certificados que puedan ser intentos de phishing."""
certs = self.query_crt_sh(domain)
suspicious = []
for cert in certs:
common_name = cert.get("common_name", "").lower()
name_value = cert.get("name_value", "").lower()
issuer = cert.get("issuer_name", "")
not_before = cert.get("not_before", "")
not_after = cert.get("not_after", "")
# Verificar coincidencias exactas de dominio (legítimas)
extracted = tldextract.extract(common_name)
cert_domain = f"{extracted.domain}.{extracted.suffix}"
if cert_domain == domain:
continue # Certificado legítimo
# Marcar patrones sospechosos
flags = []
if domain.replace(".", "") in common_name.replace(".", ""):
flags.append("contiene cadena del dominio objetivo")
if any(kw in common_name for kw in self.brand_keywords):
flags.append("contiene palabra clave de marca")
if "let's encrypt" in issuer.lower():
flags.append("AC gratuita (Let's Encrypt)")
if flags:
suspicious.append({
"common_name": cert.get("common_name", ""),
"name_value": cert.get("name_value", ""),
"issuer": issuer,
"not_before": not_before,
"not_after": not_after,
"serial": cert.get("serial_number", ""),
"flags": flags,
"crt_sh_id": cert.get("id", ""),
"crt_sh_url": f"https://crt.sh/?id={cert.get('id', '')}",
})
print(f"[+] Encontrados {len(suspicious)} certificados sospechosos")
return suspicious
monitor = CTLogMonitor(
monitored_domains=["mycompany.com", "mycompany.org"],
brand_keywords=["mycompany", "mybrand", "myproduct"],
)
suspicious = monitor.find_suspicious_certs("mycompany.com")
for cert in suspicious[:5]:
print(f" [{cert['common_name']}] Banderas: {cert['flags']}")
Paso 2: Monitoreo en Tiempo Real con Certstream
import certstream
import Levenshtein
import re
from datetime import datetime
class CertstreamMonitor:
def __init__(self, watched_domains, brand_keywords, similarity_threshold=0.8):
self.watched_domains = [d.lower() for d in watched_domains]
self.brand_keywords = [k.lower() for k in brand_keywords]
self.threshold = similarity_threshold
self.alerts = []
def start_monitoring(self, max_alerts=100):
"""Iniciar monitoreo en tiempo real de registros CT."""
print("[*] Iniciando monitoreo Certstream...")
print(f" Observando: {self.watched_domains}")
print(f" Palabras clave: {self.brand_keywords}")
def callback(message, context):
if message["message_type"] == "certificate_update":
data = message["data"]
leaf = data.get("leaf_cert", {})
all_domains = leaf.get("all_domains", [])
for domain in all_domains:
domain_lower = domain.lower().strip("*.")
if self._is_suspicious(domain_lower):
alert = {
"domain": domain,
"all_domains": all_domains,
"issuer": leaf.get("issuer", {}).get("O", ""),
"fingerprint": leaf.get("fingerprint", ""),
"not_before": leaf.get("not_before", ""),
"detected_at": datetime.now().isoformat(),
"reason": self._get_reason(domain_lower),
}
self.alerts.append(alert)
print(f" [ALERTA] {domain} - {alert['reason']}")
if len(self.alerts) >= max_alerts:
raise KeyboardInterrupt
try:
certstream.listen_for_events(callback, url="wss://certstream.calidog.io/")
except KeyboardInterrupt:
print(f"\n[+] Monitoreo detenido. {len(self.alerts)} alertas recopiladas.")
return self.alerts
def _is_suspicious(self, domain):
"""Verificar si el dominio es sospechoso en relación con los dominios observados."""
for watched in self.watched_domains:
# Coincidencia exacta de palabra clave
watched_base = watched.split(".")[0]
if watched_base in domain and domain != watched:
return True
# Distancia de Levenshtein (detección de typosquatting)
domain_base = tldextract.extract(domain).domain
similarity = Levenshtein.ratio(watched_base, domain_base)
if similarity >= self.threshold and domain_base != watched_base:
return True
# Coincidencia de palabra clave de marca
for keyword in self.brand_keywords:
if keyword in domain:
return True
return False
def _get_reason(self, domain):
"""Determinar por qué se marcó el dominio."""
reasons = []
for watched in self.watched_domains:
watched_base = watched.split(".")[0]
if watched_base in domain:
reasons.append(f"contiene '{watched_base}'")
domain_base = tldextract.extract(domain).domain
similarity = Levenshtein.ratio(watched_base, domain_base)
if similarity >= self.threshold and domain_base != watched_base:
reasons.append(f"similar a '{watched}' ({similarity:.0%})")
for kw in self.brand_keywords:
if kw in domain:
reasons.append(f"palabra clave de marca '{kw}'")
return "; ".join(reasons) if reasons else "desconocido"
cs_monitor = CertstreamMonitor(
watched_domains=["mycompany.com"],
brand_keywords=["mycompany", "mybrand"],
similarity_threshold=0.75,
)
alerts = cs_monitor.start_monitoring(max_alerts=50)
Paso 3: Enumerar Subdominios desde Registros CT
def enumerate_subdomains_ct(domain):
"""Descubrir todos los subdominios a partir de los registros de Transparencia de Certificados."""
params = {"q": f"%.{domain}", "output": "json"}
resp = requests.get("https://crt.sh", params=params, timeout=30)
if resp.status_code != 200:
return []
certs = resp.json()
subdomains = set()
for cert in certs:
name_value = cert.get("name_value", "")
for name in name_value.split("\n"):
name = name.strip().lower()
if name.endswith(f".{domain}") or name == domain:
name = name.lstrip("*.")
subdomains.add(name)
sorted_subs = sorted(subdomains)
print(f"[+] Enumeración de subdominios CT para {domain}: {len(sorted_subs)} subdominios")
return sorted_subs
subdomains = enumerate_subdomains_ct("example.com")
for sub in subdomains[:20]:
print(f" {sub}")
Paso 4: Generar Informe de Inteligencia CT
def generate_ct_report(suspicious_certs, certstream_alerts, domain):
report = f"""# Informe de Inteligencia de Transparencia de Certificados
## Dominio Objetivo: {domain}
## Generado: {datetime.now().isoformat()}
## Resumen
- Certificados sospechosos encontrados: {len(suspicious_certs)}
- Alertas en tiempo real disparadas: {len(certstream_alerts)}
## Certificados Sospechosos (crt.sh)
| Nombre Común | Emisor | Banderas | Enlace crt.sh |
|------------|--------|-------|-------------|
"""
for cert in suspicious_certs[:20]:
flags = "; ".join(cert.get("flags", []))
report += (f"| {cert['common_name']} | {cert['issuer'][:30]} "
f"| {flags} | [Ver]({cert['crt_sh_url']}) |\n")
report += f"""
## Alertas en Tiempo Real de Certstream
| Dominio | Emisor | Razón | Detectado |
|--------|--------|--------|----------|
"""
for alert in certstream_alerts[:20]:
report += (f"| {alert['domain']} | {alert['issuer']} "
f"| {alert['reason']} | {alert['detected_at'][:19]} |\n")
report += """
## Recomendaciones
1. Agregar los dominios marcados a la lista de bloqueo del sumidero DNS / proxy web
2. Enviar solicitudes de eliminación para los dominios de phishing confirmados
3. Monitorear los registros CT continuamente para nuevas registraciones de certificados
4. Implementar registros DNS CAA para restringir la emisión de certificados para tus dominios
5. Implementar DMARC para evitar la suplantación de correo electrónico desde dominios similares
"""
with open(f"ct_report_{domain.replace('.','_')}.md", "w") as f:
f.write(report)
print(f"[+] Informe CT guardado")
return report
generate_ct_report(suspicious, alerts if 'alerts' in dir() else [], "mycompany.com")
Criterios de Validación
- Las consultas a crt.sh devuelven datos de certificados para los dominios objetivo
- Se identifican certificados sospechosos basados en patrones de imitación
- El monitoreo en tiempo real de Certstream detecta nuevos certificados de phishing
- La enumeración de subdominios produce una lista completa a partir de los registros CT
- Se generan alertas con clasificación de la razón
- Se crea un informe de inteligencia CT con recomendaciones accionables