Author: HACHp1@Knownsec 404 Team
Date: August 09, 2019
Chinese Version: https://paper.seebug.org/1006/

Introduction

KDE Frameworks is a collection of libraries and software frameworks by KDE readily available to any Qt-based software stacks or applications on multiple operating systems.They offer a wide variety of commonly needed functionality solutions like hardware integration, file format support, additional graphical control elements, plotting functions, spell checking and more and serve as technological foundation for KDE Plasma 5 and KDE Applications distributed under the GNU Lesser General Public License (LGPL). The KDE framework is currently used by several Linux distributions, including Kubuntu, OpenMandriva, openSUSE, and OpenMandriva.

On July 28, 2019, Dominik Penner (@zer0pwn) found that there was a command execution vulnerability when the KDE framework version was <= 5.60.0.

On August 5, 2019, Dominik Penner released this 0-day vulnerability on Twitter. It is the way class KDesktopFile handles .desktop or .directory files that has caused this vulnerability. If the victim downloads a maliciously constructed .desktop or .directory file, the bash code injected into the files will be executed.

On August 8, 2019, the KDE community finally fixed it in their updates, and there was no official patches till that day.

Gossip

When Dominik Penner released this vulnerability, he did not tell the KDE community about it and directly posted it on Twitter. A lot of interesting things happened between Penner and KDE developers later on, but we will not discuss about them in this paper.

Affected Version

Operating system with built-in or installed KDE Frameworks version <=5.60.0 , such as Kubuntu.

Recurrence

Environment Construction

Mirror of virtual machine : kubuntu-16.04.6-desktop-amd64.iso

KDE Framework 5.18.0

Remember to shut down the network since it takes a lot of time to download the language pack. Enter the system and turn off the iso effect after the installation is complete, otherwise you will not be able to enter the system.

Recurrence Result

In this article, I reproduced it in three ways. The first and second are just proof of concept, and the third is what an attacker will do in the real world.

1.PoC1: Create a file named "payload.desktop":

Open the file manager after saving, and we will find that the written payload is executed:

The file content shows as follows:

2.PoC2:

Create a file named".directory":

Open it via vi:

Open the file manager after saving, the payload is successfully executed:

3.PoC3: The attacker starts the netcat and waite for connnection:

The attacker zips the payload file and mounts it on a web server to induce the victim to download:

The victim unzips it:

After that, the payload will be executed and the attacker receives the connected shell:

Impact of this venerability: Although the victims may become alert when the file is downloaded directly, an attacker can packs the malicious files and use social engineering to induce victims to unpack it. Whether the victim opens the unzipped file or not, the malicious code will always be executed, because the KDE system will call the desktop parsing function after the file is unzipped.

Details

Dominik Penner has already explained the vulnerability clearly. But first let's take a look at Linux's desktop entries.

Desktop Entry

The XDG Desktop Entry specification defines a standard for applications to integrate into application menus of desktop environments implementing the XDG Desktop Menu specification.

Each desktop entry must have a Type and a Name key and can optionally define its appearance in the application menu.

In other words, it's a specification for parsing desktop items like icon, name, type.

Development projects applying this specification should add the parsing configuration in the .directory or .desktop files.

Causes

KDE's desktop configuration refers to the XDG, but it includes some functions from KDE and its implementation is also different from the XDG official. These are the causes of the vulnerability.

Shell Expansion

So called Shell Expansion can be used to provide more dynamic default values. With shell expansion the value of a configuration key can be constructed from the value of an environment variable.

To enable shell expansion for a configuration entry, the key must be followed by [$e]. Normally the expanded form is written into the users configuration file after first use. To prevent that, it is recommend to lock the configuration entry down by using [$ie].
Example: Dynamic Entries

The value for the "Email" entry is determined by filling in the values of the $USER and$HOST environment variables. When joe is logged in on joes_host this will result in a value equal to "joe@joes_host". The setting is not locked down.

[Mail Settings]
Email[$e]=${USER}@${HOST} To make the setting more flexible , KDE implements and supports dynamic configuration. It is worth noticing that the ${USER} is taken from the environment variable. It's definitely related to command execution.

Every time the KDE desktop system needs to read an icon, the readEntry function is called. We can see the process of code tracking in Penner's reports. The implementation process of the entire vulnerability is as follows:
Firstly, create the malicious file as follows:

payload.desktop

[Desktop Entry]
Icon[$e]=$(echo hello>~/POC.txt)

Enter the file manager, and the system will parse the .desktop file. Enter the process of parsing Icon, according to the instructions in the document, the shell dynamic parsing will be called when the parameter has a [$e]: kdesktopfile.cpp: QString KDesktopFile::readIcon() const { Q_D(const KDesktopFile); return d->desktopGroup.readEntry("Icon", QString()); } And theKConfigPrivate::expandString(aValue) is called: kconfiggroup.cpp: QString KConfigGroup::readEntry(const char *key, const QString &aDefault) const { Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group"); bool expand = false; // read value from the entry map QString aValue = config()->d_func()->lookupData(d->fullName(), key, KEntryMap::SearchLocalized, &expand); if (aValue.isNull()) { aValue = aDefault; } if (expand) { return KConfigPrivate::expandString(aValue); } return aValue; } Then according to the official documentation of KDE, this is the process of parsing dynamic commands. The program will get the string between the first $( and the first ) as a command, and then call popen:
kconfig.cpp

QString KConfigPrivate::expandString(const QString &value)
{
QString aValue = value;

// check for environment variables and make necessary translations
int nDollarPos = aValue.indexOf(QLatin1Char('$')); while (nDollarPos != -1 && nDollarPos + 1 < aValue.length()) { // there is at least one$
if (aValue[nDollarPos + 1] == QLatin1Char('(')) {
int nEndPos = nDollarPos + 1;
// the next character is not $while ((nEndPos <= aValue.length()) && (aValue[nEndPos] != QLatin1Char(')'))) { nEndPos++; } nEndPos++; QString cmd = aValue.mid(nDollarPos + 2, nEndPos - nDollarPos - 3); QString result; // FIXME: wince does not have pipes #ifndef _WIN32_WCE FILE *fs = popen(QFile::encodeName(cmd).data(), "r"); if (fs) { QTextStream ts(fs, QIODevice::ReadOnly); result = ts.readAll().trimmed(); pclose(fs); } #endif This is the code execution process to exploit the vulnerability. You can see that KDE execute system commands only to get some parameter dynamically such as ${USER}. This is not appropriate.

Official Patches

The official fix it brutally in their latest version: they directly delete the popen function, and popen cannot parse property [e] dynamically anymore.

By the way, the official also says:

Summary:
It is very unclear at this point what a valid use case for this feature
would possibly be. The old documentation only mentions $(hostname) as an example, which can be done with$HOSTNAME instead.

Conclusion

Personally I think this vulnerability represents something out of itself. Firstly, it's not clear what were the developers thinking when when they were developing KDE, perhaps to make the framework more flexible. But it is used only to get the value of the \${USER} variable according to the document.

I found that flexibility and security are sometimes conflicting to each other.

Dominik Penner directly released the vulnerability without notifying the official, which is quite controversial. Personally, I think it is better to send this vulnerability to its developers to get it patched before releasing it. Many people supported Penner on Twitter and he explained that this was just because he wanted to submit his 0-day vulnerability before defcon started.