
Présentation d’un write-up de résolution du challenge « Web – OctogoneBoobaKaarris » de la BreizhCTF 2019.
Durant la nuit du 12 au 13/04/2018 se déroulait la BreizhCTF 2019 sous forme d’un CTF Jeopardy. Ayant eu l’occasion et le temps d’y participer avec quelques collègues et amis, voici un write-up de résolution d’un des challenges.
- Catégorie : Web
- Nom : OctogoneBoobaKaarris
- Description : N/A
- URL : http://ctf.bzh:20006 / http://10.50.254.254:20006
- Points : 75
tl;dr; : Analyser un code source PHP pour satisfaire toutes les conditions (variables GET, POST, typage, valeurs) afin d’afficher le flag.

L’URL du challenge révèle un fichier PHP interprété, dont le code source est directement affiché (highlight_file()) :

<?php
highlight_file(__FILE__);
error_reporting(0);
if($_REQUEST){
foreach ($_REQUEST as $key => $value) {
if(preg_match('/[a-zA-Z]/i', $value)) die('<center><b>booba vs kaaris... #tristesse</b></center>');
}
}
if($_SERVER){
if(preg_match('/octogone|flag|sans_regles/i', $_SERVER['QUERY_STRING'])) die('<center><b>booba vs kaaris... #tristesse</b></center>');
}
if(isset($_GET['octogone'])){
if(!(substr($_GET['octogone'], 32) === md5($_GET['octogone']))){
die('<center><b>booba vs kaaris... #tristesse</b></center>');
}else{
if(preg_match('/viens_pas_a_12_cette_fois$/', $_GET['sans_regles']) && $_GET['sans_regles'] !== 'Le dopage sera interdit bien evidemment et viens pas a 12 cette fois'){
$getflag = file_get_contents($_GET['flag']);
}
if(isset($getflag) && $getflag === '#jaiMalAMaFrance'){
include 'flag.php';
echo $flag;
}else die('<center><b>booba vs kaaris... #tristesse</b></center>');
}
}
?>
A la lecture de ce code source, on voit clairement qu’il nous faut satisfaire l’ensemble des conditions pour que le fichier « flag.php » soit inclus et que le flag soit affiché :
if(isset($getflag) && $getflag === '#jaiMalAMaFrance'){
include 'flag.php';
echo $flag;
}else die('<center><b>booba vs kaaris... #tristesse</b></center>');
Pour faciliter l’avancement de résolution de ce challenge, et notamment la validation de chaque condition, dupliquons le code source visible dans un fichier « octogone.php » dans notre Apache/XAMP local.
Numérotons les erreurs (booba vs kaaris… #tristesse), ajoutons quelques « echo » et réactivons les erreurs relatives à l’exécution de ce code pour commencer :

On remarque qu’en premier lieu, toutes les variables « $_REQUEST » (donc tous les $_GET et $_POST) sont contrôlés dans une boucle afin de vérifier qu’aucun caractère alpha (lower/upper) ne se trouve dans leurs valeurs :
if($_REQUEST){
foreach ($_REQUEST as $key => $value) {
if(preg_match('/[a-zA-Z]/i', $value)) die('<center><b>1 booba vs kaaris... #tristesse</b></center>');
}
}

Nous sommes donc contraints de faire figurer que des valeurs autres que lower-alpha / upper-alpha pour passer cette condition et ne pas déclencher le premier « die() ».
La condition suivante s’assure que les chaînes « octogone », « flag » ou « sans_regles » ne figurent pas parmi les paramètres GET via la variable « $_SERVER[‘QUERY_STRING’] ».

Mais par la suite, le script nécessite la variable « $_GET[‘octogone’] »… Ça semble contradictoire, non?
Affichons dans le script les valeurs de $_SERVER[‘QUERY_STRING’], $_GET[« octogone »] et observons :

$_SERVER[‘QUERY_STRING’] preg_match
La seconde condition détecte en effet l’utilisation du mot « octogone » en paramètre GET, pourtant nous avons besoin de ce nom de variable par la suite. Le second « die() » est donc bloquant.
Tâchons de contourner cette analyse de $_SERVER[‘QUERY_STRING’] en URL-encodant un caractère du nom de la variable GET :

$_SERVER[‘QUERY_STRING’] analysis
Parfait ! En URL-encodant un des caractères du nom de la variable GET « octogone », on contourne le test réalisé sur $_SERVER[‘QUERY_STRING’], et la variable $_GET[« octogone »] existe bel et bien.
En effet, si l’on compare pour les URLs suivantes :
http://127.0.0.1/breizhctf/octogone.php?octogone=1337
$_SERVER['QUERY_STRING'] === "octogone=1337"
$_GET["octogone"] === "1337"
http://127.0.0.1/breizhctf/octogone.php?octo%67one=1337
$_SERVER['QUERY_STRING'] === "octo%67one=1337"
$_GET["octogone"] === "1337"
La variable $_SERVER[‘QUERY_STRING’] n’est pas « url_decode() » automatiquement contrairement aux variables $_GET, ce que la documentation confirme :

La condition suivante, maintenant que la variable $_GET[« octogone »] existe, vérifie que le substr() à partir du 32ème caractère de cette même variable, est strictement égal au md5() global de la valeur de la variable elle-même (!?).
Brisons cette condition en trans-typant en tant que tableau la variable $_GET[« octogone »] :
http://127.0.0.1/breizhctf/octogone.php?octo%67one[]=1337

En ayant réactivé les erreurs (erreur_reporting(0) commenté), PHP devient tout de suite plus verbeux, mais ce ne sont que des « Warning », donc la condition est passée ! Le dernier « die() » est déclenché.
Condition suivante, la valeur de la variable $_GET[« sans_regles »] doit se terminer par « viens_pas_a_12_cette_fois » sans être strictement égale à « Le dopage sera interdit bien evidemment et viens pas a 12 cette fois« .
Aïe, cette condition risque de nous donner du fil à retordre car « viens_pas_a_12_cette_fois » est composé de caractère alpha, ce qui rentre en conflit avec la toute première condition et le premier die()…

Pour cette étape, de nombreuses tentatives infructueuses d’encodage dans tous les sens de la valeur « viens_pas_a_12_cette_fois » ont été faites, en vain…
- URL-encode
- Double-URL-encode
- Octal-Encode
- Long Unicode
- Etc.
Aucun de ces encodages ne permettait de contourner la première vérification-condition sur l’expression régulière « /[a-zA-Z]/i ». Même l’URL-encodage (donc en hexadécimal) contenait des caractères A-F :
http://127.0.0.1/breizhctf/octogone.php?octo%67one[]=1337&sans_%72egles=%76%69%65%6e%73%5f%70%61%73%5f%61%5f%31%32%5f%63%65%74%74%65%5f%66%6f%69%73
Mais, depuis tout à l’heure, nous ne jouons qu’avec les paramètres GET ! Alors que ce premier contrôle est réalisé sur $_REQUEST !
$_REQUEST regroupe à la fois les paramètres $_GET et $_POST. Que se passe t’il si un même nom de variable est utilisé à la fois en GET et en POST ? Lequel prend le dessus ? Un petit PoC s’impose :

Jackpot ! Si une même variable en GET et en POST est déclarée (avec une valeur différente), $_REQUEST privilégie la valeur POST !
On est donc à présent capable de contourner la condition :

Dernière étape, maintenant que cette ultime condition est contournée, il nous faut valuer la variable $getflag avec la chaîne « #jaiMalAMaFrance« .
Pour valuer cette valeur, un « file_get_contents() » est réalisé sur la valeur de $_GET[« flag »]. SSRF ?
Plusieurs tentatives de SSRF en passant l’URL d’un serveur tiers (http://attacker.com/flag, contenant juste « #jaiMalAMaFrance ») n’ont rien donné.
La piste du « php://input » n’était pas exploitable, car « php://input » récupère son « input » depuis les données POST, or nous en avons déjà défini, donc ça ne pourra par être strictement égale à « #jaiMalAMaFrance ».
Il reste le mode brute/raw, via le wrapper data://, testons :
http://127.0.0.1/breizhctf/octogone.php?octo%67one[]=1337&sans_%72egles=viens_pas_a_12_cette_fois&fl%61g=data:text/plain,%23jaiMalAMaFrance
Données POST :
octo%67one=1337&sans_%72egles=1337&fl%61g=1337

Parfait ! Il ne reste plus qu’à rejouer le tout sur le challenge, soit la requête finale :
POST /index.php?oct%6fgone[]=1337&sans_%72egles=viens_pas_a_12_cette_fois&fl%61g=data:text/plain,%23jaiMalAMaFrance HTTP/1.1
Host: 10.50.254.254:20006
User-Agent: Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.04
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 46
Connection: close
Upgrade-Insecure-Requests: 1
sans_%72egles=1337&fl%61g=1337&oct%6fgone=1337


Flag :
BREIZHCTF{un_octogone_sans_arbitre_sans_règles…#jaimalamafrance}
Ce challenge nous a bien occupé une bonne partie de la nuit, surtout qu’à 10 minutes de la fin nous n’étions que la seconde équipe à le réussir :

Chapeau à Estelle, Martin, et Brian (Samy) pour celui-ci !
Merci à toute l’équipe de la BreizhCTF pour l’organisation et la qualité des challenges !
Salutations à toute l’équipe, on remet ça quand vous voulez // Gr3etZ