CASO DI STUDIO · PROFESSIONAL SERVICES

From Vulnerable Plugin to Root: A WordPress Site on an Unsupported CentOS 7 Server

  • CLIENTProfessional firm (anonymized data)
  • PLATFORMWordPress

A neglected plugin, a webshell and a local privilege escalation never patched on CentOS 7: the server was root-compromised. WordPress cleaned within 12 hours, then migrated.

A server labeled CentOS 7 EOL and a calendar frozen at June 30, 2024; beside it, a staircase of ever-higher steps, plugin, webshell, pkexec and root, climbs to a crown

Background

A professional firm of about twenty people, including partners, associates and administrative staff. The WordPress site presented the firm, published in-depth articles and collected contact requests through a form. It ran on an unmanaged virtual server set up in 2019 by an outside consultant who had since stopped looking after it: CentOS 7, Apache, a newer PHP than the distribution’s own taken from a third-party repository, MariaDB and no control panel. For confidentiality, the firm’s name, its area of practice and details that could identify it have been omitted or changed.

The decisive detail of this story is the date. CentOS Linux 7 reached end of life on June 30, 2024: as the CentOS project explained in its announcement of the end-of-support dates, from that day on no more updates are published, and packages remain only in a frozen archive. In our case, though, the server had been stalled far earlier: the last patches installed dated from the end of 2020, when a conflict between the PHP packages from the external repository and the system ones had made an update fail, and nobody had tried again.

The symptoms

The first signal didn’t come from the site, but from the virtual server provider: an abuse report pointing to SSH login attempts against other servers originating from the firm’s IP address. In the following days some emails sent by the site, such as contact form confirmations, had started landing in spam or being rejected, because the address had ended up on a blocklist. The site was slower than usual, but apparently normal: no modified pages, no redirects.

An associate at the firm had tried to “update everything,” but yum had stopped with a mirror error, the mirrors having disappeared precisely because the distribution was no longer supported. At that point the firm contacted us and chose the service with intervention within 12 hours, also asking us to find out whether the problem affected only the site or the entire server.

What we found at the application level

Analysis of the Apache logs, done as described in the article on access logs as evidence of the attack vector, quickly led to a photo gallery plugin stuck at a version three years old, with a known, public vulnerability that let anyone, without authentication, upload a file with an arbitrary extension. In mid-July, from an address never seen before, a POST request to the plugin’s upload endpoint was followed a few seconds later by a GET to a PHP file in the uploads folder.

That file was a webshell: a few obfuscated lines that ran commands received in a parameter. Next to it were two more, with names imitating image thumbnails, and an .htaccess file that re-enabled PHP execution in the folder. The database was essentially clean: no added administrators and no code in options or posts. Whoever got in wasn’t interested in the site itself: they needed a foothold on the server. And with the permissions of the apache user, which the site ran as, there’s little you can do. The next step was to become root.

What we found at the system level

When an attacker may have become root, the compromised system’s tools are no longer reliable: a rootkit can make ps, ls and ss lie. That’s why we didn’t work on the running server. We asked the provider for a disk snapshot, mounted it read-only on a clean analysis machine and compared every file against the original packages. The investigation of logs and suspicious files was sped up by open-weight AI models run locally, without any of the firm’s data leaving our infrastructure.

# Disk image mounted read-only on /mnt/caso
cat /mnt/caso/etc/centos-release
rpm --root /mnt/caso -q polkit sudo kernel

# Libraries loaded into every process, if the file exists
cat /mnt/caso/etc/ld.so.preload

# Package files whose content differs from the original (excluding configuration files)
rpm --root /mnt/caso -Va 2>/dev/null | grep -E '^..5' | grep -v ' c /'

# Persistence: systemd units, system cron, root's keys
ls -la --time-style=full-iso /mnt/caso/etc/systemd/system/ /mnt/caso/etc/cron.d/
cat /mnt/caso/root/.ssh/authorized_keys

The picture that emerged was complete. The polkit package was at the version before the PwnKit fix (CVE-2021-4034), a local vulnerability in pkexec that lets any user on the system obtain root privileges; sudo was also older than the Baron Samedit fix (CVE-2021-3156). In the /var/log/secure log, two minutes after the webshell first ran, there was a pkexec message about an environment variable with “suspicious content”: it’s one of the traces that the original advisory from Qualys researchers lists as consistent with exploitation of that vulnerability. In the apache user’s temporary folder, the working files of one of the public exploits remained.

As root, the attacker had built multi-layered persistence:

  • a shared library in /usr/lib64, with a name similar to a system library, listed in /etc/ld.so.preload: it was loaded into every program and hid the attacker’s own files, processes and connections;
  • the ps, top and netstat binaries replaced with modified copies, which rpm -Va flagged with a checksum different from the original;
  • a systemd unit enabled at boot, with a system-service-style name, that launched an executable hidden in /var/lib;
  • a file in /etc/cron.d that re-downloaded the executable every ten minutes if it was missing;
  • a foreign SSH key appended to the end of /root/.ssh/authorized_keys, on a server where key-based root login was still allowed.

The executable was an agent with two functions: a cryptocurrency miner limited to part of the CPU, to avoid attention, and a module that scanned other servers for weak SSH passwords, which is exactly the activity the provider had reported. The outbound connections to a command server on port 443 were invisible from the server itself, because the preloaded library filtered them, but they showed up clearly in the traffic logged by the provider. Anyone who has read our article on cryptominers on servers will recognize the pattern.

