Bug Bounty Academy

Cours · classe de vulnérabilité

Méthodologie de reconnaissance

Transformer un scope en carte d'attaque priorisée, sans bruit inutile.

~20 min de lecture

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 :

MÉTHODOLOGIE RECONEntonnoir de reconnaissance
💡 Mécanisme clé : Chaque étage réduit le volume et augmente la précision. Le périmètre conditionne tout le reste : un actif hors scope ne doit jamais entrer dans l'entonnoir.

Étage 1 : lire la politique comme un contrat

La politique est un document juridique autant que technique. Relève systématiquement :

ÉlémentExempleConséquence
Wildcard*.acme.testTous les sous-domaines appartenant à Acme sont inclus, sauf exclusion explicite
Exclusionpay.acme.test, status.acme.testAucun test, même passif poussé, même si la faille semble évidente
Actif tiersacme.zendesk.example, CDN, SaaSEn 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 requisX-Bug-Bounty: ton-pseudoPermet à l'équipe de distinguer ton trafic d'une attaque
Types exclusDoS, ingénierie sociale, rapports de scannersNe 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.txt

Les 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,403

Une 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 :

TypeExemple de préfixeExposition côté client
Clé publiable de paiementpk_live_Prévue pour le navigateur, non signalable
Clé navigateur de cartographieAIza...Normale si restreinte par référent et par API
Clé secrète de paiementsk_live_Jamais côté client : critique
Identifiants cloudAKIA... + clé secrèteJamais côté client : critique
Jeton de CI ou de dépôtghp_, 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 403 sur /admin (la ressource existe), un 401 avec un en-tête WWW-Authenticate inhabituel, une page de connexion d'un logiciel tiers non mis à jour.

Méthode de test

  1. 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.
  2. Collecte passive : CT, DNS passif, archives. Dédoublonne et filtre immédiatement contre la liste d'exclusions.
  3. Validation active : résolution, sondage HTTP, capture des titres et technologies.
  4. 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 fournisseur

Un 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.

  1. 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
  1. 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.
  2. 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 NS délégué à une zone supprimée (plus grave : contrôle complet de la sous-zone), ou via enregistrement A vers une IP cloud libérée puis réattribuable.
  • Secrets dans le JavaScript, dans les source maps, dans un dépôt .git exposé, 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énarioVecteur CVSS 3.1Score
Clé secrète de paiement ou identifiants cloud en écriture dans un bundle JS publicAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N9.1 (Critique)
Sauvegarde de base de données téléchargeable sans authentificationAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N7.5 (Élevé)
Takeover de blog.acme.test permettant de servir du contenu arbitraire sous le domaineAV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N6.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:L5.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 ».