Polyshell is a Magento 2 vulnerability present in all current versions of Magento 2 (including 2.4.8-p4). Which allows potential attackers to abuse the products custom options functionality to upload malicious files to the server. Which in turn, can be used to achieve remote code execution (RCE) on misconfigured servers.

If your server is configured correctly, then the vulnerability should be limited to just allowing malicious file uploads. Which is still a important issue, but not as severe as allowing RCE. If your server is misconfigured and allows RCE, then this vulnerability becomes an extremely critical issue that should be addressed immediately.

Read the full write up by Sansec here: https://sansec.io/research/magento-polyshell

Check if you store is Vulnerable to Polyshell

RCE Vulnerability

The most simple way to check if you are vulnerable to the RCE portion of the exploit is by creating a simple PHP file under the pub/media/custom_options/ path. And attempting to access it via the browser. If you can access the file, then you are vulnerable to RCE and need to adjust your server configuration to prevent executing non trusted PHP files.

File Upload Vulnerability

It is safe to assume you are vulnerable to the file upload aspect of the exploit if the following conditions apply:

  • not running a proactive WAF such as Sansec Shield
  • running a version below 2.4.9-alpha3 / 2.4.9-beta1
  • have not explicitly patched the exploit

POC Validation Script

I do have a POC script for validating the exploit, but currently I am choosing not to publish it, to help prevent wide spread abuse of the vulnerability. Although I will release the script once we start to see the vulnerability being actively exploited in the wild, and sufficient time has passed allowing for people to patch/mitigate the vulnerability.

If you want help validating your store in the meantime, please reach out to me directly and I will do my best to help validate your environments.

How to patch PolyShell

RCE Mitigation

If you are vulnerable to the RCE aspect, cross reference your nginx configuration with the official distributed sample configuration. You want to pay attention to the following specific sections as these are what protects you against this attack

File Upload Mitigation

The simple answer here is to run Sansec Shield. The uploads are blocked via their WAF rules, and these are proactively updated as new attack vectors and workarounds are discovered. It serves as a great first line of defense against most Magento vulnerabilities, and helps buy time to apply the necessary patches and hardening to your store.

Depending on your risk tolerance, you may also want to consider applying the Adobe commit that introduced the fix for this vulnerability. You may need to adapt the patch to work with your current Magento version, but it should be fairly straightforward to apply. You can find the commit here: https://github.com/magento/magento2/commit/796c4ce195cee0814ac92e5a19fc2ecfa79dae69

Or if you want to apply a minimal patch that just addresses the file upload aspect of the vulnerability, you can use the following diff which should apply across all versions of Magento 2.4:

# Subset of the Adobe patch for the Polyshell vuln (APSB25-94)
# https://www.samdjames.uk/blog/magento2-polyshell-vulnerability/
# https://github.com/markshust/magento-polyshell-patch/
--- a/vendor/magento/framework/Api/ImageProcessor.php
+++ b/vendor/magento/framework/Api/ImageProcessor.php
@@ -135,6 +135,7 @@
      */
     public function processImageContent($entityType, $imageContent)
     {
+        $this->uploader->setAllowedExtensions(['jpg', 'jpeg', 'gif', 'png']);
         if (!$this->contentValidator->isValid($imageContent)) {
             throw new InputException(new Phrase('The image content is invalid. Verify the content and try again.'));
         }
--- a/vendor/magento/framework/Api/ImageContentValidator.php
+++ b/vendor/magento/framework/Api/ImageContentValidator.php
@@ -60,6 +60,9 @@
         if ($sourceMimeType != $imageContent->getType() || !$this->isMimeTypeValid($sourceMimeType)) {
             throw new InputException(new Phrase('The image MIME type is not valid or not supported.'));
         }
+        if (in_array(pathinfo($imageContent->getName(), PATHINFO_EXTENSION), ['jpg', 'jpeg', 'gif', 'png']) === false) {
+            throw new InputException(new Phrase('The image file extension is not allowed.'));
+        }
         if (!$this->isNameValid($imageContent->getName())) {
             throw new InputException(new Phrase('Provided image name contains forbidden characters.'));
         }

Bonus Tip: How to check at scale?

If you are running a large number of Magento stores is can become difficult to check each store for the vulnerability. Instead you can use ansible to bulk check for the vulnerability across all your stores with a single command. Best run as part of a regular health check playbook to ensure you are regularly monitoring for the vulnerability.

We have added the following task for asserting the RCE aspect of the vulnerability alongside our other standard checks.

Ansible Playbooks

# tasks/healthchecks/security/polyshell.yaml
---
- name: Create our PolyShell test file
  tags: polyshell
  ansible.builtin.copy:
    dest: "{{ project_root }}/pub/media/custom_options/polyshell-test.php"
    content: "<?php echo 'PolyShell Test'; ?>"

- name: Check if we can access the PolyShell test file
  tags: polyshell
  delegate_to: localhost
  register: polyshell_response
  failed_when: false
  ansible.builtin.uri:
    url: "https://{{ project_domain }}/media/custom_options/polyshell-test.php"
    method: GET
    return_content: true
    validate_certs: false
    http_agent: "SamJUK-Healthcheck/1.0"

- name: Assert PolyShell Vulnerability Status
  tags: polyshell
  delegate_to: localhost
  ansible.builtin.assert:
    that:
      - polyshell_response.status != 200 or 'PolyShell Test' not in polyshell_response.content
    fail_msg: "Store is VULNERABLE to PolyShell. See: https://www.samdjames.uk/docs/platforms/magento/security/polyshell/ for details."
    success_msg: "Store is NOT vulnerable to PolyShell."