Automatisierte Domain-Namen-Extraktion aus Let's-Encrypt-Certificate-Transparency-Logs
Vor ein paar Tagen ist Let’s Encrypt in die öffentliche Beta gegangen. Zum Zeitpunkt der Erstellung dieses Artikels wurden fast 120k Zertifikate ausgestellt, einschließlich des Zertifikats für TechOverflow.
Ich mag den Let’s-Encrypt-Service wirklich und ich glaube, dass er tatsächlich die Art verändern könnte, wie Menschen HTTPS-Verschlüsselung wahrnehmen. Es gibt jedoch einen selten erwähnten Nebeneffekt beim Schutz deiner Domains mit ihren Zertifikaten.
Let’s Encrypt veröffentlicht Certificate-Transparency-Logs auf crt.sh. Diese Transparenz kommt jedoch nicht ohne Nebeneffekte: crt.sh veröffentlicht effektiv.
Mit anderen Worten: Das Verstecken von Sites vor der Öffentlichkeit durch Nicht-Veröffentlichen ihrer (Sub-)Domain-Namen funktioniert nicht, wenn man ein Zertifikat für die Domain bei Services wie Let’s Encrypt ausstellt.
Zur Demonstration habe ich schnell ein Skript zusammengeschrieben, das automatisch eine definierbare Anzahl von crt.sh-IDs abruft und deren Domain-Namen ausgibt. Es startet beim aktuellsten Zertifikat von Let’s Encrypt, das in der crt.sh-Datenbank vorhanden ist.
Verwende es so, um 1000 Zertifikate abzurufen:
$ python3 letsencrypt-domains.py 1000Beachte, dass 1000 Domains nicht zwingend 1000 Domain-Namen entsprechen: Einerseits stellen Leute manchmal Zertifikate neu aus, während sie sich an die Mechanismen von Let’s Encrypt gewöhnen. Andererseits kann ein Zertifikat mehrere Domain-Namen enthalten.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Automatisierte Extraktion von Domain-Namen aus Certificate-Transparency-Logs
Extrahiert Domain-Namen aus crt.sh-Transparency-Logs für Zertifikate,
die von Let's Encrypt ausgestellt wurden
NUR FÜR DEMONSTRATIONSZWECKE
"""
__copyright__ = "Copyright (c) 2015 Uli Köhler"
__license__ = "Apache License v2.0"
__version__ = "1.0.0"
import requests
import re
import itertools
from multiprocessing import Pool
domainRegex = re.compile(r"DNS:([A-Za-z0-9\.]+)<BR>")
certLinkRegex = re.compile(r'<A href="\?id=(\d+)">')
def fetchDomainsByID(i):
"Rufe eine crt.sh-Domain-Liste nach ID ab"
response = requests.get("https://crt.sh/?id={0}".format(i))
return domainRegex.findall(response.text)
def findLatestCACRTSHID(caid):
"""
Für eine gegebene crt.sh-CA-ID, finde
"""
response = requests.get("https://crt.sh/?Identity=%25&iCAID={0}".format(caid))
return int(certLinkRegex.search(response.text).group(1))
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('n', type=int, default=100, help='Anzahl der Zertifikat-IDs zum Ausprobieren')
parser.add_argument('-p', default=32, type=int, help='Anzahl der parallelen IO-Threads')
parser.add_argument('-q', '--quiet', action="store_true", help='Info-Meldungen unterdrücken')
args = parser.parse_args()
pool = Pool(args.p)
start = findLatestCACRTSHID(7395)
n = args.n
if not args.quiet:
print("Rufe {0} crt.sh-IDs ab ab {1}".format(n, start))
# Domains mit einem Pool abrufen & extrahieren
domainLists = pool.map(fetchDomainsByID,
range(start, start - n, -1))
domains = sorted(set(itertools.chain(*domainLists)))
# Domains ausgeben
for domain in domains:
print(domain)