{"repro_id":"REPRO-2026-00132","version":2,"title":"ShowDoc Unauthenticated File Upload RCE via deprecated ThinkPHP syntax","repro_type":"security","status":"published","severity":"critical","cvss_score":9.4,"description":"ShowDoc versions before 2.8.7 contain an unrestricted file upload vulnerability. Due to deprecated ThinkPHP 3.1 syntax ($upload->allowExts instead of $upload->exts), attackers bypass extension checks using angle bracket tricks (e.g., test.<>php) to upload PHP webshells. The endpoint /index.php?s=/home/page/uploadImg is accessible without authentication.","root_cause":"## Summary\nShowDoc v2.8.2 is vulnerable to unauthenticated file upload leading to code execution. The real endpoint `POST /index.php?s=/home/page/uploadImg` accepts a multipart file named `test.<>php`, stores it under `Public/Uploads`, and the uploaded PHP is executable. The bug is caused by deprecated ThinkPHP syntax (`$upload->allowExts`) being ignored, so extension allowlisting is not enforced in the effective upload path.\n\n## Impact\n- **Package/component affected:** `showdoc/showdoc`, `PageController::uploadImg()` and ThinkPHP upload handling (`ThinkPHP Upload->upload()`)\n- **Affected versions:** ShowDoc before 2.8.7 (runtime validated on v2.8.2)\n- **Risk level:** Critical\n- **Consequences:**\n  - Unauthenticated attacker can upload executable PHP\n  - Uploaded file is reachable from web path (`/Public/Uploads/...`)\n  - Remote code execution in application context\n\n## Root Cause\nIn vulnerable code, `uploadImg()` configures ThinkPHP upload with:\n- `$upload->allowExts = array('jpg','gif','png','jpeg');`\n- then `$info = $upload->upload();`\n\nOn ThinkPHP 3.2+, `allowExts` is deprecated/ignored and the active key is `exts`. Because of this mismatch, intended extension restrictions are ineffective in the vulnerable path. A crafted filename (`test.<>php`) bypasses the simple `.php` substring check and results in a saved `.php` payload.\n\n**Fix commits:**\n- `fb77dd4db88dc23f5e570fc95919ee882aca520a`: replaces `allowExts` with `exts` (upload blocked)\n- `e1cd02a3f98bb227c0599e7fa6b803ab1097597f`: adds early `return false` in `uploadImg()` (endpoint disabled)\n\n## Reproduction Steps\n1. Run `repro/reproduction_steps.sh`.\n2. The script:\n   - clones ShowDoc and checks out three revisions: vulnerable `v2.8.2`, fixed `fb77dd4`, fixed `e1cd02a`\n   - builds Docker runtime images (`php:7.4-apache`) for each revision\n   - initializes each instance via `install/non_interactive.php`\n   - sends real multipart POST to `/index.php?s=/home/page/uploadImg` with filename `test.<>php`\n   - for vulnerable revision, requests the uploaded PHP and verifies execution marker\n   - for fixed revisions, verifies upload rejection/disable behavior and absence of uploaded PHP\n3. Expected evidence:\n   - vulnerable upload response contains success JSON + uploaded `.php` URL\n   - vulnerable execution response contains `RCE_OK_1e08f13e8e4695e97fef6d9de3665be4`\n   - fb77 shows `上传文件后缀不允许` and no uploaded PHP\n   - e1cd returns empty response body and no uploaded PHP\n\n## Evidence\n- Execution logs:\n  - `logs/reproduction_run8.log`\n  - `logs/reproduction_run9.log`\n- Vulnerable artifacts:\n  - `repro/runtime_artifacts/http/vuln/upload_response.txt`\n  - `repro/runtime_artifacts/http/vuln/uploaded_php_files.txt`\n  - `repro/runtime_artifacts/http/vuln/webshell_exec_response.txt`\n  - `repro/runtime_artifacts/http/vuln/container.log`\n  - `repro/runtime_artifacts/http/vuln/controller_lines.txt`\n  - `repro/runtime_artifacts/http/vuln/controller_upload_flow.txt`\n- Fixed artifacts:\n  - `repro/runtime_artifacts/http/fb77/upload_response.txt`\n  - `repro/runtime_artifacts/http/fb77/uploaded_php_files.txt`\n  - `repro/runtime_artifacts/http/fb77/container.log`\n  - `repro/runtime_artifacts/http/e1cd/upload_response.txt`\n  - `repro/runtime_artifacts/http/e1cd/uploaded_php_files.txt`\n  - `repro/runtime_artifacts/http/e1cd/container.log`\n- Revision evidence:\n  - `repro/runtime_artifacts/http/vuln/revision.txt`\n  - `repro/runtime_artifacts/http/fb77/revision.txt`\n  - `repro/runtime_artifacts/http/e1cd/revision.txt`\n- Summary artifact:\n  - `repro/runtime_artifacts/http/summary.txt`\n\n## Recommendations / Next Steps\n- Upgrade to a fixed release (>= 2.8.7) or include both fixes (`fb77dd4` and `e1cd02a`).\n- Keep upload endpoint authenticated or disabled if not required.\n- Enforce strict extension/MIME checks with current framework API (`exts`) and add server-level execution restrictions in upload directories.\n- Add regression tests for tricky filenames like `test.<>php` and other extension obfuscation inputs.\n\n## Additional Notes\n- **Idempotency:** confirmed. `repro/reproduction_steps.sh` was run twice consecutively (`reproduction_run8.log`, `reproduction_run9.log`) with successful reproduction and consistent vulnerable-vs-fixed behavior.\n- **Limitation:** requires Docker availability in the execution environment.","cve_id":"CVE-2025-0520","cwe_id":"CWE-434","source_url":"https://github.com/advisories/GHSA-6jmr-r7p6-f5wr","package":{"name":"showdoc/showdoc","ecosystem":"github","affected_versions":"ShowDoc versions before 2.8.7 (commonly referenced as <=2.8.6); Vulhub lab uses 2.8.2"},"reproduced_at":"2026-04-14T14:59:24.402943+00:00","duration_secs":8381.0,"tool_calls":526,"handoffs":4,"vulnerable_version_variant_outcome":"unknown","fix_bypass_outcome":"unknown","variant_disclosure_state":"unknown","quality":{"idempotent_verified":false,"community_verifications":0},"published_at":"2026-04-14T14:59:26.284085+00:00","retracted":false,"artifacts":[{"path":"repro/rca_report.md","filename":"rca_report.md","size":4336,"category":"analysis"},{"path":"repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":10273,"category":"reproduction_script"},{"path":"coding/proposed_fix.diff","filename":"proposed_fix.diff","size":917,"category":"patch"},{"path":"bundle/ticket.json","filename":"ticket.json","size":1975,"category":"other"},{"path":"bundle/repro/judge_review.json","filename":"judge_review.json","size":1029,"category":"other"},{"path":"bundle/AGENTS.repro.md","filename":"AGENTS.repro.md","size":770,"category":"documentation"},{"path":"bundle/ticket.md","filename":"ticket.md","size":1542,"category":"ticket"},{"path":"repro/runtime_manifest.json","filename":"runtime_manifest.json","size":1085,"category":"other"},{"path":"repro/runtime_artifacts/library_api_output.txt","filename":"library_api_output.txt","size":2361,"category":"other"},{"path":"repro/runtime_artifacts/http/fb77/container_id.txt","filename":"container_id.txt","size":65,"category":"other"},{"path":"repro/runtime_artifacts/http/fb77/controller_lines.txt","filename":"controller_lines.txt","size":207,"category":"other"},{"path":"repro/runtime_artifacts/http/fb77/healthcheck_http_code.txt","filename":"healthcheck_http_code.txt","size":4,"category":"other"},{"path":"repro/runtime_artifacts/http/fb77/container.log","filename":"container.log","size":809,"category":"log"},{"path":"repro/runtime_artifacts/http/fb77/upload_response.txt","filename":"upload_response.txt","size":1742,"category":"other"},{"path":"repro/runtime_artifacts/http/fb77/revision.txt","filename":"revision.txt","size":41,"category":"other"},{"path":"repro/runtime_artifacts/http/fb77/webshell_exec_response.txt","filename":"webshell_exec_response.txt","size":0,"category":"other"},{"path":"repro/runtime_artifacts/http/fb77/controller_upload_flow.txt","filename":"controller_upload_flow.txt","size":1943,"category":"other"},{"path":"repro/runtime_artifacts/http/fb77/uploaded_php_files.txt","filename":"uploaded_php_files.txt","size":0,"category":"other"},{"path":"repro/runtime_artifacts/http/fb77/install_output.txt","filename":"install_output.txt","size":20,"category":"other"},{"path":"repro/runtime_artifacts/http/fb77/request_metadata.txt","filename":"request_metadata.txt","size":115,"category":"other"},{"path":"repro/runtime_artifacts/http/summary.txt","filename":"summary.txt","size":745,"category":"other"},{"path":"repro/runtime_artifacts/http/vuln/container_id.txt","filename":"container_id.txt","size":65,"category":"other"},{"path":"repro/runtime_artifacts/http/vuln/controller_lines.txt","filename":"controller_lines.txt","size":212,"category":"other"},{"path":"repro/runtime_artifacts/http/vuln/healthcheck_http_code.txt","filename":"healthcheck_http_code.txt","size":4,"category":"other"},{"path":"repro/runtime_artifacts/http/vuln/container.log","filename":"container.log","size":937,"category":"log"},{"path":"repro/runtime_artifacts/http/vuln/upload_response.txt","filename":"upload_response.txt","size":459,"category":"other"},{"path":"repro/runtime_artifacts/http/vuln/revision.txt","filename":"revision.txt","size":41,"category":"other"},{"path":"repro/runtime_artifacts/http/vuln/webshell_exec_response.txt","filename":"webshell_exec_response.txt","size":213,"category":"other"},{"path":"repro/runtime_artifacts/http/vuln/controller_upload_flow.txt","filename":"controller_upload_flow.txt","size":1948,"category":"other"},{"path":"repro/runtime_artifacts/http/vuln/uploaded_php_files.txt","filename":"uploaded_php_files.txt","size":58,"category":"other"},{"path":"repro/runtime_artifacts/http/vuln/install_output.txt","filename":"install_output.txt","size":20,"category":"other"},{"path":"repro/runtime_artifacts/http/vuln/request_metadata.txt","filename":"request_metadata.txt","size":115,"category":"other"},{"path":"repro/runtime_artifacts/http/e1cd/container_id.txt","filename":"container_id.txt","size":65,"category":"other"},{"path":"repro/runtime_artifacts/http/e1cd/controller_lines.txt","filename":"controller_lines.txt","size":239,"category":"other"},{"path":"repro/runtime_artifacts/http/e1cd/healthcheck_http_code.txt","filename":"healthcheck_http_code.txt","size":4,"category":"other"},{"path":"repro/runtime_artifacts/http/e1cd/container.log","filename":"container.log","size":808,"category":"log"},{"path":"repro/runtime_artifacts/http/e1cd/upload_response.txt","filename":"upload_response.txt","size":347,"category":"other"},{"path":"repro/runtime_artifacts/http/e1cd/revision.txt","filename":"revision.txt","size":41,"category":"other"},{"path":"repro/runtime_artifacts/http/e1cd/webshell_exec_response.txt","filename":"webshell_exec_response.txt","size":0,"category":"other"},{"path":"repro/runtime_artifacts/http/e1cd/controller_upload_flow.txt","filename":"controller_upload_flow.txt","size":1903,"category":"other"},{"path":"repro/runtime_artifacts/http/e1cd/uploaded_php_files.txt","filename":"uploaded_php_files.txt","size":0,"category":"other"},{"path":"repro/runtime_artifacts/http/e1cd/install_output.txt","filename":"install_output.txt","size":20,"category":"other"},{"path":"repro/runtime_artifacts/http/e1cd/request_metadata.txt","filename":"request_metadata.txt","size":115,"category":"other"},{"path":"repro/runtime_artifacts/library_api_success.txt","filename":"library_api_success.txt","size":5,"category":"other"},{"path":"repro/validation_verdict.json","filename":"validation_verdict.json","size":677,"category":"other"},{"path":"logs/clone_showdoc.log","filename":"clone_showdoc.log","size":58,"category":"log"},{"path":"logs/reproduction_run1.log","filename":"reproduction_run1.log","size":308,"category":"log"},{"path":"logs/docker_build_vuln.log","filename":"docker_build_vuln.log","size":871,"category":"log"},{"path":"logs/reproduction_run6.log","filename":"reproduction_run6.log","size":308,"category":"log"},{"path":"logs/reproduction_run3.log","filename":"reproduction_run3.log","size":308,"category":"log"},{"path":"logs/docker_build_fb77.log","filename":"docker_build_fb77.log","size":871,"category":"log"},{"path":"logs/git_checkout_vuln.log","filename":"git_checkout_vuln.log","size":577,"category":"log"},{"path":"logs/git_checkout_e1cd.log","filename":"git_checkout_e1cd.log","size":636,"category":"log"},{"path":"logs/reproduction_run4.log","filename":"reproduction_run4.log","size":308,"category":"log"},{"path":"logs/reproduction_run7.log","filename":"reproduction_run7.log","size":308,"category":"log"},{"path":"logs/reproduction_run2.log","filename":"reproduction_run2.log","size":308,"category":"log"},{"path":"logs/git_checkout_fb77.log","filename":"git_checkout_fb77.log","size":635,"category":"log"},{"path":"logs/reproduction_run5.log","filename":"reproduction_run5.log","size":308,"category":"log"},{"path":"logs/phpserver_vuln.log","filename":"phpserver_vuln.log","size":973,"category":"log"},{"path":"logs/fetch_showdoc.log","filename":"fetch_showdoc.log","size":0,"category":"log"},{"path":"logs/reproduction_run8.log","filename":"reproduction_run8.log","size":308,"category":"log"},{"path":"logs/reproduction_run9.log","filename":"reproduction_run9.log","size":308,"category":"log"},{"path":"logs/docker_build_e1cd.log","filename":"docker_build_e1cd.log","size":871,"category":"log"},{"path":"logs/vuln_variant_e1cd_install_probe2.log","filename":"vuln_variant_e1cd_install_probe2.log","size":20,"category":"log"},{"path":"logs/vuln_variant_e1cd_login_probe.json","filename":"vuln_variant_e1cd_login_probe.json","size":93,"category":"other"},{"path":"logs/vuln_variant_e1cd_install_probe.log","filename":"vuln_variant_e1cd_install_probe.log","size":20,"category":"log"},{"path":"coding/verify_logs/base/container_id.txt","filename":"container_id.txt","size":65,"category":"other"},{"path":"coding/verify_logs/base/health_http_code.txt","filename":"health_http_code.txt","size":4,"category":"other"},{"path":"coding/verify_logs/base/seed_item_output.txt","filename":"seed_item_output.txt","size":7,"category":"other"},{"path":"coding/verify_logs/base/uploaded_phtml_files.txt","filename":"uploaded_phtml_files.txt","size":60,"category":"other"},{"path":"coding/verify_logs/base/container.log","filename":"container.log","size":937,"category":"log"},{"path":"coding/verify_logs/base/phtml_count.txt","filename":"phtml_count.txt","size":2,"category":"other"},{"path":"coding/verify_logs/base/upload_response.txt","filename":"upload_response.txt","size":451,"category":"other"},{"path":"coding/verify_logs/base/all_uploaded_files.txt","filename":"all_uploaded_files.txt","size":100,"category":"other"},{"path":"coding/verify_logs/base/install_output.txt","filename":"install_output.txt","size":20,"category":"other"},{"path":"coding/verify_logs/base/login_response.txt","filename":"login_response.txt","size":767,"category":"other"},{"path":"coding/verify_logs/build_patched.log","filename":"build_patched.log","size":877,"category":"log"},{"path":"coding/verify_logs/patch_apply.log","filename":"patch_apply.log","size":79,"category":"log"},{"path":"coding/verify_logs/summary.txt","filename":"summary.txt","size":404,"category":"other"},{"path":"coding/verify_logs/build_base.log","filename":"build_base.log","size":874,"category":"log"},{"path":"coding/verify_logs/git_checkout_base.log","filename":"git_checkout_base.log","size":636,"category":"log"},{"path":"coding/verify_logs/git_checkout_patched.log","filename":"git_checkout_patched.log","size":636,"category":"log"},{"path":"coding/verify_logs/patched/container_id.txt","filename":"container_id.txt","size":65,"category":"other"},{"path":"coding/verify_logs/patched/health_http_code.txt","filename":"health_http_code.txt","size":4,"category":"other"},{"path":"coding/verify_logs/patched/seed_item_output.txt","filename":"seed_item_output.txt","size":7,"category":"other"},{"path":"coding/verify_logs/patched/uploaded_phtml_files.txt","filename":"uploaded_phtml_files.txt","size":0,"category":"other"},{"path":"coding/verify_logs/patched/container.log","filename":"container.log","size":937,"category":"log"},{"path":"coding/verify_logs/patched/phtml_count.txt","filename":"phtml_count.txt","size":2,"category":"other"},{"path":"coding/verify_logs/patched/upload_response.txt","filename":"upload_response.txt","size":501,"category":"other"},{"path":"coding/verify_logs/patched/all_uploaded_files.txt","filename":"all_uploaded_files.txt","size":40,"category":"other"},{"path":"coding/verify_logs/patched/install_output.txt","filename":"install_output.txt","size":20,"category":"other"},{"path":"coding/verify_logs/patched/login_response.txt","filename":"login_response.txt","size":767,"category":"other"},{"path":"coding/verify_logs/git_clone.log","filename":"git_clone.log","size":64,"category":"log"},{"path":"coding/verify_fix.sh","filename":"verify_fix.sh","size":6148,"category":"other"},{"path":"coding/summary_report.md","filename":"summary_report.md","size":2442,"category":"documentation"}]}