Blog Post

Joomla: PHP Bug Introduces Multiple XSS Vulnerabilities

Stefan Schiller photo

Stefan Schiller

Vulnerability Researcher

Date

  • Security

Update 2024-02-23: Full technical details added.

Key Information

  • Sonar’s Vulnerability Research Team has discovered an issue that led to multiple XSS vulnerabilities in the popular Content Management System Joomla.
  • The issue discovered with the help of SonarCloud affects Joomla’s core filter component and is tracked as CVE-2024-21726.
  • Attackers can leverage the issue to gain remote code execution by tricking an administrator into clicking on a malicious link.
  • The underlying PHP bug is an inconsistency in how PHP’s mbstring functions handle invalid multibyte sequences.
  • The bug was fixed with PHP versions 8.3 and 8.4, but not backported to older PHP versions.
  • Joomla released a security announcement and published version 5.0.3/4.4.3, which mitigates the vulnerability.

Joomla

Joomla is a free and open-source Content Management System (CMS) used for building websites and online applications. Roughly 2% of all websites use Joomla, which makes it one of the most popular CMSs with millions of deployments worldwide.


The widespread usage of Joomla and the fact that most deployments are publicly accessible makes it a valuable target for threat actors. Just recently, Joomla was targeted in an attack against different organizations via an improper access control vulnerability (CVE-2023-23752).


In this article, we dive into an interesting XSS issue detected by SonarCloud, which led us down the rabbit hole to the discovery of a bug in PHP. We will explain how an inconsistency in PHP’s mbstring functions can be leveraged by attackers to bypass Joomla’s input sanitization introducing multiple XSS vulnerabilities.

Impact

Joomla versions 5.0.2/4.4.2 and below are prone to multiple XSS vulnerabilities. Attackers tricking an administrator into clicking on a malicious link can gain remote code execution (RCE):


Joomla version 5.0.3/4.4.3 mitigates the issue regardless of the PHP version. The underlying PHP bug was fixed with PHP versions 8.3 and 8.4, but not backported to older PHP versions.


We strongly recommend updating Joomla to the latest version as well as keeping your PHP version up-to-date.

Technical Details

In our continuous effort to help secure open-source projects and improve our Clean Code solution, we regularly scan open-source projects via SonarCloud and evaluate the findings. When scanning Joomla, SonarCloud reported an interesting XSS issue:

View this issue on SonarCloud


This small code snippet is taken from a settings page on the admin panel. According to the raised issue, the query parameter forcedItemType is reflected in the output, which introduces an XSS vulnerability.


Please notice that the third argument of the get method used to retrieve the query parameter is set to string. This value determines which filters should be applied to the query parameter. Under the hood, the get method uses the Joomla\Filter\InputFilter class to sanitize potentially malicious input, which should prevent an XSS attack.


The filter logic is quite complex and uses a method called cleanTags to remove all HTML tags that are not explicitly allowed. For query parameters, no tags are allowed at all.


Thus, for the following example input:

some-text<script>alert(1)</script>

…, the <script> tags are removed, which results in this output:

some-textalert(1)

The cleanTags method performs this sanitization by determining the position of any opening tags (<) and then removing all data following until and including the corresponding closing tag (>):

The characters before an opening tag (e.g., some-text in the example above) are extracted by determining the offset of the opening tag ($tagOpenStart) via StringHelper::strpos and then using StringHelper::substr to extract it:

// Is there a tag? If so it will certainly start with a '<'.
$tagOpenStart = StringHelper::strpos($source, '<');
while ($tagOpenStart !== false) {
    // Get some information about the tag we are processing
    $preTag .= StringHelper::substr($postTag, 0, $tagOpenStart);

For the example string some-text<script>alert(1)</script>, the first call to StringHelper::substr returns the string some-text, which is appended to the $preTag variable:

On the second iteration, the string alert(1) is added:

The $preTag variable used to collect all sanitized substrings is later returned as the final result:

    // ... 
    return $preTag;
}

The StringHelper::strpos and StringHelper::substr methods are just wrappers around the respective PHP mbstring functions mb_strpos and mb_substr.


