This blog post presents a post-exploitation approach to inject code into KeePass without process injection. It is performed by abusing the cache resulting from the compilation of PLGX plugin.

Foreword

Today, users have a lot of online accounts. They are composed of a username (which is often predictable) and a password (which is something to keep secret) used to authenticate them.

Most users create weak or predictable passwords and reuse them across multiple services for simplicity, which is considered bad practice. For example, if any of these websites has a data breach, then an attacker can retrieve a salted password hash, as it is the recommended way to store passwords. Since a hash can't be reversed, an attacker uses a dictionary attack or a brute-force attack to retrieve the password. As a result, all the user's accounts that use this one could be compromised.

To avoid that, a good practice for users is to pick a strong, randomly generated password for each service, and store them in a password manager. More details about "strong password" are available in the NIST Special Publication 800-63B, Appendix A — Strength of Memorized Secrets.

Password managers are programs/apps that help users by generating passwords and storing them properly in the same place. A master key is used to encrypt/decrypt the passwords stored in the application, meaning that only one secret should be kept in mind. Unfortunately, a password manager is an attractive target for attackers, as extracting the passwords stored in the password manager will let the attacker compromise all the user's accounts. Therefore, some password managers try to prevent extraction of the passwords they store locally (or on the cloud for those using that).

Introduction

KeePass is a well-known free open-source password manager developed by Dominik Reichl. The software is available on the most popular operating systems (Linux, macOS, and Microsoft Windows). It provides a password generator and a local database where passwords are stored encrypted using a master key. All details about KeePass security are written in the Security section.

KeePass comes with a plugin framework that allows developers to extend its features like backup the local database to the cloud, supply credentials through a network protocol, add an additional import/export format and so on. A list of available plugins is given in the official website.

According to the Plugin Development (2.x) page, as a plugin depends on the version of KeePass during the build, an optional plugin file format (called PLGX) was created. This blog post presents a way to abuse a mechanism related to this format to load arbitrary code in the KeePass process, even if KeePass is installed with administrative rights on Windows. Mitigations are given for those worried about the security of the plugin cache.

This blog post is written after reporting and discussing the issue with Dominik Reichl (thanks to him). As a reminder, the DLL format is better from a security point of view. If you need strong compatibility, you can keep using PLGX plugins but you should follow the clear mitigation now written in the plugins page in the Security section.

PLGX Plugin

A KeePass plugin is developed in C#. The compiler create a dynamic-link library (DLL) containing the managed assembly. This kind of plugin format can cause a crash because of an API change, since the plugin depends on the version of KeePass that was used to build it. An optional plugin file format (called PLGX) is available to address this potential issue.

PLGX is an object-oriented file format that contains all information needed to compile the plugin with the version of KeePass to a DLL. The compiled libraries are stored in a folder called PluginCache, from which they are loaded at start-ups.

All details about the format are documented in the official website.

Problem

As long as KeePass can load plugins, then it can be easy to load a malicious plugin by registering one in the Plugins folder (located in the application folder).

To restrict access to the Plugins folder, KeePass can be installed in the %ProgramFiles% folder with administrative rights. As a result, Plugins isn't writable by users meaning that a plugin can't be registered by a user. See the Plugin Security section.

The following PowerShell command confirms that a sub-folder of %ProgramFiles% only inherits the GENERIC_READ and GENERIC_EXECUTE permission concerning the Users group. The permission mapping between generic access rights and file permissions is given in the official documentation.

PS C:\> get-acl '.\Program Files\' | 
>> select -expand access | 
>> ? {$_.IdentityReference -like "*Users"}


FileSystemRights  : -1610612736
AccessControlType : Allow
IdentityReference : BUILTIN\Users
IsInherited       : False
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : InheritOnly

The FileSystemRights value is converted in hexadecimal using the Python hex function. It is composed of GENERIC_READ (0x80000000) and GENERIC_EXECUTE (0x20000000):

>>> hex(-1610612736 & 0xFFFFFFFF)
'0xa0000000'

