# REPRO-2026-00132: ShowDoc Unauthenticated File Upload RCE via deprecated ThinkPHP syntax ## Summary Status: published Severity: critical CVSS: 9.4 / 10 CWE: CWE-434 (Unrestricted Upload of File with Dangerous Type) Type: security Confidence: Unknown ## Identifiers REPRO ID: REPRO-2026-00132 CVE: CVE-2025-0520 ## Package Name: showdoc/showdoc Ecosystem: github Affected: ShowDoc versions before 2.8.7 (commonly referenced as <=2.8.6); Vulhub lab uses 2.8.2 Fixed: Unknown ## Root Cause ## Summary ShowDoc 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. ## Impact - **Package/component affected:** `showdoc/showdoc`, `PageController::uploadImg()` and ThinkPHP upload handling (`ThinkPHP Upload->upload()`) - **Affected versions:** ShowDoc before 2.8.7 (runtime validated on v2.8.2) - **Risk level:** Critical - **Consequences:** - Unauthenticated attacker can upload executable PHP - Uploaded file is reachable from web path (`/Public/Uploads/...`) - Remote code execution in application context ## Root Cause In vulnerable code, `uploadImg()` configures ThinkPHP upload with: - `$upload->allowExts = array('jpg','gif','png','jpeg');` - then `$info = $upload->upload();` On 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. **Fix commits:** - `fb77dd4db88dc23f5e570fc95919ee882aca520a`: replaces `allowExts` with `exts` (upload blocked) - `e1cd02a3f98bb227c0599e7fa6b803ab1097597f`: adds early `return false` in `uploadImg()` (endpoint disabled) ## Reproduction Steps 1. Run `repro/reproduction_steps.sh`. 2. The script: - clones ShowDoc and checks out three revisions: vulnerable `v2.8.2`, fixed `fb77dd4`, fixed `e1cd02a` - builds Docker runtime images (`php:7.4-apache`) for each revision - initializes each instance via `install/non_interactive.php` - sends real multipart POST to `/index.php?s=/home/page/uploadImg` with filename `test.<>php` - for vulnerable revision, requests the uploaded PHP and verifies execution marker - for fixed revisions, verifies upload rejection/disable behavior and absence of uploaded PHP 3. Expected evidence: - vulnerable upload response contains success JSON + uploaded `.php` URL - vulnerable execution response contains `RCE_OK_1e08f13e8e4695e97fef6d9de3665be4` - fb77 shows `上传文件后缀不允许` and no uploaded PHP - e1cd returns empty response body and no uploaded PHP ## Evidence - Execution logs: - `logs/reproduction_run8.log` - `logs/reproduction_run9.log` - Vulnerable artifacts: - `repro/runtime_artifacts/http/vuln/upload_response.txt` - `repro/runtime_artifacts/http/vuln/uploaded_php_files.txt` - `repro/runtime_artifacts/http/vuln/webshell_exec_response.txt` - `repro/runtime_artifacts/http/vuln/container.log` - `repro/runtime_artifacts/http/vuln/controller_lines.txt` - `repro/runtime_artifacts/http/vuln/controller_upload_flow.txt` - Fixed artifacts: - `repro/runtime_artifacts/http/fb77/upload_response.txt` - `repro/runtime_artifacts/http/fb77/uploaded_php_files.txt` - `repro/runtime_artifacts/http/fb77/container.log` - `repro/runtime_artifacts/http/e1cd/upload_response.txt` - `repro/runtime_artifacts/http/e1cd/uploaded_php_files.txt` - `repro/runtime_artifacts/http/e1cd/container.log` - Revision evidence: - `repro/runtime_artifacts/http/vuln/revision.txt` - `repro/runtime_artifacts/http/fb77/revision.txt` - `repro/runtime_artifacts/http/e1cd/revision.txt` - Summary artifact: - `repro/runtime_artifacts/http/summary.txt` ## Recommendations / Next Steps - Upgrade to a fixed release (>= 2.8.7) or include both fixes (`fb77dd4` and `e1cd02a`). - Keep upload endpoint authenticated or disabled if not required. - Enforce strict extension/MIME checks with current framework API (`exts`) and add server-level execution restrictions in upload directories. - Add regression tests for tricky filenames like `test.<>php` and other extension obfuscation inputs. ## Additional Notes - **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. - **Limitation:** requires Docker availability in the execution environment. ## Reproduction Details Reproduced: 2026-04-14T14:59:24.402Z Duration: 8381 seconds Tool calls: 526 Turns: Unknown Handoffs: 4 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00132 pruva-verify CVE-2025-0520 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00132&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00132/artifacts/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2025-0520 - Source: https://github.com/advisories/GHSA-6jmr-r7p6-f5wr ## Artifacts - repro/rca_report.md (analysis, 4336 bytes) - repro/reproduction_steps.sh (reproduction_script, 10273 bytes) - coding/proposed_fix.diff (patch, 917 bytes) - bundle/ticket.json (other, 1975 bytes) - bundle/repro/judge_review.json (other, 1029 bytes) - bundle/AGENTS.repro.md (documentation, 770 bytes) - bundle/ticket.md (ticket, 1542 bytes) - repro/runtime_manifest.json (other, 1085 bytes) - repro/runtime_artifacts/library_api_output.txt (other, 2361 bytes) - repro/runtime_artifacts/http/fb77/container_id.txt (other, 65 bytes) - repro/runtime_artifacts/http/fb77/controller_lines.txt (other, 207 bytes) - repro/runtime_artifacts/http/fb77/healthcheck_http_code.txt (other, 4 bytes) - repro/runtime_artifacts/http/fb77/container.log (log, 809 bytes) - repro/runtime_artifacts/http/fb77/upload_response.txt (other, 1742 bytes) - repro/runtime_artifacts/http/fb77/revision.txt (other, 41 bytes) - repro/runtime_artifacts/http/fb77/webshell_exec_response.txt (other, 0 bytes) - repro/runtime_artifacts/http/fb77/controller_upload_flow.txt (other, 1943 bytes) - repro/runtime_artifacts/http/fb77/uploaded_php_files.txt (other, 0 bytes) - repro/runtime_artifacts/http/fb77/install_output.txt (other, 20 bytes) - repro/runtime_artifacts/http/fb77/request_metadata.txt (other, 115 bytes) - repro/runtime_artifacts/http/summary.txt (other, 745 bytes) - repro/runtime_artifacts/http/vuln/container_id.txt (other, 65 bytes) - repro/runtime_artifacts/http/vuln/controller_lines.txt (other, 212 bytes) - repro/runtime_artifacts/http/vuln/healthcheck_http_code.txt (other, 4 bytes) - repro/runtime_artifacts/http/vuln/container.log (log, 937 bytes) - repro/runtime_artifacts/http/vuln/upload_response.txt (other, 459 bytes) - repro/runtime_artifacts/http/vuln/revision.txt (other, 41 bytes) - repro/runtime_artifacts/http/vuln/webshell_exec_response.txt (other, 213 bytes) - repro/runtime_artifacts/http/vuln/controller_upload_flow.txt (other, 1948 bytes) - repro/runtime_artifacts/http/vuln/uploaded_php_files.txt (other, 58 bytes) - repro/runtime_artifacts/http/vuln/install_output.txt (other, 20 bytes) - repro/runtime_artifacts/http/vuln/request_metadata.txt (other, 115 bytes) - repro/runtime_artifacts/http/e1cd/container_id.txt (other, 65 bytes) - repro/runtime_artifacts/http/e1cd/controller_lines.txt (other, 239 bytes) - repro/runtime_artifacts/http/e1cd/healthcheck_http_code.txt (other, 4 bytes) - repro/runtime_artifacts/http/e1cd/container.log (log, 808 bytes) - repro/runtime_artifacts/http/e1cd/upload_response.txt (other, 347 bytes) - repro/runtime_artifacts/http/e1cd/revision.txt (other, 41 bytes) - repro/runtime_artifacts/http/e1cd/webshell_exec_response.txt (other, 0 bytes) - repro/runtime_artifacts/http/e1cd/controller_upload_flow.txt (other, 1903 bytes) - repro/runtime_artifacts/http/e1cd/uploaded_php_files.txt (other, 0 bytes) - repro/runtime_artifacts/http/e1cd/install_output.txt (other, 20 bytes) - repro/runtime_artifacts/http/e1cd/request_metadata.txt (other, 115 bytes) - repro/runtime_artifacts/library_api_success.txt (other, 5 bytes) - repro/validation_verdict.json (other, 677 bytes) - logs/clone_showdoc.log (log, 58 bytes) - logs/reproduction_run1.log (log, 308 bytes) - logs/docker_build_vuln.log (log, 871 bytes) - logs/reproduction_run6.log (log, 308 bytes) - logs/reproduction_run3.log (log, 308 bytes) - logs/docker_build_fb77.log (log, 871 bytes) - logs/git_checkout_vuln.log (log, 577 bytes) - logs/git_checkout_e1cd.log (log, 636 bytes) - logs/reproduction_run4.log (log, 308 bytes) - logs/reproduction_run7.log (log, 308 bytes) - logs/reproduction_run2.log (log, 308 bytes) - logs/git_checkout_fb77.log (log, 635 bytes) - logs/reproduction_run5.log (log, 308 bytes) - logs/phpserver_vuln.log (log, 973 bytes) - logs/fetch_showdoc.log (log, 0 bytes) - logs/reproduction_run8.log (log, 308 bytes) - logs/reproduction_run9.log (log, 308 bytes) - logs/docker_build_e1cd.log (log, 871 bytes) - logs/vuln_variant_e1cd_install_probe2.log (log, 20 bytes) - logs/vuln_variant_e1cd_login_probe.json (other, 93 bytes) - logs/vuln_variant_e1cd_install_probe.log (log, 20 bytes) - coding/verify_logs/base/container_id.txt (other, 65 bytes) - coding/verify_logs/base/health_http_code.txt (other, 4 bytes) - coding/verify_logs/base/seed_item_output.txt (other, 7 bytes) - coding/verify_logs/base/uploaded_phtml_files.txt (other, 60 bytes) - coding/verify_logs/base/container.log (log, 937 bytes) - coding/verify_logs/base/phtml_count.txt (other, 2 bytes) - coding/verify_logs/base/upload_response.txt (other, 451 bytes) - coding/verify_logs/base/all_uploaded_files.txt (other, 100 bytes) - coding/verify_logs/base/install_output.txt (other, 20 bytes) - coding/verify_logs/base/login_response.txt (other, 767 bytes) - coding/verify_logs/build_patched.log (log, 877 bytes) - coding/verify_logs/patch_apply.log (log, 79 bytes) - coding/verify_logs/summary.txt (other, 404 bytes) - coding/verify_logs/build_base.log (log, 874 bytes) - coding/verify_logs/git_checkout_base.log (log, 636 bytes) - coding/verify_logs/git_checkout_patched.log (log, 636 bytes) - coding/verify_logs/patched/container_id.txt (other, 65 bytes) - coding/verify_logs/patched/health_http_code.txt (other, 4 bytes) - coding/verify_logs/patched/seed_item_output.txt (other, 7 bytes) - coding/verify_logs/patched/uploaded_phtml_files.txt (other, 0 bytes) - coding/verify_logs/patched/container.log (log, 937 bytes) - coding/verify_logs/patched/phtml_count.txt (other, 2 bytes) - coding/verify_logs/patched/upload_response.txt (other, 501 bytes) - coding/verify_logs/patched/all_uploaded_files.txt (other, 40 bytes) - coding/verify_logs/patched/install_output.txt (other, 20 bytes) - coding/verify_logs/patched/login_response.txt (other, 767 bytes) - coding/verify_logs/git_clone.log (log, 64 bytes) - coding/verify_fix.sh (other, 6148 bytes) - coding/summary_report.md (documentation, 2442 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00132 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00132/artifacts/repro/reproduction_steps.sh - Web: https://www.pruva.dev/reproductions/REPRO-2026-00132 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://www.pruva.dev