When determining if this sanitization is safe, we noticed that both PHP functions, mb_strpos, and mb_substr, handle invalid UTF-8 sequences differently. When mb_strpos encounters a UTF-8 leading byte, it tries to parse the following continuation bytes until the full byte sequence is read. If an invalid byte is encountered, all previously read bytes are considered one character, and the parsing is started over again at the invalid byte:

Thus, the following call to mb_strpos returns the index 4:

mb_strpos("\xf0\x9fAAA<BB", '<'); // 4

This index is the position of the opening angle bracket < (3c) character within the string.


mb_substr, on the other hand, skips over continuation bytes when encountering a leading byte:

This means that for mb_substr, the first four bytes are considered one character and the opening angle bracket < (3c) character has the index 2. Thus, the following call to mb_substr returns "\xf0\x9fAAA<B" when using the index returned by mb_strpos :

mb_substr("\xf0\x9fAAA<BB", 0, 4); // "\xf0\x9fAAA<B"

Because of this inconsistency between both functions, Joomla’s sanitization extracts not only the text before an opening angle bracket but also the opening angle bracket itself and the following character when encountering this invalid UTF-8 byte sequence:

An attacker can insert multiple invalid UTF-8 sequences, which effectively offset the index returned by StringHelper::strpos way beyond the opening angle bracket and thus include arbitrary HTML tags in the sanitized output. This completely bypasses the sanitization applied by Joomla. Since this issue affects Joomla’s core filter functionality, which is used all over the whole code base, this leads to multiple XSS vulnerabilities.


One of the resulting XSS vulnerabilities can for example be leveraged by an attacker to craft a malicious link. When an administrator clicks on this link, the injected JavaScript payload can be used to customize a template and insert arbitrary PHP code. Thus, an attacker can gain remote code execution (RCE) by tricking an administrator into clicking on the malicious link.

Patch

Joomla addressed the issue by replacing the usage of the mbstring functions with PHP’s regular string functions:

// Is there a tag? If so it will certainly start with a '<'.
- $tagOpenStart = StringHelper::strpos($source, '<');
+ $tagOpenStart = strpos($source, '<');

while ($tagOpenStart !== false) {
    // Get some information about the tag we are processing
-    $preTag .= StringHelper::substr($postTag, 0, $tagOpenStart);
+    $preTag .= substr($postTag, 0, $tagOpenStart);

The difference between these functions is that PHP’s regular string functions are not multibyte aware and operate on single bytes. Since multibyte awareness is not required for the applied sanitization, these functions should be preferred.


We also reported the inconsistent behavior of the mbstring functions to the PHP maintainers, since we consider it as unintended. The PHP maintainers provided a patch, which makes the behavior consistent by not skipping over continuation bytes when encountering a leading byte. Unfortunately, the issue was not classified as security-relevant, which means that the patch is not backported to older versions of PHP.


More background information on the behavior of the PHP mbstring functions and the patch can be found in the excellent explanation from Alex Dowad in the related commit message.

Timeline

DateAction
2023-11-22We report the vulnerability to the Joomla! Security Strike Team
2023-11-28The Joomla! Security Strike Team confirms our findings.
2023-12-01We report the inconsistent mbstring function behavior to the
PHP maintainers.
2023-12-10The PHP maintainers provide a patch, which is applied to
PHP 8.3 and 8.4.
2024-02-20Joomla releases version 5.0.3/4.4.3, which mitigates the issue
regardless of the PHP version.
2024-02-20Coordinated release of security announcement by Joomla and Sonar.
2024-02-23Full technical details added.

Summary

In this article, we explained how SonarCloud led us to an interesting XSS finding in the popular CMS Joomla. During our analysis of the issue, we discovered an inconsistency in how PHP’s mbstring functions handle invalid multibyte sequences. Attackers could leverage this behavior to bypass the sanitization performed by Joomla’s core filter leading to multiple XSS vulnerabilities.


Finally, we would like to thank the Joomla! Security Strike Team for quickly responding to our notification, collaborating on a corresponding patch, and informing all users.


Also, thanks a lot to Alex Dowad for quickly addressing the issue from the PHP side!

Related Blog Posts

Get new blogs delivered directly to your inbox!

Stay up-to-date with the latest Sonar content. Subscribe now to receive the latest blog articles. 

By submitting this form, you agree to the Privacy Policy and Cookie Policy.