Aller au contenu principal
own2pwn
appsec/certificate-transparency-shadow-it.tsx

Certificate Transparency : débusquer le shadow IT par les certificats partagés

Chaque certificat TLS émis finit dans un journal public et permanent. En pivotant sur les certificats partagés (mêmes SAN, même empreinte), on relie des actifs inconnus à un périmètre connu. La technique de recon par Certificate Transparency, côté EASM/NIS2 et bug bounty.

own2pwn··13 min de lecture

Vous avez verrouillé acme.com, son www et son api. Trois actifs, patchés, monitorés, propres. Puis un matin, dans le flux des certificats fraîchement émis, un nom apparaît : vpn-old.acme-corp.net. Un domaine que vous aviez oublié posséder, qui sert exactement le même certificat wildcard que votre prod. Personne ne l'avait inventorié. Le certificat, lui, l'a balancé à la terre entière, signé, horodaté, public.

C'est tout le sujet de cet article : le Certificate Transparency (CT) transforme chaque certificat TLS en balise traçable. On va voir comment pivoter sur les certificats partagés — mêmes SAN, même empreinte — pour relier des actifs inconnus à un périmètre que vous connaissez. La même technique sert l'inventaire de surface d'attaque externe côté défense et l'élargissement de scope côté bug bounty.

Cadre : recon passive, mais scope obligatoire
Lire des logs CT et interroger Censys ou crt.sh est de la reconnaissance passive sur des données publiques — parfaitement légal. Attaquer un actif que vous venez de découvrir ne l'est que s'il entre dans un périmètre autorisé : un mandat de pentest, ou le scope explicite d'un programme de bug bounty. Découvrir un hôte ne le met pas dans le scope. On y revient plus bas.

Le journal public où finissent tous vos certificats

Le Certificate Transparency est né d'un problème concret : une autorité de certification compromise pouvait émettre un certificat valide pourgoogle.com sans que Google ne le sache. La parade, standardisée dans la RFC 6962, oblige les CA à publier chaque certificat émis dans des journaux publics, append-only, vérifiables cryptographiquement (arbres de Merkle). Depuis 2018, Chrome refuse tout simplement d'afficher un certificat qui ne présente pas de preuve d'inscription (un SCT, Signed Certificate Timestamp) dans au moins deux logs.

L'effet de bord, du point de vue de la reconnaissance, est énorme : tout certificat TLS publiquement validé est, par construction, public et permanent. Vous ne pouvez pas le retirer du journal. Chaque fois qu'un de vos services, ou un prestataire en votre nom, demande un certificat (un certbot qui tourne, une instance qui boot, un SaaS qui provisionne un sous-domaine), il grave dans le marbre le nom d'hôte correspondant. Y compris les noms que vous n'avez jamais publiés nulle part.

Trois portes d'entrée pour consulter ces journaux : crt.sh (l'interface web la plus directe, avec une API JSON), Censys (qui indexe les certificats et les hôtes qui les présentent), et le flux temps réel via CertStream, qui pousse chaque nouvelle entrée CT au fil de l'eau. On va se servir des trois.

Le SAN, la première fuite

Un certificat ne couvre pas qu'un seul nom. Le champ Subject Alternative Name (SAN) liste tous les hôtes pour lesquels il est valide. Pour économiser des certificats, on regroupe : une seule demande Let's Encrypt peut couvrir huit sous-domaines d'un coup. Résultat, un seul certificat trahit d'un seul regard une grappe d'actifs — dont certains qui ne sont liés depuis aucune page, aucun sitemap, aucun DNS public évident.

L'extraction de base tient en une requête. L'API de crt.sh renvoie du JSON, et le champ name_value contient les SAN (un par ligne) :

bash
# Tous les noms vus dans des certificats pour *.acme.com, dédoublonnés
curl -s 'https://crt.sh/?q=%25.acme.com&output=json' \
  | jq -r '.[].name_value' \
  | tr '[:upper:]' '[:lower:]' | sed 's/^\*\.//' \
  | sort -u

# acme.com
# api.acme.com
# vpn-old.acme.com          <- celui-là, vous l'aviez oublié
# grafana.staging.acme.com  <- et celui-là, il n'aurait jamais dû sortir

C'est l'usage classique, déjà évoqué dans notre article sur le shadow IT et les actifs exposés que personne ne gère. Mais énumérer les SAN d'un domaine que vous connaissez déjà, c'est rester en terrain balisé. Le vrai gain vient du pivot : partir d'un actif connu pour sauter vers des actifs dont vous ignoriez l'existence, voire vers d'autres domaines racine.

Le vrai pivot : le certificat partagé

Voici l'idée centrale. Un certificat possède une empreinte (le SHA-256 de sa forme DER) qui l'identifie de façon unique. Or un même certificat est souvent déployé sur plusieurs machines : les nœuds derrière un load-balancer, une copie de staging clonée depuis la prod, une vieille VM oubliée, l'IP d'origine cachée derrière un CDN. Tous présentent la même empreinte.