Despite the Plugins folder not being writable for users, it is still possible to load a plugin without high privileges if the plugin format is PLGX. Once a PLGX plugin has been put in the Plugins folder (which requires high-level privileges), KeePass (at startup) builds the plugin. As described in the Plugin Cache section, the DLLs newly compiled are stored in a cache folder which is located in the user's application data directory (%LocalAppData%\KeePass\PluginCache). As shown by the following command, the current user has full control of sub-folders (and their files) of %LocalAppData%. Thus, the DLLs in PluginCache is writable by the current user by default, and it can be overwritten, resulting in a way to inject code into the KeePass process. This is a typical case of DLL hijacking.

PS C:\> get-acl "C:\Users\$env:UserName\AppData\Local\" | 
>> select -expand access | 
>> ? {$_.IdentityReference -like "*$env:UserName"}

FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : [REDACTED]
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

Note that to do this an attacker must already have access to the user account, but doesn't need to have administrative rights.

Impact

Abusing the plugin cache could be a way to bypass security products. This approach allows to inject code into KeePass in a stealthy manner, because process injection (which can trigger alarms of security products) isn't required.

A plugin has full access to the KeePass memory. As an example, a malicious plugin would be able to decrypt all protected information once the database is decrypted or break the integrity of the cryptographic functions by spoofing the random number generator.

As an example, find below a code snippet that can be used to dump all stored passwords in the database through a plugin at runtime. First, it finds all classes that represent visible objects (also known as Controls ), which are called m_lvEntries. These controls must have data which is ensured by a non-null Tag and a filled-in Entry. As documented, passwords are stored encrypted in memory while KeePass is running. That is why the value is retrieved using ReadSafe before saving it in a file.

private void OnUIStateUpdated(object sender, EventArgs e)
{
    ListView lv = (m_host.MainWindow.Controls.Find("m_lvEntries", true)[0] as ListView);
    if (lv == null) { return; }

    foreach (ListViewItem lvi in lv.Items)
    {
        PwListItem li = (lvi.Tag as PwListItem);
        if (li == null) { continue; }

        PwEntry entry = li.Entry;
        if (entry == null) { continue; }

        var passphrase = entry.Strings.ReadSafe(PwDefs.PasswordField);
        File.AppendAllText("db.txt", passphrase + "\n");
    }
}

KeeFarceReborn (made by d3lb3) is a standalone DLL using an easier way to export a KeePass databases. It calls the KeePassXml2x.Export method that saves the database in plain XML format. This project is referenced by KeePwn which is a tool to automate KeePass discovery and secret extraction.

Mitigation

At least, there are two ways to deal with the issue that is ultimately related to file permission being too permissive: fix the permissions and do not use a user's folder.

The PluginCache folder is in %LocalAppData%\KeePass, which is owned by the current user. The Write permission of the cache folder should be adjusted as suggested in the Security section. Otherwise, you can use another folder that isn't writable for the current user. As explained, in the Plugin Cache section, the default path to the cache folder can be changed using the PluginCachePath key in the configuration file. In any case, KeePass should be run with administrative privileges with each update to let it compile them. Take care, the plugins will execute their code with the same privilege level of the KeePass process.

Find below the way to change the location of the cache folder:

<Application>
    <PluginCachePath>C:\PATH\TO\DEDICATED\FOLDER</PluginCachePath>

[TRUNCATED]

</Application>

The issue only affects PLGX plugins, therefore a user should use plugins in native format (although this presents the potential compatibility issues discussed above). However, some developers only release plugins in PLGX format. In this case, users can run KeepPass in a controlled environment to let it compile the extensions, and then copy the resulting DLLs to the Plugins folder on the target system, which is in the application directory as long as KeePass was installed with administrative rights.

Beyond that, KeePass can be hardened using enforced configuation and application policy. For example, KeePass provides a policy that disable the database export functionality. In conjunction with an enforced configuration, an attacker that only has an access to the user account can't dump the database using the built-in export function.


If you would like to learn more about our security audits and explore how we can help you, get in touch with us!