Was macht Analyse von Certificate-Transparency-Logs zur Phishing-Erkennung?
Überblick
Certificate Transparency (CT) ist ein Internetsicherheitsstandard, der ein öffentliches, nur-anhängbares Log aller ausgestellten SSL/TLS-Zertifikate erstellt. Die Überwachung von CT-Logs ermöglicht die frühzeitige Erkennung von Phishing-Domains, die Zertifikate registrieren, die legitime Marken nachahmen, unbefugten Zertifikatsausstellungen für eigene Domains sowie zertifikatsbasierter Angriffsinfrastruktur. Diese Fertigkeit umfasst das Abfragen von CT-Logs über crt.sh, die Echtzeitüberwachung mit Certstream, den Aufbau automatisierter Warnungen für verdächtige Zertifikate und die Integration der Ergebnisse in Threat-Intelligence-Workflows.
Verwendungszweck
- Bei der Untersuchung von Sicherheitsvorfällen, die eine Analyse von Certificate Transparency auf Phishing erfordern
- Beim Erstellen von Erkennungsregeln oder Threat-Hunting-Abfragen für diesen Bereich
- Wenn SOC-Analysten strukturierte Verfahren für diesen Analysetyp benötigen
- Beim Validieren der Sicherheitsüberwachungsabdeckung für verwandte Angriffstechniken
Voraussetzungen
- Python 3.9+ mit den Bibliotheken
requests,certstream,tldextract,Levenshtein - Zugriff auf crt.sh (https://crt.sh/) für historische CT-Log-Abfragen
- Certstream (https://certstream.calidog.io/) für Echtzeitüberwachung
- Liste der zu überwachenden Unternehmensdomains und Markenschlüsselwörter
- Verständnis der SSL/TLS-Zertifikatstruktur und des Ausstellungsprozesses
Schlüsselkonzepte
Certificate-Transparency-Logs
CT-Logs sind kryptografisch gesicherte, öffentlich prüfbare, nur-anhängbare Aufzeichnungen der TLS-Zertifikatsausstellung. Große Zertifizierungsstellen (Let’s Encrypt, DigiCert, Sectigo, Google Trust Services) übermitteln alle ausgestellten Zertifikate an mehrere CT-Logs. Ab 2025 verlangen Chrome und Safari für alle öffentlich vertrauenswürdigen Zertifikate CT.
Phishing-Erkennung via CT
Angreifer registrieren täuschend ähnliche Domains und besorgen sich kostenlose Zertifikate (oft von Let’s Encrypt), um Phishing-Seiten mit HTTPS legitim erscheinen zu lassen. Die CT-Überwachung erkennt diese frühzeitig, da das Zertifikat bereits vor dem Start der Phishing-Kampagne in den Logs erscheint – ein Zeitfenster für proaktive Blockaden.
crt.sh-Datenbank
crt.sh ist eine kostenlose Weboberfläche und PostgreSQL-Datenbank, betrieben von Sectigo, die CT-Logs indiziert. Sie unterstützt Wildcard-Suchen (%.example.com), direkte SQL-Abfragen und JSON-API-Antworten. Sie erfasst die Zertifikatsausstellung, -ablauf und -widerruf über alle wichtigen CT-Logs hinweg.
Arbeitsablauf
Schritt 1: crt.sh nach Zertifikatshistorie abfragen
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):
"""crt.sh nach Zertifikaten zu einer Domain abfragen."""
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)} Zertifikate für *.{domain}")
return certs
return []
def find_suspicious_certs(self, domain):
"""Zertifikate finden, die möglicherweise Phishing-Versuche sind."""
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", "")
# Prüfen auf exakte Domain-Übereinstimmungen (legitim)
extracted = tldextract.extract(common_name)
cert_domain = f"{extracted.domain}.{extracted.suffix}"
if cert_domain == domain:
continue # Legitimes Zertifikat
# Verdächtige Muster markieren
flags = []
if domain.replace(".", "") in common_name.replace(".", ""):
flags.append("enthält Ziel-Domain-String")
if any(kw in common_name for kw in self.brand_keywords):
flags.append("enthält Markenschlüsselwort")
if "let's encrypt" in issuer.lower():
flags.append("kostenlose CA (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"[+] {len(suspicious)} verdächtige Zertifikate gefunden")
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']}] Flags: {cert['flags']}")
Schritt 2: Echtzeitüberwachung mit 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):
"""Echtzeit-CT-Log-Überwachung starten."""
print("[*] Starte Certstream-Überwachung...")
print(f" Überwacht: {self.watched_domains}")
print(f" Schlüsselwörter: {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" [ALARM] {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[+] Überwachung gestoppt. {len(self.alerts)} Alarme gesammelt.")
return self.alerts
def _is_suspicious(self, domain):
"""Prüfen, ob eine Domain im Verhältnis zu überwachten Domains verdächtig ist."""
for watched in self.watched_domains:
# Exakte Schlüsselwortübereinstimmung
watched_base = watched.split(".")[0]
if watched_base in domain and domain != watched:
return True
# Levenshtein-Distanz (Typosquatting-Erkennung)
domain_base = tldextract.extract(domain).domain
similarity = Levenshtein.ratio(watched_base, domain_base)
if similarity >= self.threshold and domain_base != watched_base:
return True
# Markenschlüsselwortübereinstimmung
for keyword in self.brand_keywords:
if keyword in domain:
return True
return False
def _get_reason(self, domain):
"""Ermitteln, warum die Domain markiert wurde."""
reasons = []
for watched in self.watched_domains:
watched_base = watched.split(".")[0]
if watched_base in domain:
reasons.append(f"enthält '{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"ähnlich zu '{watched}' ({similarity:.0%})")
for kw in self.brand_keywords:
if kw in domain:
reasons.append(f"Markenschlüsselwort '{kw}'")
return "; ".join(reasons) if reasons else "unbekannt"
cs_monitor = CertstreamMonitor(
watched_domains=["mycompany.com"],
brand_keywords=["mycompany", "mybrand"],
similarity_threshold=0.75,
)
alerts = cs_monitor.start_monitoring(max_alerts=50)
Schritt 3: Subdomains aus CT-Logs aufzählen
def enumerate_subdomains_ct(domain):
"""Alle Subdomains aus Certificate-Transparency-Logs entdecken."""
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"[+] CT-Subdomain-Aufzählung für {domain}: {len(sorted_subs)} Subdomains")
return sorted_subs
subdomains = enumerate_subdomains_ct("example.com")
for sub in subdomains[:20]:
print(f" {sub}")
Schritt 4: CT-Intelligence-Report erstellen
def generate_ct_report(suspicious_certs, certstream_alerts, domain):
report = f"""# Certificate-Transparency-Intelligence-Report
## Ziel-Domain: {domain}
## Erstellt: {datetime.now().isoformat()}
## Zusammenfassung
- Verdächtige Zertifikate gefunden: {len(suspicious_certs)}
- Echtzeit-Alarme ausgelöst: {len(certstream_alerts)}
## Verdächtige Zertifikate (crt.sh)
| Common Name | Aussteller | Flags | crt.sh-Link |
|------------|--------|-------|-------------|
"""
for cert in suspicious_certs[:20]:
flags = "; ".join(cert.get("flags", []))
report += (f"| {cert['common_name']} | {cert['issuer'][:30]} "
f"| {flags} | [Ansehen]({cert['crt_sh_url']}) |\n")
report += f"""
## Echtzeit-Certstream-Alarme
| Domain | Aussteller | Grund | Erkannt |
|--------|--------|--------|----------|
"""
for alert in certstream_alerts[:20]:
report += (f"| {alert['domain']} | {alert['issuer']} "
f"| {alert['reason']} | {alert['detected_at'][:19]} |\n")
report += """
## Empfehlungen
1. Markierte Domains zur DNS-Sinkhole / Web-Proxy-Blockliste hinzufügen
2. Takedown-Anträge für bestätigte Phishing-Domains einreichen
3. CT-Logs kontinuierlich auf neue Zertifikatsregistrierungen überwachen
4. CAA-DNS-Einträge implementieren, um die Zertifikatsausstellung für Ihre Domains einzuschränken
5. DMARC bereitstellen, um E-Mail-Spoofing durch ähnliche Domains zu verhindern
"""
with open(f"ct_report_{domain.replace('.','_')}.md", "w") as f:
f.write(report)
print(f"[+] CT-Report gespeichert")
return report
generate_ct_report(suspicious, alerts if 'alerts' in dir() else [], "mycompany.com")
Validierungskriterien
- crt.sh-Abfragen geben Zertifikatsdaten für Ziel-Domains zurück
- Verdächtige Zertifikate werden anhand von Nachahmungsmustern identifiziert
- Certstream-Echtzeitüberwachung erkennt neue Phishing-Zertifikate
- Subdomain-Aufzählung liefert eine umfassende Liste aus CT-Logs
- Alarme werden mit Grundklassifizierung generiert
- CT-Intelligence-Report wird mit handlungsorientierten Empfehlungen erstellt