Objectifs
À la fin de ce cours, tu sauras :
- Analyser une politique de programme pour délimiter précisément le périmètre (wildcards, exclusions, actifs tiers, règles de débit).
- Distinguer l'énumération passive de l'énumération active et choisir la bonne technique selon le contexte.
- Identifier les endpoints, technologies et secrets exposés dans les fichiers JavaScript, en séparant clés publiques et clés secrètes.
- Évaluer et prioriser les cibles selon leur valeur métier plutôt que selon leur nombre.
- Démontrer une prise de contrôle de sous-domaine avec une preuve acceptable et non nuisible.
- Rédiger un rapport de trouvaille de reconnaissance dont l'impact est argumenté et la sévérité justifiée.
Pourquoi c'est important en bug bounty
La reconnaissance décide où tu passes tes heures. Sur un programme ouvert depuis des années, l'application principale a été testée par des centaines de chercheurs. Les trouvailles payées se cachent souvent sur un actif oublié : préproduction, ancienne version d'API, tableau de bord interne exposé, sauvegarde restée dans un répertoire public.
Certaines trouvailles relèvent directement de la recon : secrets exposés dans du JavaScript, prises de contrôle de sous-domaine, sauvegardes ou fichiers de configuration accessibles, consoles d'administration sans authentification. Leur sévérité va de « informatif » (une clé publique restreinte) à « critique » (une clé d'API serveur donnant accès en écriture à l'infrastructure).
Ce qui distingue un rapport payé d'un rapport fermé :
- Le périmètre : un actif hors scope ou un service tiers non couvert est fermé sans discussion, parfois avec une pénalité de réputation.
- L'impact démontré : « j'ai trouvé une clé » ne suffit pas. Il faut établir, de façon minimale, que la clé est valide et ce qu'elle permet.
- La nuance : les rapports automatiques (liste de sous-domaines, en-têtes manquants, versions de logiciels sans exploitabilité) sont presque toujours classés informatifs ou doublons.
Le mécanisme
La reconnaissance repose sur un constat simple : une organisation expose beaucoup plus d'informations qu'elle ne le pense, et ces informations sont dispersées dans des sources que tu peux consulter sans jamais toucher la cible. Chaque certificat TLS émis est publié dans des journaux de Certificate Transparency (RFC 6962). Chaque résolution DNS observée par des capteurs alimente des bases de DNS passif. Chaque page archivée par la Wayback Machine conserve d'anciennes URL, paramètres et fichiers JavaScript.
La méthode consiste à transformer ce volume brut en un petit nombre d'hypothèses testables, en filtrant à chaque étape :
Étage 1 : lire la politique comme un contrat
La politique est un document juridique autant que technique. Relève systématiquement :
| Élément | Exemple | Conséquence |
|---|---|---|
| Wildcard | *.acme.test | Tous les sous-domaines appartenant à Acme sont inclus, sauf exclusion explicite |
| Exclusion | pay.acme.test, status.acme.test | Aucun test, même passif poussé, même si la faille semble évidente |
| Actif tiers | acme.zendesk.example, CDN, SaaS | En général hors périmètre : la plateforme tierce a son propre programme |
| Règles de débit | « 5 requêtes par seconde maximum » | Configure tes outils en conséquence dès le départ |
| En-tête requis | X-Bug-Bounty: ton-pseudo | Permet à l'équipe de distinguer ton trafic d'une attaque |
| Types exclus | DoS, ingénierie sociale, rapports de scanners | Ne perds pas de temps sur ces catégories |
Un wildcard couvre les actifs de l'organisation. Un sous-domaine blog.acme.test dont le CNAME pointe vers une plateforme d'hébergement tierce reste dans le scope pour une prise de contrôle (c'est le DNS d'Acme qui est mal configuré), mais tester les failles du logiciel de la plateforme elle-même ne l'est pas.
Étage 2 : énumération passive
L'énumération passive n'envoie aucun paquet vers l'infrastructure de la cible.
## Journaux Certificate Transparency (crt.sh), %25 = caractère joker encodé
curl -s "https://crt.sh/?q=%25.acme.test&output=json" | jq -r '.[].name_value' | sort -u
## Agrégateurs de sources passives
subfinder -d acme.test -all -silent -o passif.txt
## URL historiques (archives)
waybackurls acme.test | sort -u > archives.txtLes certificats révèlent des noms comme staging-api.acme.test ou jenkins.int.acme.test. Les archives révèlent d'anciens endpoints (/api/v1/), des paramètres oubliés et des fichiers JavaScript disparus du site actuel mais parfois toujours servis.
Étage 3 : énumération active
L'énumération active contacte directement la cible : résolution DNS, sondage HTTP, découverte de contenu. Elle doit être raisonnée :
## Résolution et sondage HTTP, débit limité, en-tête d'identification
httpx -l passif.txt -rl 5 -H "X-Bug-Bounty: ton-pseudo" -title -tech-detect -status-code -o vivants.txt
## Découverte de contenu ciblée, liste adaptée à la technologie détectée
ffuf -u https://app.acme.test/FUZZ -w spring-endpoints.txt -rate 5 -H "X-Bug-Bounty: ton-pseudo" -mc 200,301,302,401,403Une liste de 200 000 mots lancée à pleine vitesse contre la production est inutile et dangereuse. Une liste de 300 chemins adaptés à la technologie (par exemple les endpoints Actuator d'un backend Spring) produit plus de résultats sans déclencher le WAF ni dégrader le service.
Étage 4 : cartographier les fonctionnalités
Un hôte vivant n'est pas une cible : une fonctionnalité l'est. Navigue dans l'application avec ton proxy d'interception (Burp Suite, Caido) en tâche de fond, crée tes comptes de test, et note chaque fonctionnalité sensible : import de fichier, export, partage, invitation, paiement, gestion des rôles, webhooks.
Analyse des fichiers JavaScript. Les applications monopages embarquent souvent toute la carte de l'API côté client :
// Extrait d'un bundle minifié de app.acme.test
const API = {
listInvoices: "/api/v2/invoices",
exportAll: "/api/v2/admin/export", // route admin visible par tous
legacyUser: "/api/v1/users/:id", // ancienne version encore routée ?
};
const STRIPE_PK = "pk_live_EXEMPLE_PUBLIQUE"; // clé publique : normale
const MAPS_KEY = "AIzaEXEMPLE_NAVIGATEUR"; // clé navigateur : à vérifier (restrictions)La distinction entre clés est essentielle :
| Type | Exemple de préfixe | Exposition côté client |
|---|---|---|
| Clé publiable de paiement | pk_live_ | Prévue pour le navigateur, non signalable |
| Clé navigateur de cartographie | AIza... | Normale si restreinte par référent et par API |
| Clé secrète de paiement | sk_live_ | Jamais côté client : critique |
| Identifiants cloud | AKIA... + clé secrète | Jamais côté client : critique |
| Jeton de CI ou de dépôt | ghp_, glpat- | Jamais côté client : critique |
Identifier les technologies : en-têtes (Server, X-Powered-By), cookies (JSESSIONID, laravel_session), pages d'erreur, chemins caractéristiques. La technologie oriente tes hypothèses : une application Spring incite à vérifier /actuator, un backend GraphQL à tester l'introspection et les autorisations par champ.
Étage 5 : prioriser
Classe tes cibles selon trois axes : valeur métier (paiement, données personnelles, administration), surface (nombre de paramètres et de rôles), nouveauté (actif récent, peu testé, ou au contraire legacy oublié). Un sous-domaine admin-old.acme.test qui répond avec une page de connexion personnalisée vaut plus que cinquante pages marketing.
Étage 6 : tests ciblés
Chaque test répond à une hypothèse : « l'API v1 n'applique peut-être pas le contrôle d'accès ajouté en v2 ». C'est ici que tu bascules vers les cours spécialisés (IDOR, SSRF, injection).
Question de réflexion : pourquoi commencer par le passif si l'actif donne des résultats plus frais ?
Parce que le passif est sans risque et sans bruit : il ne déclenche aucune alerte, ne consomme pas ton quota de requêtes et ne peut pas dégrader le service. Il révèle aussi des actifs que l'actif ne trouvera jamais par force brute (noms imprévisibles présents dans un certificat). L'actif sert ensuite à valider et enrichir ce que le passif a révélé, sur une liste déjà filtrée par le périmètre.
Où chercher
- Certificats : noms contenant
dev,staging,uat,old,internal,vpn,jenkins,grafana. - Enregistrements DNS orphelins : CNAME vers un service cloud qui répond par une erreur caractéristique (« bucket inexistant », « aucune application à cette adresse »).
- Fichiers JavaScript : routes admin, versions d'API, noms d'hôtes internes, clés, commentaires de développeurs, fichiers
.map(source maps) qui reconstituent le code source original. - Archives : paramètres disparus de l'interface (
debug=1,redirect=), anciens fichiers d'export. - Fichiers sensibles :
.git/HEAD,.env,backup.zip,db.sql.gz,config.php.bak,/actuator/env. - Signaux faibles : un
403sur/admin(la ressource existe), un401avec un en-têteWWW-Authenticateinhabituel, une page de connexion d'un logiciel tiers non mis à jour.
Méthode de test
- Prépare l'environnement : un fichier de scope à jour, l'en-tête d'identification configuré dans le proxy et dans chaque outil, des débits plafonnés selon la politique.
- Collecte passive : CT, DNS passif, archives. Dédoublonne et filtre immédiatement contre la liste d'exclusions.
- Validation active : résolution, sondage HTTP, capture des titres et technologies.
- Contrôle des CNAME pour la prise de contrôle :
dig +short CNAME blog.acme.test
## acme-blog.hebergeur-statique.example.
dig +short acme-blog.hebergeur-statique.example
## (aucune réponse) puis la page HTTP renvoie l'erreur "site introuvable" du fournisseurUn CNAME orphelin pointe vers une ressource qui n'existe plus chez le fournisseur. Si ce dernier permet à n'importe quel client de réclamer ce nom, un tiers peut servir son propre contenu sous blog.acme.test. Vérifie que le fournisseur est réellement vulnérable (les référentiels communautaires de type « can-i-take-over-xyz » documentent les comportements connus) : beaucoup exigent désormais une vérification de propriété du domaine.
- Analyse du JavaScript : récupère les bundles, cherche les routes et les motifs de secrets, puis vérifie la présence de source maps.
GET /static/js/main.4f2a9c.js.map HTTP/1.1
Host: app.acme.test
X-Bug-Bounty: ton-pseudo- Validation minimale d'un secret : un seul appel en lecture qui confirme la validité (par exemple un appel d'identité du fournisseur de type « qui suis-je »), sans lister de données, sans rien modifier.
- Consigne tout : horodatage, commandes, réponses. Ces traces nourrissent ton rapport et te protègent en cas de contestation.
Variantes et défenses courantes
- Takeover de sous-domaine via CNAME orphelin (stockage objet, hébergement statique, plateformes PaaS), via enregistrement
NSdélégué à une zone supprimée (plus grave : contrôle complet de la sous-zone), ou via enregistrementAvers une IP cloud libérée puis réattribuable. - Secrets dans le JavaScript, dans les source maps, dans un dépôt
.gitexposé, dans une image de conteneur publique, dans les variables d'environnement d'un endpoint de debug. - Sauvegardes exposées : archives générées par un outil d'administration et déposées dans la racine web.
Pourquoi les défenses partielles échouent :
- Supprimer la ressource cloud sans supprimer l'enregistrement DNS est précisément ce qui crée le CNAME orphelin. L'ordre correct est l'inverse : DNS d'abord, ressource ensuite.
- Retirer un secret du code sans le révoquer ne sert à rien : les archives, les caches CDN et l'historique Git le conservent.
- Bloquer l'indexation (
robots.txt) ne protège rien : ce fichier est au contraire une liste de chemins intéressants. - Obfusquer le JavaScript ne cache ni les routes ni les clés : la minification n'est pas un contrôle d'accès.
Question de réflexion : une route admin trouvée dans le JavaScript est-elle une vulnérabilité ?
Non. Connaître l'existence de /api/v2/admin/export n'est pas un problème si le serveur refuse la requête d'un utilisateur standard. La vulnérabilité n'existe que si l'appel avec un compte non privilégié aboutit (contrôle d'accès fonctionnel manquant, BFLA). La découverte est un point de départ pour un test, pas un rapport.
Prouver l'impact sans nuire
Ce que les programmes acceptent généralement :
- Takeover : si la politique l'autorise, réclame la ressource et sers un fichier HTML neutre sur un chemin non devinable (par exemple
/bb-ton-pseudo-7f3a.html) contenant ton pseudo et un texte explicatif. Pas de contenu sur la racine, pas de formulaire, pas de script qui lit les cookies, pas de redirection. Libère ou transfère la ressource selon les instructions de l'équipe. Si la politique interdit la revendication, fournis l'empreinte (CNAME, réponse d'erreur du fournisseur, documentation du comportement). - Secret : un unique appel d'identité ou de lecture des métadonnées du compte. Masque la clé dans le rapport (premiers et derniers caractères seulement).
- Sauvegarde : prouve l'existence et la nature du fichier (taille, en-tête, liste des noms de fichiers d'une archive) sans télécharger ni ouvrir de données personnelles au-delà du strict nécessaire.
Ce qui est interdit : utiliser une clé pour lister des buckets, lire des données clients, créer des ressources ou envoyer des e-mails ; conserver un sous-domaine compromis ; télécharger une base complète « pour prouver ». Arrête-toi dès la preuve obtenue et supprime toute copie locale de données sensibles.
Évaluer la sévérité
| Scénario | Vecteur CVSS 3.1 | Score |
|---|---|---|
| Clé secrète de paiement ou identifiants cloud en écriture dans un bundle JS public | AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N | 9.1 (Critique) |
| Sauvegarde de base de données téléchargeable sans authentification | AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N | 7.5 (Élevé) |
Takeover de blog.acme.test permettant de servir du contenu arbitraire sous le domaine | AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N | 6.1 (Moyen) |
| Clé navigateur non restreinte, seul impact : consommation du quota facturé | AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L | 5.3 (Moyen) |
Quelques nuances. Une clé publiable (pk_live_) ou une clé navigateur correctement restreinte n'a aucun impact de sécurité : ne la signale pas. Le takeover peut monter si des cookies de session sont émis avec Domain=.acme.test ou si le sous-domaine figure dans une liste d'origines de confiance (CORS, redirections OAuth) : l'impact devient alors une prise de compte, à démontrer séparément. La clé non restreinte est souvent requalifiée en faible ou informatif par les programmes, qui appliquent leur propre grille.
Corriger
Secret embarqué côté client : le navigateur ne doit jamais détenir une clé secrète. Le serveur appelle le fournisseur et n'expose qu'une opération contrôlée.
// VULNÉRABLE : la clé secrète part dans le bundle livré à tous les visiteurs
const stripe = Stripe(process.env.NEXT_PUBLIC_STRIPE_SECRET); // préfixe public = exposé
await stripe.refunds.create({ payment_intent: id });// CORRIGÉ : route serveur authentifiée, clé lue uniquement côté serveur
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export async function POST(req) {
const user = await requireUser(req); // authentification
const { orderId } = await req.json();
const order = await db.orders.findFirst({ where: { id: orderId, ownerId: user.id } });
if (!order) return new Response(null, { status: 404 }); // autorisation par objet
await stripe.refunds.create({ payment_intent: order.paymentIntentId });
return new Response(null, { status: 204 });
}Puis révoque la clé exposée et génère-en une nouvelle : la retirer du code ne suffit pas.
Takeover : supprime l'enregistrement DNS avant de détruire la ressource cloud, et surveille en continu les CNAME pointant vers des fournisseurs externes.
Défense en profondeur : inventaire automatisé des actifs exposés (l'organisation doit faire sa propre recon), analyse de secrets dans la CI (pre-commit et pipeline), désactivation des source maps en production ou publication restreinte, règles serveur refusant .git, .env, *.bak, *.sql, endpoints de debug désactivés en production, restrictions par référent et par API sur toutes les clés navigateur.
Erreurs qui font rejeter un rapport
- Tester un actif exclu ou tiers parce qu'il « ressemble » au domaine principal.
- Envoyer une liste de sous-domaines ou de ports ouverts sans vulnérabilité démontrée.
- Signaler une clé publique (
pk_live_, clé navigateur restreinte) comme une fuite de secret. - Annoncer un takeover sans vérification : CNAME vers un fournisseur qui exige une validation de propriété, ou ressource qui existe toujours.
- Abuser d'un secret : lister des données, créer des ressources, garder l'accès. Le rapport sera fermé et le compte potentiellement banni.
- Ignorer les règles de débit : un scan agressif peut entraîner l'exclusion du programme, même avec une vraie trouvaille.
Checklist du hunter
- Scope relu, exclusions et actifs tiers isolés dans un fichier dédié.
- En-tête d'identification et débit configurés dans chaque outil.
- Passif d'abord : CT, DNS passif, archives.
- Actif raisonné : listes adaptées à la technologie, débit plafonné.
- JavaScript et source maps analysés : routes, versions d'API, clés classées publiques ou secrètes.
- CNAME vérifiés contre les comportements documentés des fournisseurs.
- Cibles priorisées par valeur métier, surface et nouveauté.
- Preuve minimale, données masquées, arrêt dès la preuve obtenue.
Pour aller plus loin
- OWASP WSTG v4.2, section « Information Gathering » (WSTG-INFO-01 à WSTG-INFO-10).
- OWASP WSTG v4.2, « Test for Subdomain Takeover » (WSTG-CONF-10).
- RFC 6962 : Certificate Transparency.
- CWE-200 : Exposure of Sensitive Information to an Unauthorized Actor ; CWE-798 : Use of Hard-coded Credentials.
- OWASP Secrets Management Cheat Sheet.
- PortSwigger Web Security Academy, « Information disclosure vulnerabilities ».