WordPress · Security

A 130-Site WordPress Portfolio Looked Clean. Google Ads Knew Something Was Wrong.

Project snapshot
Client A 130-site WordPress portfolio (anonymised)
Hosting Flywheel
Timeline 15 days to full remediation
Ongoing Monthly security review + weekly audit for high-profile sites
Result 130 sites clean, no reinfections after three months, Google Ads accounts reinstated
Summary

An agency owner who had just taken over a 130-site WordPress portfolio found their Google Ads accounts getting suspended repeatedly, with no obvious cause. Every site looked clean. Every scanner returned clear results. The infection had been running undetected across all 130 properties, silently redirecting paid ad traffic to rogue domains while evading every automated check.

For the new owner, this was not just a security problem. It was a client-retention problem, a paid-media problem, and a trust problem. Each suspension made them look responsible for a situation they had inherited and could not yet explain. We were brought in to find the cause, clean the portfolio, and make sure it stayed clean.

In 15 days, the full portfolio was cleaned, hardened, and documented for Google Ads review. Three months later, all 130 sites remain stable, with no reinfections and no recurring ad suspensions.

The Situation

A client brought us in after taking over a 130-site WordPress portfolio from a previous agency. Every site in the portfolio was running Google Ads. Within weeks of the handover, ad accounts began getting flagged and suspended for policy violations, one after another. For an agency owner trying to establish trust with 130 clients from day one, the timing could not have been worse. The sites loaded perfectly. Traffic patterns looked normal. There were no error messages, no visitor complaints, no server alerts. Yet every single property was compromised. The infection had been running completely undetected.

Why Nobody Caught It

Most malware announces itself. This one was built to do the opposite.

The hidden plugin

The infection came in the form of a PHP plugin with a generic, unremarkable name. It installed itself like any other plugin but used a WordPress filter to remove itself from the admin plugins screen. There was nothing to find in the dashboard. The only way to spot it was through FTP or direct server access. Standard plugin audits, the kind most agencies run, came back clean.

The delivery server only served actual JavaScript to browsers that arrived with active Google Ads session signals. Security scanners got a blank file. Automated crawlers got a blank file. Anyone opening the script URL in a browser tab got a blank file. Every scan said clean because, to every scan, it was. The only people who saw what the malware was actually doing were the ones clicking through from paid ads.

Hidden admin access

Built into the plugin was a hardcoded backdoor. Passing a specific GET parameter in a URL would immediately log the attacker in as the site's first administrator, with no password, no two-factor prompt, nothing. One request. Full access. This meant the attacker could return to any site at any time, regardless of how many times passwords had been reset.

Unauthorised file writes

The plugin registered a REST API endpoint open to anyone on the internet. No authentication was required. Through this endpoint, an attacker could push arbitrary PHP code directly into theme files on the site. The plugin also actively cleared all major caching plugins after each write, so injected code would be live and serving to real visitors within seconds.

Ad-click-only redirects

On every page load, the plugin made a server-side call to an external server, retrieved a base64-encoded URL, decoded it, and injected a script tag at the very top of the page before any other content was sent to the visitor.

Every visitor loaded this script on every page. The redirect logic inside it only fired for visitors who had just clicked a Google Ad. Direct visitors, organic traffic, anyone browsing normally saw nothing wrong.

The destination URL was never stored on the site. It was pulled fresh from the attacker's server on each page load. The delivery domain rotated every day or two. So the cycle ran like this: Google flags a URL and suspends the account, the client moves through reinstatement, the account goes back live, the ads run again, Google flags the new domain, the account is suspended again. The infection kept running through every reinstatement because nobody knew to look for it.

The data-cfasync='false' attribute on the script tag was not accidental. It instructs Cloudflare's Rocket Loader to leave the script alone, which meant the malicious code ran before any Cloudflare processing could touch it.

Built-in reinfection mechanism

We learned early on that simply deleting the plugin was not enough. The malware had already embedded a reinstaller into wp-cron.php that could recreate a fresh copy of itself under a different directory name. It had also embedded AES-256 encrypted PHP payloads inside files in the theme's /patterns/ directory, files that looked like ordinary template files to anyone browsing the folder. Those payloads would only execute if the attacker passed the right decryption key via URL parameters.

