What's the vulnerability?

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 Analysis

## 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.
One Command

Verify with pruva-verify

Run the Pruva CLI to automatically fetch and execute the reproduction script.

pruva-verify REPRO-2026-00132
or pruva-verify CVE-2025-0520
Install: curl -fsSL https://pruva.dev/install.sh | sh

Or Run Manually

1

Download the script

curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00132/artifacts/reproduction_steps.sh
2

Make executable

chmod +x reproduction_steps.sh
3

Run the script

./reproduction_steps.sh
Run in a VM, container, or disposable environment. This exploits a real vulnerability.

How Pruva Reproduced This

Watch the AI agent's step-by-step process.

Loading session...

Artifacts

No artifacts available