Note: This article is also available in french 🇫🇷.
Introduction
Kirby is a Nitendo video game character open-source PHP CMS oriented for creators and designers.
The vulnerability presented in this article is an XML External Entity (XXE) in Kirby’s toolbox.
Code review – Identifying vulnerable code
A quick code review on the latest version of Kirby (3.9.5 at the time) quickly revealed that the Xml::parse(string $sml)
toolkit function (src/Toolkit/Xml.php
) present in Kirby Core seemed vulnerable.
public static function parse(string $xml): array|null
{
$xml = @simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT);
if (is_object($xml) !== true) {
return null;
}
return static::simplify($xml);
}
Indeed, using the LIBXML_NOENT
option activates the substitution of external entities.
The PHP manual warns of the risk of XXE that this represents.
Code review – Usage detection
The Xml::parse()
function is part of a toolbox in Kirby Core, i.e. it is available within a library for developers. There are no calls to Xml::parse()
in Kirby Core, StarterKit or PlainKit. This means that for a Kirby instance to be vulnerable, the customer must have made a custom development that uses this function, or have installed an extension that uses this function.
In the documentation, you can see an example of virtual page creation using an RSS feed from an external source where the function is used.
On the other hand, it’s difficult to identify all the extensions that can use this function, but here’s at least one: FeedReader.
This extension, too, can be used to display an RSS feed using Xml::parse offering a higher-level interface.
According to the editor, Xml::parse()
is used in the Xml
data handler, such as Data::decode($string, 'xml')
. This is not used directly in Kirby either.
Exploitation – Creation of a demonstration / Proof of concept
The Github repository Acceis/exploit-CVE-2023-38490 contains a vulnerable application in the form of a docker container, a payload and the steps required to reproduce the exploit.
This Github repository offers a ready-to-use exploitation, but below I’ll detail the manual process of creating a vulnerable application with Kirby.
- Deploy a basic Kirby instance with the StarterKit as described here https://getkirby.com/docs/guide/quickstart
- Reproduce the example page with an RSS feed https://getkirby.com/docs/guide/virtual-pages/content-from-rss-feed
- Make the following change to
/site/models/rssfeed.php
to accept a user-controllable source
- Take a real RSS feed (such as https://www.acceis.fr/feed/), save the XML file and incorporate an XXE payload (in this case allowing
/etc/passwd
to be read)
- Serve malicious file
xxe.rss
via HTTP
- Trigger the vulnerability by delivering the malicious payload to the application, e.g. http://127.0.0.2:8080/rssfeed?feed=http://127.0.0.42:9999/xxe.rss
- The application displays the local file read from the system
Remediation – Patches content
For branches 3.8 and 3.9 (using PHP 8+), the patch is to remove the LIBXML_NOENT
option introducing the vulnerability.
src/Toolkit/Xml.php
-$xml = @simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT);
+$xml = @simplexml_load_string($xml);
For branches 3.5, 3.6 and 3.7 (using PHP 7 or 8+), the patch also disables the problematic LIBXML_NOENT
option, but it is also necessary to enable the libxml_disable_entity_loader
directive to prevent external entities from being taken into account when the application is running on a version of PHP prior to version 8.
-$xml = @simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT);
+$loaderSetting = null;
+if (\PHP_VERSION_ID < 80000) {
+ // prevent loading external entities to protect against XXE attacks;
+ // only needed for PHP versions before 8.0 (the function was deprecated
+ // as the disabled state is the new default in PHP 8.0+)
+ $loaderSetting = libxml_disable_entity_loader(true);
+}
+
+$xml = @simplexml_load_string($xml);
+
+if (\PHP_VERSION_ID < 80000) {
+ // ensure that we don't alter global state by
+ // resetting the original value
+ libxml_disable_entity_loader($loaderSetting);
+}
Chronology of events
See the timeline.
Acknowledgements
Thanks to Bastian ALLGEIER and Lukas BESTLE of the Kirby team for their warm receptivity.
About the author
Article written by Alexandre ZANNI alias noraj, Penetration Test Engineer at ACCEIS.