Automatisierte Domain-Namen-Extraktion aus Let's-Encrypt-Certificate-Transparency-Logs

English Deutsch

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:

usage.sh
$ python3 letsencrypt-domains.py 1000

Beachte, 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.

letsencrypt-domains.py
#!/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)

Check out similar posts by category: Linux