We cleaned one site and deliberately left it without the additional security layer to see what would happen. It was re-infected within 24 hours. That test shaped everything that followed.

Where the Infection Came From

The evidence pointed to a compromised WordPress management tool used across the portfolio. Because the same tool was connected to all 130 sites, the exposure spread portfolio-wide. Several of the sites had Wordfence installed and active throughout. It did not detect the infection or prevent re-infection.

The delivery infrastructure revealed an operation with more reach than a single bad actor with a cheap server. The rotating domains hosting the malicious JavaScript files did not belong to the attacker. They were other compromised WordPress sites belonging to unrelated organisations that had no idea their servers were being used. One delivery domain we identified resolved to the same server as a well-known international non-profit. The attacker had added their domain as a virtual host, planted the file, used it for a couple of days, then moved on. By the time any investigation caught up to a particular domain, the file was already gone.

What We Did

We worked through every site individually, completing the full sequence on each one before moving to the next. Partial work on a site was not an option given what the re-infection test had shown. The sequence on each site was file system remediation, database cleanup, credential rotation, and hardening. 130 sites over 15 days.

File system remediation

WordPress core files were reinstalled from verified checksums on every site. Non-core directories were inspected manually and cleared of unauthorised PHP files, including the encrypted payloads in the theme pattern directories and anything else that had no business being there. File editing through the WordPress admin was disabled at the configuration level to close that route for future writes.

Database remediation

The injected JavaScript strings were located and removed from the wp_posts and wp_options tables on each site's database. This required care to strip the malicious content without corrupting legitimate data. The conditional redirect hooks came out at the source.

Credential rotation and hardening

All active sessions were terminated, unauthorised admin accounts removed, and credentials rotated across every property. A consistent security configuration was then applied to each site to close the attack surfaces the malware had used: login protections, file permission changes, attack surface reduction across the WordPress configuration, and invalidation of all existing sessions. The configuration that had been missing from the re-infection test site was in place on all 130 by the end.

The Outcome

Three months on, all 130 sites remain clean. No re-infections, no redirects, no rogue admin accounts.

We provided the technical remediation evidence the client needed to move through the Google Ads reinstatement process. Once the domains were verified clean and the documentation submitted, the accounts came back without the recurring suspensions that had defined the previous weeks.

There is now an ongoing engagement covering the full portfolio. High-profile sites get a weekly manual audit: file integrity checks, security log review, user account monitoring, and backup validation, with a written report after each session. The wider portfolio gets a monthly review cycle.

The reason the client wanted ongoing cover is straightforward. The infection ran without a single visible indicator for weeks. The only thing that flagged it was Google pulling the ad accounts. Once you have seen an attack run that quietly for that long, the question of whether your current setup would catch something similar becomes a lot harder to answer confidently.

What This Means for Agency-Managed Portfolios

If you manage WordPress sites for clients who run paid advertising, the same conditions that allowed this infection are common across agency-managed portfolios.

There is no visible signal

The WordPress admin may look clean. The site may load normally. Scanners may report no issue. Paid traffic can still be compromised. Infections like this typically run for weeks before anything surfaces, and when they do, it is usually a Google Ads suspension that reveals it, not a security alert.

Your management tool can become a single point of failure

Centralised management is efficient, but if the tool is compromised, exposure can spread across every connected site simultaneously. The assumption that a trusted platform is safe to use is not the same as knowing it is.

Cleanup without hardening creates a loop

Removing malware is not the same as closing the entry point. In this case, a cleaned site without the added hardening layer was re-infected within 24 hours. The entry points have to be closed, or the cycle starts again.

Automated tools are not enough

This infection was designed to hide from scanners and the WordPress admin. Finding it required direct file system review, database inspection, and behavioural testing. Active security plugins on multiple sites detected nothing throughout the entire infection period.

Managing a WordPress portfolio with paid traffic behind it? YB Marketing helps agencies uncover hidden malware, recover compromised sites, and harden portfolios before silent infections turn into suspended ads, emergency calls, and client-retention problems.