Bali, Indonesia
Insights

Tracing Which cPanel Account Is Sending Spam Through Exim After a Breach

October 22, 2026
0:00
When a cPanel server gets compromised, one of the first visible symptoms is often mail. The Exim queue climbs into the thousands, the server IP starts showing up on blocklists, and legitimate customers complain their messages are delayed. The obvious move is to stop Exim or flush the whole queue. That hides the problem without telling you where it comes from. Exim already logs who submitted each message, and a few fields in exim_mainlog usually point straight at the account, mailbox, or script responsible. The questions below are the ones that come up most often at the support desk during this kind of cleanup.
Start by measuring, not deleting. Count the queue, see which recipient domains dominate, and open the headers of one or two queued messages. Spam headers often show the sending path directly. An X-PHP-Originating-Script header (added when PHP's mail.add_x_header setting is on) names the script and the user ID that called mail(). A Received line from localhost combined with an authenticated user suggests SMTP login. Next, rank senders in the log. The main Exim log on cPanel usually lives at /var/log/exim_mainlog. The three fields that matter most are cwd=, A=, and U=. The commands below are read-only, and you can run them while mail keeps flowing:
BASH
# Number of messages currently in the queue
exim -bpc

# Queue summary grouped by recipient domain
exim -bp | exiqsumm

# Headers of one queued message (take a real ID from exim -bp)
exim -Mvh MESSAGE_ID

# Most common working directories for locally submitted mail
grep -o 'cwd=[^ ]*' /var/log/exim_mainlog | sort | uniq -c | sort -rn | head -20

# Most common SMTP-authenticated logins
grep -o 'A=dovecot_[a-z]*:[^ ]*' /var/log/exim_mainlog | sort | uniq -c | sort -rn | head -20

# Most common local users on message arrival lines
grep ' <= ' /var/log/exim_mainlog | grep -o ' U=[^ ]*' | sort | uniq -c | sort -rn | head -20
The log rotates, so if the spam started earlier, run the same searches against the rotated copies too. Use zgrep if they are compressed. A sender that sits far above everyone else in one of these rankings is your lead.
cwd stands for current working directory. When a process hands mail to Exim through the local sendmail binary, which is what PHP's mail() does on most setups, Exim can log the directory that process was running in, along with the command it ran. The line looks something like cwd=/home/exampleuser/public_html/wp-content/uploads 3 args: /usr/sbin/sendmail -t -i. This only shows up when Exim's log_selector includes the arguments option. cPanel's configuration usually has it enabled, but check before you rely on it. This field is the fastest way to find a malicious PHP mailer. Legitimate site mail usually comes from the document root or a plugin's own directory. Thousands of submissions from an uploads folder, a cache directory, or a randomly named subfolder strongly suggest an uploaded script. The path also tells you the account, since /home/exampleuser/ belongs to the cPanel user exampleuser. Two values are normal noise. cwd=/var/spool/exim is Exim's own processing, such as bounces and retries. A home directory with a modest count is usually a contact form or a cron job. Paths like /tmp, /dev/shm, or / deserve a closer look because they often mean a process that is not part of any normal site. Once you have the directory, find the file inside it. Look for recently modified PHP files and match their timestamps against the log. Do not assume only one exists. Attackers often drop several copies.
A= records SMTP authentication. On a default cPanel setup, Exim checks passwords against Dovecot, so you see A=dovecot_login:[email protected] or A=dovecot_plain:[email protected]. The part after the colon is the mailbox that logged in. Usually the protocol field shows P=esmtpa or P=esmtpsa on the same line, which means authenticated SMTP, with or without TLS. This points to a different problem than cwd=. No script on the server is involved. Someone has a valid password for that mailbox and is sending through your server as a normal mail client would. The H= field on those lines shows the connecting host. Many logins for one mailbox from many unfamiliar IPs is the classic sign of stolen credentials. Logins from localhost or 127.0.0.1 can mean a script on the server is doing SMTP auth with a stolen password, or a legitimate SMTP plugin is configured with that mailbox. The headers and the site's configuration tell those apart.
U= is the local system user that submitted the message. When PHP runs as the account owner, as with suPHP or PHP-FPM pools per user, U= names the cPanel account directly, and you can combine it with cwd= to find the script. When it shows nobody, PHP is running as a shared web server user. You lose the per-account attribution in that field and have to lean on cwd= and the X-PHP-Originating-Script header instead. U=root or a system user that should never send mail is more serious, especially after a root-level compromise. The research context describes CVE-2026-41940, an authentication bypass in cPanel's cpsrvd that let unauthenticated attackers forge admin sessions and run code as root. It has been exploited in the wild for botnet installs and mass server compromises. If an attacker had root, the spam may come from a process outside any cPanel account. Logs can also be edited or truncated. In that case treat the log as a lead, not proof, and look at running processes and network connections as well.
Stop the specific sender, then clean up what it already queued. Do these in order:
  1. For a mailbox found through A=dovecot_login, change its password first. Then suspend or hold its outgoing mail from the email account settings in cPanel. Menu names differ by version, so look for the per-account sending options.
  2. For a script found through cwd=, remove or quarantine the file and look for siblings. If you cannot clean it right away, suspend outgoing mail for the whole cPanel account in WHM. This blocks that account only, while other accounts on the server keep sending.
  3. Once new spam stops arriving, remove only the messages from the identified sender. You can select them by sender address or by the account's domain using exiqgrep with the matching flags, and pass the IDs to exim -Mrm.
Warning: removing messages with exim -Mrm is permanent. Run the exiqgrep selection alone first, review a sample of the matched messages, and only then delete. A loose pattern can match legitimate mail. Stopping Exim entirely or wiping the whole queue is usually worse. Legitimate mail from every other account gets lost or delayed, and the actual sender keeps running in the background, ready to refill the queue as soon as Exim comes back.
No. Outbound spam is a symptom. The mailer script got onto the server somehow, and the mailbox password leaked somehow. After a server-level breach, attackers rarely plant only one thing. Injected rewrite rules are common too, and auditing .htaccess files for injected redirects covers that part of the cleanup. If you plan to rebuild from backups, a recent backup may already contain the attacker's changes. Choosing a clean restore point explains how to pick one. Also check the server IP's blocklist status and keep watching the queue count for a few days. Spam that comes back after cleanup usually means a second script or a second set of credentials you have not found yet. The practical habit is to read before you delete. Count the queue, rank cwd=, A=, and U= in exim_mainlog, and let the outlier tell you whether you are chasing a PHP script, a stolen mailbox password, or something running outside any account. Then block that one sender, remove only its messages, and keep going until you know how it got in.