Le point clé : on ne partage pas sa clé privée avec des inconnus. Si deux hôtes servent le même certificat auto-géré, c'est qu'ils appartiennent à la même organisation. Censys et Shodan scannent l'Internet et indexent le certificat présenté par chaque IP. Donc : prenez l'empreinte d'un certificat dont vous savez qu'il appartient à la cible, demandez « qui d'autre sert exactement ce certificat ? », et vous récupérez des hôtes que rien d'autre ne reliait à la cible.

Pam de The Office comparant deux photos identiques : « corporate wants you to find the difference »
L'hôte inconnu et l'actif de prod servent le même certificat. They're the same org.

Concrètement, on récupère d'abord l'empreinte d'un actif connu, puis on la rejoue comme pivot :

bash
# 1) Empreinte SHA-256 du certificat servi par un actif connu
echo | openssl s_client -connect api.acme.com:443 -servername api.acme.com 2>/dev/null \
  | openssl x509 -noout -fingerprint -sha256
# SHA256 Fingerprint=A1:B2:...:9F

# 2) Pivot Censys : tout hôte qui présente CE certificat exact
#    (recherche : services.tls.certificates.leaf_data.fingerprint_sha256)
censys search 'services.tls.certificates.leaf_data.fingerprint_sha256="a1b2...9f"'
# 203.0.113.10   <- api.acme.com (connu)
# 198.51.100.7   <- inconnu : origine derrière le CDN ? staging ?
pivot-certificat
Connu
api.acme.com
L'actif inventorié qui présente ce certificat : le point de départ.
Découvert
IP d'origine
Derrière le CDN, exposée en direct, hors WAF.
Pivot
Certificat partagé
Empreinte A1B2…9F. Sur Censys / Shodan : « quelles IP servent cette empreinte ? »
Découvert
Copie de staging
Jamais référencée, mais servie avec le même certificat.
Découvert
VM oubliée
Hors inventaire, reliée à la cible par le seul certificat.
Un seul certificat partagé relie un actif connu à des hôtes inconnus : autant de rayons vers le même périmètre.

Ce pivot a une vertu particulière en bug bounty : il perce les CDN. Une cible derrière Cloudflare cache son IP réelle, mais si l'origine sert encore le même certificat que le domaine public (cas extrêmement fréquent), une recherche par empreinte sur Censys recrache l'IP d'origine. Vous tapez alors directement le serveur, en contournant le WAF.

Pivoter par le sujet et l'émetteur

L'empreinte n'est pas le seul axe. Les champs du Subject d'un certificat portent souvent de l'or : une organisation (O=Acme Corporation) sur un certificat OV/EV, un Common Name interne sur un certificat auto-signé qui a fuité dans CT, des noms d'hôtes de management (idrac-07.dc.acme.lan) qu'un admin a un jour exposés par erreur. On pivote alors par organisation pour ratisser tous les certificats d'une entité, pas seulement ceux d'un domaine :

text
# Censys : tous les certificats où l'organisation du sujet = la cible
parsed.subject.organization: "Acme Corporation"

# Censys : certificats émis pour un domaine, même sur d'autres apex
parsed.names: /.*\.acme-corp\.(net|io)/

# crt.sh accepte aussi la recherche par Organisation (Identity -> O)

C'est ce pivot par organisation qui fait sauter d'un domaine racine à l'autre. Vous connaissiez acme.com ; le champ O= vous révèle que la même entité a aussi des certificats sur acme-corp.io et getacme.dev — des apex qu'aucune énumération de sous-domaines n'aurait jamais reliés entre eux.

CertStream : surveiller le flux en temps réel

Tout ce qui précède est une photographie de l'existant. Mais CT est un flux : des dizaines de milliers de certificats par minute. En s'y branchant en direct, on attrape un nouvel actif au moment où son certificat est émis — souvent avant même que le service soit en prod. Et on attrape aussi les domaines de typosquatting qu'un attaquant prépare pour une campagne de phishing : un certificat pour acme-secure-login.com qui apparaît, c'est un signal d'alerte précoce.

Personnage scrutant un mur d'indices à la loupe
Brancher un filtre sur le flux CT, c'est voir vos actifs (et vos sosies malveillants) naître en direct.

Un guetteur minimal tient en quelques lignes : on s'abonne au flux, on filtre sur les motifs qui touchent la marque, on alerte :

text
# CertStream pousse chaque entrée CT en JSON sur un WebSocket public.
# Filtre maison : tout nom qui ressemble à la marque surveillée.

import certstream

WATCH = ("acme", "acme-corp")

def on_event(message, context):
    if message["message_type"] != "certificate_update":
        return
    for name in message["data"]["leaf_cert"]["all_domains"]:
        low = name.lower()
        if any(w in low for w in WATCH):
            print(f"[CT] {name}")          # -> webhook, SIEM, Discord...

certstream.listen_for_events(on_event, url="wss://certstream.calidog.io/")
Filtrer fin, sinon c'est le déluge
Un filtre trop large sur une marque générique noie l'alerte sous le bruit. Affinez sur des motifs spécifiques (votre nom de marque accolé à login, vpn, pay, -secure), et pondérez par la distance de Levenshtein avec vos domaines légitimes pour faire remonter les sosies. Le typosquat utile à repérer, c'est celui qui imite, pas celui qui contient trois lettres communes.

