Remote Code Execution via Python __import__() - MMACTF 2016 Tsurai Web 300 writeup
Sep 5, 2016 •
ctf
Manipulating Python's __import__() statement to import attacker controlled modules (MMACTF 2016 web 100 writeup)
Introduction
After a successful completion of MongoDB NoSQL injection(Web 100), I moved on to a more challenging question, which is tsuari web, a 300 point problem.
Challenge
A quick look at the challenge tells us that there are options to register
, login
and also upload files
of any type to the server via image upload (Never more interesting). We are also given the source code of the website which seems to be written in Flask. A quick peek at the code reveals some interesting information:
Challenge URL: http://tweb.chal.ctf.westerns.tokyo
File: app.py
There are several interesting things to note here:
1) You can see an __import__()
call whose input is basically the MD5 hash of our username. If there were no hashing, this is a direct code execution if we can control the username but thats not the case here.
2) There is a file named userhash.py
, where userhash is the MD5(username)
which is where user informations is saved (or I guess that’s where it is stored).
3) There is a directory named userhash/
where userhash is again the MD5(username)
where the uploaded files of each user is saved.
4) We can upload any kinds of files to the server where filename can be controlled by us or server saves the files inside the userhash/
directory with the exam name with which we upload it (There are some client side protections but can easily be bypassed using burp). Interesting !
Solution
Now it is obvious on how to exploit the scenario but that was not the case when I was solving it. If you look at the way how __import__()
works, you can see that it tries to import the __init__.py
from the function which we imported. So how can we make use of this here ?
1) Upload a file named __init__.py
to the userhash/
directory. So next time when the config = __import__(h(session.get('username')))
is loaded, instead of userhash.py
, the import will actually execute the init file we uploaded !
2) return render_template('albums.html', msg="Hello, {} !".format(session.get('username')), imgs=config.imgs)
tells us that the names of the images are taken from an array named imgs[]
and is shown to the user when he logs in.
3) So by uploading an __init__.py
we essentially control the config and inturn the config.imgs
, so what if we can execute commands and save the output as image names ? Even through image doesn’t exist, the application still shows us the output this way.
After messing around couple of times, here is the final __init__.py
I uploaded to get the flag:
References
These are some of the awesome references which came in handy during solving this problem:
- The import system - python 3.5.2 Documentation
Anirudh Anand
Product Security ♥ | CTF - @teambi0s | Security Trainer - @7asecurity | certs - eWDP, OSCP, OSWE