Two Metasploit Framework modules have held my interest in the last few weeks: the ones for persistence using Linux package managers apt and Yum. While they require root privileges to exploit, they provide a novel persistence mechanism that is likely overlooked by defenders. While looking at these modules I noticed that persistence using DNF wasn’t included, and I set off to find a way to persist on Linux systems using DNF plugins.
What’s a Package Manager, anyway?
On Windows systems most folks download applications from sites on the Internet or install them from media such as CDs or DVDs. This proves to be a major threat to the stability and security of Windows as it lets users download and install unpredictable software. On the other side of the spectrum, it allows users to find and use software to improve their world.
For Linux distributions, the software installation model looks different. You can still download software from the Internet, but it is not usually distributed in binary form for Linux systems. In these cases, you’d need to download the source code from a trusted mirror, compile it, and resolve any dependencies needed to make the software work. This is unfriendly for inexperienced users and can cause misconfiguration. As systems developed, a more friendly form of software distribution evolved: packages. Packages became an easy way to distribute compiled binaries for specific Linux distro versions and the software that managed packages helped administrators automate dependency resolution and configuration.
Now, most Linux distributions have a package manager and consume one of a few package types. The most common are RPM Package Manager (RPM) and Debian (DEB) packages. RPM packages work with systems that derive from Red Hat Enterprise Linux. These include RHEL itself, CentOS, and Fedora. DEB packages work with systems that derive from Debian Linux. These include Debian, Ubuntu, Mint, ElementaryOS, and others.
On the RPM side, administrators used the Yum command to interact with packages prior to RHEL/CentOS 8 and Fedora 22. After those builds, administrators began to use the dnf command. On the DEB side, administrators typically use the apt or apt-get commands to interact with packages. As these package managers became more complex, they included plugins or additional functionality to make package installation more extensible. A good example of this in Yum is the fastestmirror plugin that measures the speed of interaction with one or more update mirrors and helps Yum pick the fastest one.
For the rest of this post we will focus on Yum and DNF plugins. Some of this functionality also exists in apt, but that’s a story for another day.
Foundational Yum and DNF Plugin Knowledge
Yum and DNF are both written in Python and handle all the operations around package installation. From the terminal you’ll run commands such as this:
1 2 /usr/bin/dnf install httpd /usr/bin/yum update
Since these tools are implemented in Python, their plugins are also Python.
With Yum, we need a few things for plugin execution. First, plugins must be enabled in
/etc/yum.conf. This is done with the line
plugins=1. Next, one or more plugin configurations present in the
/etc/yum/pluginconf.d directory must be enabled with a line
enabled=1. Finally, the Python plugin code itself lives in the
/usr/lib/yum-plugins directory. When writing the plugin, the developer can set hook functions that will execute when Yum executes specific triggers.
As it so happens, the
fastestmirror plugin is always used by yum in its default state. An easy way to exploit this is to plant malicious code within
/usr/lib/yum-plugins/fastestmirror.py. This requires root privileges and will cause the malicious code to run as root every time Yum executes for installations or updates.
With DNF, the configuration and code paths change but most of the concepts stay the same. First, DNF configurations exist in
/etc/dnf/dnf.conf. Plugins are enabled by default and are only disabled if a line similar to
plugins=False is present. Next, plugin configurations exist in the
/etc/dnf/plugins directory. Finally, the actual plugin code lives under the
/usr/lib/python3.6/site-packages/dnf-plugins directory, but the path will change as Python versions change.
In DNF, we can target one specific plugin for persistence:
generate_completion_cache.py. This plugin executes whenever an administrator runs
dnf install or
dnf update to speed up shell completion. It is distributed by default across Fedora, CentOS, and RHEL distributions. There doesn’t appear to be a specific configuration file that disables the plugin itself. Even in the dnf-plugins-core documentation, the plugin appears intended to always run in the background without user interaction. This is perfect for exploitation. To show, we can use vim to change the plugin code.
/usr/lib/python3.6/site-packages/dnf-plugins/generate_completion_cache.py to add the following code above
1 2 import os os.system(‘touch /etc/root-marker’)
This will give us proof that the plugin executed as root and wrote a file to disk.
Next, execute these commands (as root):
1 2 3 dnf update ls -l /etc/root-marker
/etc/root-marker exists, you have a successful execution.
Detection and Breakage
I haven’t dived too far into detection for this technique. Process monitoring will show the code you add as spawning from an instance of
python with the command line showing
dnf update or a similar command. You may find luck monitoring for different
exec system calls that spawn shell processes, scripts, or other utilities. Any Python code you add that does not spawn an external command will execute within the
dnf python instance itself. This will blend in with surrounding update activity.
This technique will probably break with package updates to
dnf-plugins-core. In addition, you can revert the plugin file by reverting the added code.
Why not just change DNF itself?
You can probably do that, I just zoomed in on the plugins for execution. If you want to modify DNF for persistence, you probably can.
Is it useful?
It depends. This technique requires root/sudo privileges to use. The technique assumes you’re attempting to persist on a system that you’ve found either misconfigured or susceptible to privilege escalation. This path should not be your first stop for persistence if you only have user privileges.