# File Storer

AngstromCTF 2018 - Web.

# AngstromCTF 2018 : File Storer

This challenge was the 7 of the 8 proposed by Angstrom at this contest.

### Challenge

We follow the given link (http://web2.angstromctf.com:8899/) and it shows a login page.

Fig 1 - Login page

Once we create an account and log into it, we see that the application is allowing us to add files through an URL.

Fig 2 - Home

### Exploit

After several tries like Remote File Inclusion, with no good result we tried discovering directories (which also was a hint given further).

So we found a .git/HEAD file, here we go, we extract the whole directory with GitTools (https://github.com/internetwache/GitTools). Now we can simply extract all the informations we can find in the .git directory. As you can see we retrieve informations about commits, the tree and files’ content:

Fig 3 - .git directory

Fig 4 - Informations about the first commit

Fig 5 - Versions and files

We quickly notice that the interesting part of the challenge is the index.py file:

from flask import Flask, request, render_template, abort
import os, requests

class user:
self.files = []
def getPass(self):

users = {}

@app.errorhandler(500)
def custom500(error):
return str(error), 500

@app.route("/", methods=["GET", "POST"])
def mainpage():
if request.method == "POST":
if request.form["action"] == "Login":
if request.form["username"] in users:
return "user does not exist"
elif request.form["action"] == "Signup":
if request.form["username"] not in users:
else:
return "user already exists"
elif request.form["action"] == "Add File":
return render_template("loggedout.html")

#beta feature for viewing info about other users - still testing
val = getattr(users[username], request.form['field'], None)
if val != None: return val
else: return "error"

@app.route("/files/<path:file>", methods=["GET"])
def getFile(file):
if "index.py" in file:

if request.form['url'][-1] == "/": downloadurl = request.form['url'][:-1]
return "file already exists"
first = True
for chunk in file.iter_content(chunk_size=1024*512):
if not first: break
f.write(chunk)
first = False
f.close()

if name == "__main__": app.run(host="0.0.0.0")


I guess you probably noticed the /user/ path which could let us extract informations about User’s attributes. So what we basically tried is to ask for the admin’s password, but hey did not worked as intended because of the __ in front of password :)

Fig 6 - Get username

Fig 7 - Trial for __password

Fig 8 - Trial for getPass function

We did not found how we could abuse it that way to properly assign arguments to the getPass function neither to extract the __password attributes. Now the CTF is over we can see that the wanted solution was to ask for _user__password to get the password :)

But that’s not how we did to get the flag…

After a good night of sleep, we went back on it, and discovered that we could eventually extract content files of the server because when we ask for localhost/etc/passwd it stated the files already exists ! And as shown in index.py’s code, that it because the code checked the file was there, even if we did not upload such a file…

Fig 9 - localhost /etc/passwd

Fig 10 - Oups…

But now the challenge for us was to retrieve that content ! Here we go browsing the /files/ path with a hex encoding of “/” => %2F

Fig 11 - /etc/passwd extracted

Ok that sounds good, we saw in the index.py’s code that the flag we’re looking for is an environment variable :)

Browsing files such as /etc/environment, /etc/profile, /root/.bashrc (which is also ~/.bashrc), /root/.profile, /root/.bash_profile, we supposed the environment variable was set at the docker’s deployment… So what we know is that process’ environment variables are saved in /proc/PID/environ (PID is an integer). And now comes the dirty CTF script to bruteforce the PID !

Fig 12 - Dirty Bruteforce Script

We retrieved the flag :) \o/

Fig 13 - Flag

The author of the challenge was also kinda suprised we successed that way ;)

Fig 14 - kmh11, the author

Flag: actf{2_und3rsc0res_h1des_n0th1ng}

_ACKNAK_