ECSC 2025¶
Challenge: Flag Szop¶
Tags: web¶
Difficulty: Medium¶
Table of Contents¶
- Solution Overview
- Tools Used
- Initial Reconnaissance
- Vulnerability Analysis
- Exploitation Journey
- Full Solution Script
- Flag
Solution Overview¶
This challenge involves exploiting a PHP type juggling vulnerability, bypassing SSRF protection, and navigating proxy header limitations to upload and execute a PHP webshell.
TL;DR: Exploit MD5 hash comparison with loose equality, use imgproxy SSRF to access localhost-only admin panel, discover that multiple header= parameters don't work in the proxy, combine headers into a single parameter with \r\n separator, upload PHP shell with password+file in one multipart request, execute /var/www/html/readflag binary.
Tools Used¶
- Python 3 with
requestslibrary - curl
- Browser developer tools
Initial Reconnaissance¶
The challenge presents a web application with an image proxy at /imgproxy. Looking at the source code (script.php), we identified the admin panel at /admin with several security checks:
- Accept-Language Check: Must contain "id" (Indonesian)
- IP Address Check: Must be from
127.0.0.1or::1(localhost only) - Password Authentication: Checks MD5 hash against
0e198271987298738473298472398488 - File Upload: After authentication, allows uploading files with preserved extensions
The imgproxy allows us to make requests to internal endpoints:
Vulnerability Analysis¶
1. PHP Type Juggling (Critical)¶
The password check uses loose comparison:
The hash 0e198271987298738473298472398488 starts with 0e followed by only digits. PHP interprets this as scientific notation (0 × 10^...). Any password whose MD5 also starts with 0e followed by digits will equal 0.
Magic password found: 240610708 → MD5: 0e462097431906509019562988736854
2. SSRF via imgproxy¶
The /imgproxy endpoint allows us to make requests to 127.0.0.1, bypassing the IP check. We can authenticate and upload files by proxying through this endpoint.
3. No File Extension Validation¶
The admin panel preserves the original file extension without validation, allowing us to upload executable .php files.
Exploitation Journey¶
Challenge 1: Session Management¶
Problem: After authenticating through the proxy, subsequent requests fail the Accept-Language check.
Analysis: The proxy maintains its own session (visible in cookies), but the backend at 127.0.0.1 has a separate session that we can't access directly. The backend sets $_SESSION['admin'] = true, but this session is server-side only.
Failed Attempts:
- Extracting backend session cookie (not accessible through proxy)
- Using proxy's session for subsequent requests (different domains)
- Accessing
/admindirectly (blocked by IP check)
Solution: We need to do BOTH authentication AND file upload in a SINGLE request!
Challenge 2: Multipart Request with Password and File¶
Idea: Send a multipart POST that contains both:
passfield with value240610708filefield with our PHP shell
Problem: When we tried to add a Content-Type header for multipart:
The Accept-Language check started failing!
Challenge 3: The Proxy Header Bug (Critical Discovery!)¶
Debugging process:
- Simple auth works: ✓
- Adding second header fails: ✗
Discovery: The imgproxy only supports ONE header= parameter! When you add a second one, it ignores or overwrites the first.
Solution: Combine multiple headers into a single parameter using \r\n (URL encoded as %0D%0A):
Final Exploit¶
- Construct multipart body with both password and file:
--boundary
Content-Disposition: form-data; name="pass"
240610708
--boundary
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: application/x-php
<?php system($_GET["c"] ?? "cat /flag*"); ?>
--boundary--
- Combine headers in single parameter:
- Send request through imgproxy:
-
Extract filename from "OK: /img/xxxxx.php" response
-
Execute commands via uploaded shell:
Full Solution Script¶
See solve/exploit.py for the complete exploit script.
Flag¶
ECSC{raccoon_shopping_cart_full_of_flags}