L'angle NIS2 : la découverte d'actifs comme obligation

Cette mécanique n'est pas qu'un jeu de recon. L'article 21 de NIS2 impose une gestion des actifs et une analyse de risques proportionnée — or on ne gère pas un inventaire qu'on ne connaît pas. Le flux CT est précisément la source qui comble l'écart entre la CMDB déclarative et la surface réellement exposée. On a détaillé cette jonction réglementaire dans notre article pilier EASM et conformité NIS2.

L'intérêt du Certificate Transparency pour un dossier NIS2 est triple : la découverte est continue (le flux ne dort jamais), elle est auditable (chaque entrée CT est horodatée et signée, ça pèse lourd face à un contrôle ANSSI), et elle précède l'attaquant qui consulte exactement les mêmes journaux. Surveiller CT, c'est regarder sa propre surface d'attaque avec les yeux de l'adversaire — ce qui est, mot pour mot, la promesse de l'EASM.

CT n'est pas une couverture totale
Le journal ne voit que les certificats publiquement validés. Une PKI interne (CA d'entreprise) n'y apparaît pas, ni les services en HTTP nu, ni ceux derrière un certificat auto-signé jamais soumis. CT cartographie magnifiquement la surface web exposée ; il faut le compléter par du DNS passif, du scan de ports et de l'OSINT pour le reste. Aucune source unique ne suffit.

L'angle bug bounty : élargir le scope proprement

En bug bounty, la qualité de la recon fait la différence entre rapporter le même XSS que cent autres chasseurs et trouver l'actif que personne n'a regardé. Le pivot par certificat est une arme de premier plan pour ça : il déterre des apex secondaires, des environnements de préprod, des origines derrière CDN — exactement le genre de surface sous-explorée où dorment les failles.

Mais il y a un piège, et il est sérieux. Découvrir n'est pas autoriser. Un programme de bug bounty définit un scope précis. Si votre pivot par O= fait remonter acme-labs.io et qu'il n'est pas listé, c'est hors-scope — quand bien même il appartient manifestement à la cible. Le tester vous expose, au mieux à un rapport rejeté, au pire à des poursuites. La bonne pratique : documenter l'actif découvert, vérifier sa présence dans le scope, et le cas échéant le signaler au programme plutôt que de l'attaquer.

Le faux pivot des certificats mutualisés
Toutes les empreintes partagées ne prouvent pas une parenté. Un certificat mutualisé de CDN (Cloudflare, Fastly) couvre des dizaines de clients sans aucun lien entre eux : pivoter sur son empreinte vous reliera à des organisations tierces, pas à la cible. Le pivot par empreinte n'est fiable que sur des certificats auto-gérés (une seule organisation au O=, des SAN cohérents). Vérifiez l'émetteur et le sujet avant de conclure : un certificat dont le sujet ne mentionne que des domaines de la cible, oui ; un cert Cloudflare générique, non.

De la découverte à la preuve

Trouver un actif n'est que la moitié du chemin. Un sous-domaine de staging déterré par CT peut héberger un panel d'admin sans MFA, une copie de base avec des données réelles, une API debug. Reste à le prouver sans casser quoi que ce soit. C'est la frontière entre lister une exposition et démontrer son exploitabilité, qu'on a creusée dans pentest vs scan de vulnérabilité : un inventaire dit « ça existe », un pentest web blackbox dit « voilà ce qu'on en fait ».

Et pour les failles qui ne renvoient rien dans la réponse — une SSRF sur cette API de staging, par exemple — la preuve passe par un canal out-of-band, qu'on a appris à construire dans notre serveur OOB from scratch. La chaîne complète, du certificat oublié à la preuve d'exploitation, c'est exactement le pipeline qu'on industrialise.

À retenir

  • Tout certificat TLS publiquement validé est journalisé dans CT, public et permanent : chaque hôte couvert y laisse une trace, même non publié.
  • Le champ SAN trahit les sous-domaines d'un coup ; le vrai gain vient du pivot par empreinte (mêmes certificats = même organisation) et par O= (saut vers d'autres apex).
  • Le pivot par empreinte perce les CDN en révélant l'IP d'origine — à condition que le certificat soit auto-géré, pas un cert mutualisé Cloudflare.
  • CertStream donne le flux en temps réel : nouvel actif dès l'émission du certificat, et détection précoce des domaines de typosquatting.
  • Côté NIS2, c'est une découverte d'actifs continue et auditable ; côté bug bounty, un élargissement de scope puissant — mais découvrir n'est jamais autoriser.

Votre vrai périmètre n'est pas dans votre tableur : il est dans les journaux CT, et il grossit à chaque certificat émis. La seule question utile, c'est qui le surveille en premier. C'est précisément le métier de notre plateforme EASM, qui corrèle le flux CT au reste de votre surface exposée — ou parlez-en avec un humain via la page contact.

Articles liés