Attack chain in five steps from left to right. One: outdated gallery plugin, file upload without authentication. Two: webshell in uploads, run as the apache user. Three: local escalation through polkit's pkexec, package stuck at 2020, never patched. Four: root. Five: persistence, with ld.so.preload, replaced ps, top and netstat commands, a systemd unit, a file in cron.d and a root SSH key; from here start outbound connections to a command server, a miner and SSH scans against other servers. Below, a bar with the dates: last patches at the end of 2020, CentOS 7 end of life on June 30, 2024, entry in mid-July 2026, abuse report in early August 2026.
From plugin to root in a few minutes: the site was only the way in, the target was the server.

How we removed it

Here is the decision that sets this case apart from an ordinary cleanup. On a system where the attacker has been root there is no reliable way to say “it’s clean now”: they may have modified the kernel, the packages, the logs, even the package database used for verification. And even if we had removed everything we found, the operating system would have remained exposed to the same vulnerability and to future ones, because it no longer receives fixes. Putting the site back up on that server would have meant cleaning a house and leaving the door without a lock.

We therefore cleanly separated two activities, and put it in writing with the firm before starting:

  1. the WordPress cleanup, covered by the service within 12 hours: from the disk image we extracted only the data, namely the database dump, the uploads and the custom theme. The core was rebuilt from official packages, the plugins reinstalled from the official repository (except the vulnerable one, which was replaced), and the theme compared line by line with the copy supplied by the developer. From the uploads we removed every executable file and checked that the images were really images; in the database we verified users, options, triggers and content;
  2. the migration to a new server, agreed as a separate activity and outside the guarantee: a new virtual server with AlmaLinux 9, a supported distribution, installed from scratch, onto which the cleaned site was published and verified before the DNS switch.

Before shutting down the old server we preserved the evidence: the disk image with its checksum, the logs, the malicious library, the executable and the list of contacted addresses, which we passed to the provider to close the abuse report. The old server was isolated from the network and then destroyed after thirty days, in agreement with the firm.

All credentials were changed, on the assumption that every secret present on the server had been read: database password, WordPress keys and salts, site user passwords, SMTP credentials saved in the mail plugin, SSH keys, access to the provider’s panel. Because the contact form collected personal data from prospective clients, we gave the firm the technical elements to assess, with its own privacy advisor, any notification obligations.

Hardening

The new server was configured so as not to repeat any of the old one’s mistakes:

  • PHP from the distribution’s official modules, with no external repositories, and security updates installed automatically, with a notice when a reboot is needed;
  • SSH access with keys only, root excluded, port reachable only from the firm’s and support’s addresses;
  • SELinux in enforcing mode, which on the old server had been turned off “because it caused problems”;
  • PHP execution blocked in uploads, as explained in the article on webshells in uploads, and file permissions separated between who owns the files and who runs them, following the scheme in the article on hardening wp-config.php;
  • a firewall that also limits outbound traffic: a brochure site has no need to open SSH connections to the Internet;
  • monitoring of file integrity, processes and connections, with alerts sent from outside the server.
Comparison between the old and the new server. On the left, in red, the old one: CentOS 7 unsupported since June 30, 2024, last patches in 2020, PHP from a third-party repository, SELinux disabled, root SSH access with a key, no limit on outbound traffic, no monitoring. On the right, in green, the new one: supported AlmaLinux 9, automatic security updates, PHP from the distribution's modules, SELinux active, SSH with keys only and no root from authorized addresses, firewall also on outbound traffic, monitoring of files, processes and connections. Between the two, an arrow: data extracted and verified, WordPress rebuilt from official sources.
Only the data was carried over from the old server, verified one by one: everything else was rebuilt from official sources.

The result

The cleaned WordPress was ready within the service’s 12 hours, already published on the new server at a test address. The migration, with the DNS switch and checks on the contact form, email and certificate, closed the next day. The provider closed the abuse report, the new server’s address appears on no blocklist, and over the following thirty days monitoring recorded no unauthorized changes or anomalous connections.

The firm received a report with the reconstruction of the attack, the list of indicators of compromise, the preserved evidence and the measures taken, along with a short maintenance plan: who updates what, how often, and who monitors the alerts.

What this case teaches

An unsupported operating system isn’t just “old”: it’s a system where every local vulnerability discovered from that moment on stays open forever. In our case the fixes for PwnKit and Baron Samedit existed but had never been installed; on CentOS 7, after June 30, 2024, for the next vulnerability of that kind there won’t even be a fix to install, short of moving to Red Hat’s paid extended support or to another distribution. And when a vulnerability in the site combines with one in the system, a neglected plugin becomes root access to the entire server.

The second lesson is about trust: a server where an attacker has been root isn’t cleaned, it’s rebuilt. It’s more work, but it’s the only way to honestly say the system is under control.

Malwarecleanup.com is the WordPress malware removal service of Managed Server S.r.l., which has been running servers and hosting since 2005: we work on both the application and the system, with guaranteed times of 6, 12 or 24 hours for each WordPress installation, counted from receipt of the access credentials. When a case requires work beyond cleanup, such as a migration, we say so beforehand and agree it separately. If your server is still on an unsupported distribution, or you suspect a compromise, you can write to us.

THE NEXT STEP

Is your site
infected?

Every hour counts: the malware keeps hitting visitors and customers, and Google, your host and the blacklists won’t wait. Choose a plan, pay online and share your access: the cleanup starts right away, at a fixed price and with guaranteed times.

Request a cleanup

Takes you to the plans: cleanup within 6, 12 or 24 hours of receiving access.

MANAGED SERVER S.R.L.info@malwarecleanup.com +39 02 5656 9681

Monday – Friday · 9:30am – 7:30pm Italian time (CET)

Prefer an online form?
Contact us at managedserver.it ↗