CVE-2024-24725 - Exploiting PHP Deserialization Vulnerability in Gibbon LMS <=26.0.0
In January 2024, our team have reported an authenticated remote code execution vulnerability to Gibbon. The bug originated from a legacy part of the code which is used for data import.
The vulnerability is patched by the Gibbon team and will be included in v27.0.00 release. CVE-2024-24725 was issued.
This blogpost describes how this bug was discovered, exploited and mitigated.
Discovery
When we were analyzing the source code for security issues, the first thing we did was to search for the low-hanging fruits. It’s a well-known fact that many PHP applications, especially legacy code, suffer from insecure deserialization vulnerabilities, so we looked for that. When we searched for the unserialize
function in the code, we could easily identify our target.
The relevant lines of code are located in the core/modules/System Admin/import_run.php
file. This file is part of the Import From File
feature in Gibbon. According to the Gibbon documentation, the purpose of this feature is as follows:
In step 4 of the import process, 'columnOrder
' and 'columnText
' are taken in serialized form and then unserialized, unlike in step 3, where they are in plain form.
elseif ($step==3 || $step==4) {
// Gather our data
$mode = $_POST['mode'] ?? null;
$syncField = $_POST['syncField'] ?? null;
$syncColumn = $_POST['syncColumn'] ?? null;
$csvData = $_POST['csvData'] ?? null;
if ($step==4) {
$columnOrder = isset($_POST['columnOrder'])? unserialize($_POST['columnOrder']) : null;
$columnText = isset($_POST['columnText'])? unserialize($_POST['columnText']) : null;
} else {
$columnOrder = $_POST['columnOrder'] ?? null;
$columnText = $_POST['columnText'] ?? null;
}
Exploitation
When we attempt to import a file from the UI, in step 3, we have the option to choose “Ignore Errors?” before proceeding to step 4, where the vulnerability exists.
When we capture the network request, we can observe the serialized data being transmitted.
POST /index.php?q=/modules/System%20Admin/import_run.php&type=externalAssessment&step=4 HTTP/1.1
Host: localhost:8080
...
------WebKitFormBoundary6GcgCleAbW3KrG2n
Content-Disposition: form-data; name="columnOrder"
a:7:{i:3;s:2:"-4";i:0;s:1:"0";i:1;s:1:"1";i:2;s:1:"2";i:4;s:1:"3";i:5;s:1:"4";i:6;s:1:"5";}
------WebKitFormBoundary6GcgCleAbW3KrG2n
Content-Disposition: form-data; name="columnText"
N;
...
Now that we have discovered serialized data, we can begin our search for usable PHP gadgets. By checking the composer.json
file, we can determine if there's a gadget that can be utilized. Fortunately, Gibbon employs Monolog, which offers us a viable option.
We can generate a payload using phpggc
, and we can also verify whether it is being triggered in our system by using the --test-payload
flag.
Let’s generate a payload using phpggc
and see if it can be triggered. Since the payload contains null bytes, we need to first copy it in a URL-encoded form and later decode it within Burp Suite.
Gibbon crashed without triggering code execution.
During debugging, we found that while the data is correctly unserialized, it is subsequently used in a code snippet where an error is thrown due to invalid data, leading to an exit. Consequently, the destruct
function cannot be called, preventing the triggering of our payload.
if ($importSuccess || $ignoreErrors) {
$buildSuccess &= $importer->buildTableData($importType, $columnOrder, $columnText);
}
Luckily, we can circumvent this issue by using the --fast-destruct
flag in phpggc
, which ensures the object is destroyed immediately after it's unserialized, allowing for immediate payload triggering.
Now, let’s generate the payload again and try it.
Now, as before, we can paste it into Burp Suite, URL-decode it, and then send it.
Bingo! We got code execution.
Exploit Code
The proof-of-concept (PoC) code has been released on Exploit Database and can be utilized to exploit the vulnerability automatically.
You can use it as follows:
Patch
The remediation was straightforward, as the developer simply had to replace unserialize
with json_decode
, similar to other cases (commit):
$csvData = $_POST['csvData'] ?? null;
if ($step==4) {
- $columnOrder = isset($_POST['columnOrder'])? unserialize($_POST['columnOrder']) : null;
- $columnText = isset($_POST['columnText'])? unserialize($_POST['columnText']) : null;
+ $columnOrder = isset($_POST['columnOrder'])? json_decode($_POST['columnOrder'], true) : null;
+ $columnText = isset($_POST['columnText'])? json_decode($_POST['columnText'], true) : null;
} else {
$columnOrder = $_POST['columnOrder'] ?? null;
$columnText = $_POST['columnText'] ?? null;
$form->addHiddenValue('syncField', $syncField);
$form->addHiddenValue('syncColumn', $syncColumn);
- $form->addHiddenValue('columnOrder', serialize($columnOrder));
- $form->addHiddenValue('columnText', serialize($columnText));
+ $form->addHiddenValue('columnOrder', json_encode($columnOrder));
+ $form->addHiddenValue('columnText', json_encode($columnText));
$form->addHiddenValue('fieldDelimiter', urlencode($fieldDelimiter));
$form->addHiddenValue('stringEnclosure', urlencode($stringEnclosure));
Timeline
Date | Action |
---|---|
19-01-2024 | Reported the issue to Gibbon |
22-01-2024 | Developer made the patch |
23-03-2024 | CVE Number CVE-2024-24725 published |
Summary
In conclusion, we have successfully identified and exploited a deserialization vulnerability in Gibbon LMS, and our efforts have contributed significantly to its effective mitigation. This endeavor highlights the importance of continuous vigilance and proactive security measures in software systems.
Related Links
- https://nvd.nist.gov/vuln/detail/CVE-2024-24725
- https://cve.mitre.org/cgi-bin/cvename.cgi?name=2024-24725
- https://packetstormsecurity.com/files/177635/Gibbon-LMS-26.0.00-PHP-Deserialization-Code-Execution.html