PHPackages                             aregowe/magento2-module-polyshell-protection - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Security](/categories/security)
4. /
5. aregowe/magento2-module-polyshell-protection

ActiveMagento2-module[Security](/categories/security)

aregowe/magento2-module-polyshell-protection
============================================

Comprehensive defense-in-depth module that closes the PolyShell unrestricted file upload vulnerability (APSB25-94) in Adobe Commerce and Magento Open Source.

1.3.4(3mo ago)5973.1k↓21.6%8[4 issues](https://github.com/aregowe/magento2-module-polyshell-protection/issues)[1 PRs](https://github.com/aregowe/magento2-module-polyshell-protection/pulls)MITPHPPHP ^8.1

Since Apr 14Pushed 1mo ago5 watchersCompare

[ Source](https://github.com/aregowe/magento2-module-polyshell-protection)[ Packagist](https://packagist.org/packages/aregowe/magento2-module-polyshell-protection)[ RSS](/packages/aregowe-magento2-module-polyshell-protection/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (6)Dependencies (3)Versions (14)Used By (0)

Aregowe\_PolyShellProtection
============================

[](#aregowe_polyshellprotection)

Purpose
-------

[](#purpose)

Comprehensive defense-in-depth module that closes the **PolyShell** unrestricted file upload vulnerability (APSB25-94) in Adobe Commerce. PolyShell affects all Magento Open Source and Adobe Commerce versions up to 2.4.9-alpha2. No official isolated patch exists for production versions.

This module was originally forked from [markshust/magento2-module-polyshell-patch](https://github.com/markshust/magento-polyshell-patch) by [Mark Shust](https://github.com/markshust). With Mark's permission, his module's logic has been fully integrated into this one, and he has deprecated his package in favor of this module.

**Reference:** [Sansec — PolyShell: unrestricted file upload in Magento and Adobe Commerce](https://sansec.io/research/magento-polyshell)

If this module helped protect your store, consider [buying me a coffee ☕](https://ko-fi.com/aregowe) — it helps me keep maintaining and improving it.

Installation
------------

[](#installation)

### Via Composer (recommended)

[](#via-composer-recommended)

```
composer require aregowe/magento2-module-polyshell-protection
bin/magento module:enable Aregowe_PolyShellProtection
bin/magento setup:upgrade
bin/magento cache:flush
```

### Manually

[](#manually)

Copy the module into your project:

```
mkdir -p app/code/Aregowe/PolyShellProtection
cp -r * app/code/Aregowe/PolyShellProtection/
bin/magento module:enable Aregowe_PolyShellProtection
bin/magento setup:upgrade
bin/magento cache:flush
```

### Uninstallation

[](#uninstallation)

```
bin/magento module:disable Aregowe_PolyShellProtection
bin/magento setup:upgrade
composer remove aregowe/magento2-module-polyshell-protection
bin/magento cache:flush
```

Migrating from MarkShust\_PolyshellPatch
----------------------------------------

[](#migrating-from-markshust_polyshellpatch)

This module **integrates and supersedes** [markshust/magento2-module-polyshell-patch](https://github.com/markshust/magento-polyshell-patch). You do **not** need both modules — this one includes all of Mark Shust's original protection and extends it significantly.

Mark Shust's module provided a focused two-plugin fix that enforced a 4-extension image allowlist (`jpg`, `jpeg`, `gif`, `png`) on `ImageContentValidator` and `ImageProcessor`. That logic is now fully integrated into this module's `HardenImageContentValidatorPlugin` and `HardenImageProcessorPlugin`, which add:

- Polyglot file scanning (detects valid images with embedded PHP)
- No-extension and double-extension attack detection
- Multi-pass URL decoding and obfuscation normalization
- Known attack filename/pattern matching
- Request path blocking at the FrontController and `pub/get.php` level
- Controller-level upload blocking for customer attribute and file upload endpoints
- Configurable filename validation for custom option file uploads via the Webapi File Processor, with admin-configurable allowed and blocked extension lists

The `composer.json` includes a `"replace"` directive for `markshust/magento2-module-polyshell-patch`, so Composer will automatically handle the transition.

### If you currently have MarkShust\_PolyshellPatch installed

[](#if-you-currently-have-markshust_polyshellpatch-installed)

```
bin/magento module:disable MarkShust_PolyshellPatch
bin/magento setup:upgrade
composer require aregowe/magento2-module-polyshell-protection
bin/magento module:enable Aregowe_PolyShellProtection
bin/magento setup:upgrade
bin/magento cache:flush
```

Composer's `replace` directive will remove MarkShust's package automatically when this module is installed.

### Credits

[](#credits)

This module was forked from [markshust/magento2-module-polyshell-patch](https://github.com/markshust/magento-polyshell-patch), created by [Mark Shust](https://github.com/markshust) and sponsored by [M.academy](https://m.academy/). Mark gave his permission to integrate his module's logic into this one and has deprecated his package in favor of this project. Thank you, Mark!

Vulnerability Summary
---------------------

[](#vulnerability-summary)

Magento's REST API accepts file uploads as part of cart item custom options. When a product option has type **file**, Magento processes an embedded `file_info` object containing base64-encoded file data, a MIME type, and a filename. The file is written to `pub/media/custom_options/quote/` on the server.

Three critical checks are missing from core Magento:

1. **No option ID validation** — the submitted option ID is never verified against the product's actual options.
2. **No option type gating** — file upload logic triggers regardless of whether the product has a file-type option.
3. **No file extension restriction** — extensions like `.php`, `.phtml`, and `.phar` are not blocked. The only validation is `getimagesizefromstring`, which is trivially bypassed using polyglot files (valid image headers containing embedded PHP).

The most dangerous endpoints are the anonymous guest cart routes:

MethodEndpointAuth RequiredPOST`/V1/guest-carts/:cartId/items`NonePUT`/V1/guest-carts/:cartId/items/:itemId`None### Live Attack Patterns

[](#live-attack-patterns)

Attackers upload **polyglot files** — valid GIF or PNG images containing executable PHP. Two payload types are in active use:

- **Cookie-authenticated webshell** — GIF89a polyglot dropped as `index.php`, verifies cookie against hardcoded MD5 hash, executes arbitrary code via `eval(base64_decode())`.
- **Password-protected RCE shell** — uses `hash_equals()` with double-MD5 hash, passes commands to `system()`.

Common attack filenames: `index.php`, `780index.php` (option\_id prefix), `json-shell.php`, `bypass.phtml`, `rce.php`, `shell.php`, `accesson.php`, `test.php`, `ato_poc.html`.

Post-exploitation deploys `accesson.php` backdoors across writable directories (`app/assets/images/`, `var/assets/images/`, `vendor/assets/images/`, etc.) and injects JavaScript malware loaders into CMS content.

How This Module Protects
------------------------

[](#how-this-module-protects)

This module implements **eight layered Magento plugins** and **three security models** that block the attack at every interception point. If one layer is bypassed, subsequent layers catch it.

### Defense Layers (in execution order)

[](#defense-layers-in-execution-order)

#### Layer 1 — Request Path Blocking

[](#layer-1--request-path-blocking)

PluginTarget ClassStrategy`BlockSuspiciousMediaPathPlugin``FrontController`Blocks HTTP requests to `/media/customer_address/`, `/media/custom_options/`, etc. via `aroundDispatch`. Returns 404.`BlockSuspiciousMediaAppPathPlugin``Media` (get.php)Blocks media serving via the `pub/get.php` entrypoint for the same paths. Uses reflection to read `Media::$relativeFileName`. Returns 404.#### Layer 2 — Controller-Level Upload Blocking

[](#layer-2--controller-level-upload-blocking)

PluginTarget ClassStrategy`BlockCustomerAttributeFileUploadControllerPlugin``AbstractUploadFile`Blocks ALL customer attribute file upload controllers at the entry point. Returns JSON error.`BlockCustomerFileUploadPlugin``FileProcessor`Blocks `saveTemporaryFile` and `moveTemporaryFile` for `customer_address`, `customer_addresses`, and `custom_options` entity types. Fails closed if reflection cannot read entity type.#### Layer 3 — Custom Option Upload Validation

[](#layer-3--custom-option-upload-validation)

PluginTarget ClassStrategy`ValidateUploadedFileNamePlugin``File\Processor` (Webapi)Validates the filename of custom option file uploads against the merged allowlist/blocklist via `FileUploadGuard::assertSafeFileName()`. Safe files pass through; dangerous files are blocked with a logged warning.`ValidateUploadedFileContentPlugin``File\Processor` (Webapi)Validates filename safety (extension, pattern, obfuscation) and scans file content for polyglot/embedded PHP.`ValidateCustomOptionUploadPlugin``CustomOptionProcessor`Validates filenames in custom option `file_info` payloads at cart/quote level via `FileUploadGuard::assertSafeFileName()`. Iterates all custom options; cart items without file payloads pass through unmodified.#### Layer 4 — Framework-Level Image Hardening

[](#layer-4--framework-level-image-hardening)

PluginTarget ClassStrategy`HardenImageContentValidatorPlugin``ImageContentValidator`After core validation, enforces a strict image-only extension allowlist, infers extensions for extension-less uploads by delegating to `FileUploadGuard::inferExtensionForFileName()` (MIME type is fetched lazily only when needed), blocks uploads when the MIME type is missing or unmapped, detects double-extension attacks (`.php.jpg`), scans base64 content for polyglot payloads. Integrates MarkShust\_PolyshellPatch's extension check.`HardenImageProcessorPlugin``ImageProcessor`Before file write, locks the Uploader's allowed extensions via reflection, infers extensions for extension-less payloads by delegating to `FileUploadGuard::inferExtensionForFileName()` (MIME type is fetched lazily only when needed), blocks uploads with missing or unmapped MIME types and non-image extensions, scans for polyglot content.### Security Models

[](#security-models)

ModelResponsibility`FileUploadGuard`Orchestrates filename validation: configurable extension allowlist/blocklist (base code-defined sets merged with admin-configured additions via `getAllowedExtensions()` / `getBlockedExtensions()`), blocked-extension pattern matching, private multi-pass normalization (unicode decoding, URL decoding, CR/LF/TAB replacement, whitespace collapse), MIME-to-extension inference via `inferExtensionFromMimeType()`, and a combined infer-validate-normalize flow via `inferExtensionForFileName()` used by both image-hardening plugins. The blocklist always overrides all allowlists. Delegates attack-pattern and polyglot detection to AttackPatternDetector and PolyglotFileDetector.`AttackPatternDetector`Maintains a list of known attack filenames and regex patterns observed in active PolyShell campaigns. Blocks exact filename matches and suspicious patterns (option\_id + index.php, double extensions, shell/backdoor naming, obfuscation hints).`PolyglotFileDetector`Detects polyglot files by checking if content starts with an image signature (PNG, GIF, JPEG, RIFF, ICO, CUR, BMP) and then scanning for embedded PHP code patterns (`
