Me, Claude, and the Malware That Wouldn’t Die

The owner of a small music site and its companion WooCommerce t-shirt store came to me with a problem that had already beaten one developer. Visitors kept hitting a fake Cloudflare popup, the "verify you are human" kind that tries to get you to paste something into a box you should never paste anything into. It would get cleaned off, everything would look fine, and then a day or two later it was back. The store was the worst of it, which made sense later, because that is where the infection actually lived.

If you have ever cleaned malware off a WordPress site and watched it return, you already know the feeling. You did everything right and it came back anyway. That is not bad luck. That is a sign you removed a symptom and left the cause.

I went at it differently this time. I ran the investigation alongside an AI assistant, Claude, from Anthropic. Not as a gimmick, and not to write my emails. As a genuine second set of eyes that could read an entire site, cross-reference every user and file, and recognize a pattern in seconds, while I made the judgment calls, kept it honest, and did the hands-on work on the server. That pairing is most of the reason this cleanup finally stuck, so it is part of the story I am telling here.

It Was Never One Piece of Malware

The reason nothing stuck is that this was not a single bad file. It was a kit. One break-in, months earlier, dropped a set of cooperating parts, and the file timestamps all landed within the same few seconds, so it was clear they had been installed together in one shot. Remove any one piece and the others quietly rebuilt it. That is the whole design. Persistence by redundancy. Seeing that shape quickly, rather than treating each symptom as its own problem, was the first thing working with a fast analytical partner bought me.

Here is what was actually in there.

A password logger wearing an image costume

Tucked into the bottom of the store theme's functions.php was a small block of code with an innocent "session analytics" comment over it. It hooked into WordPress's login process and, every time anyone logged in successfully, wrote their username and password in plain text to a file. Not to an obvious .php file someone might notice. To a .png in the uploads folder, sitting among the product images, with the destination path base64-encoded in the code so a quick search would not catch it.

That fake image had captured 190 logins by the time I opened it. Mine and the owner's were in there too, from the cleanup attempts. This one file is the reason every previous fix failed. You can change every password on the site, but if the thing recording the new passwords is still running, you have simply handed the attacker a fresh list.

A session thief that did not need passwords

In the mu-plugins folder, which loads automatically and which most people never look at, was a second component disguised as a caching helper. Whenever a logged-in administrator loaded a page, it scooped up their session cookies and quietly shipped them off to a handful of throwaway domains dressed up to look like a web-analytics CDN, the sort on cheap extensions no legitimate analytics company would ever use. Stolen cookies are as good as a login and they sidestep passwords entirely, so even a clean password reset would not have locked this attacker out.

The popup machine and the .user.ini trick

Now the part that actually explains the resurrection. WordPress allows a file called .user.ini to tell PHP to automatically run a chosen file before every single page request. The attacker had pointed that setting at a hidden PHP file with a harmless, cache-sounding name. That hidden file was the popup loader, the thing that decided when to show the fake Cloudflare screen based on your browser, where you came from, and the web address you used.

By the time I arrived, a previous cleanup had deleted the loader file itself. But nobody had removed the .user.ini that pointed at it. So the trigger was still armed and waiting. The attacker only had to drop one file back in place and the popup would instantly return, no re-hacking required. That single overlooked line is why "I removed the popup" kept turning into "the popup is back."

A spare key and a decoy

Rounding it out were two more things: a fake administrator account the attacker had made for themselves, registered in the same minute as everything else, and an inert decoy file planted alongside the working pieces, presumably to look like one more harmless plugin if anyone went poking.

Where the Human Half of the Team Earns Its Keep

I want to be honest about the parts that were not clean, because security work never is, and because they show how the human and AI sides of this actually split the load.

Early on, we flagged an administrator session from an IP address that geolocation placed in Greece and treated it as a likely intruder. I spent a while on it before matching the address against my own connection and realizing the data-center IP was me. False alarm. Confirm before you accuse, even when you are the one doing the investigating.

The better example is the login limiting. The store already had a well-regarded security plugin installed, and Claude, reading the plugin's settings straight out of the database, reported that login-attempt limiting was on, with sensible lockout numbers. On paper, done. But it nagged at me, because I was fairly sure that particular limiter was a paid feature in that plugin. We went back and checked, and I was right. The plugin only enforces the limiter on its paid tier, and the tidy numbers sitting in the database were inert defaults doing nothing at all. A stored setting is not an enforced setting. We swapped in a dedicated free limiter instead, and then verified it actually locked out a repeated bad login rather than trusting the screen.

That is the whole case for working this way, in one moment. The AI was fast and the AI was confidently wrong, and a human who knew the tool caught it. Speed from one side, judgment from the other. Neither half does this job as well alone.

Pulling It Out By the Roots

Removal was the easy half once we understood the shape of it. We mapped every piece together, then I pulled them off the server one by one: the logger block in the theme, the fake image stuffed with passwords, both mu-plugins, the .user.ini trigger, and the backdoor account. Then we re-scanned to confirm nothing had been re-dropped.

The half that actually keeps a site clean is closing the door the attacker came and went through. Because that logger had been harvesting credentials for who knows how long, every password on the store had to be treated as already burned. So passwords were changed on both sites. The internal security keys, the secret values WordPress uses to sign login sessions, were rotated, which instantly invalidated every stolen cookie in one move. The backdoor admin was deleted, and a couple of stale old developer accounts that no longer needed access were removed too, after reassigning their content first so nothing was lost. When the dust settled, each site had exactly two users: the owner and me. And a real login limiter went on both, so the automated password-guessing that feeds attacks like this one now gets a few tries and a timeout.

Trust, But Verify the Popup Is Actually Gone

Saying malware is gone is easy. Proving it is the job. The popup had been conditional, showing itself only for certain browsers, certain referrers, and certain web-address parameters, which is exactly how these things hide from the site owner while still hitting real visitors.

So we scripted the test matrix and ran it in about a minute. Both sites loaded as a desktop browser, as an iPhone, as an Android phone, arriving fresh from a Google search and from a social link, with junk parameters tacked onto the address, and as a search-engine crawler. In every combination the real page loaded and the popup did not appear. The pages served to the "victim" conditions were byte-for-byte identical to a plain visit, which is the tell that nothing is being shown selectively anymore. On top of that, we confirmed at the file level that the trigger and every planted piece were gone and had not come back.

Clean, and verified clean, which are two different claims.

What This Site Taught Me

If you take one thing from this, take this: when WordPress malware keeps coming back, stop re-deleting the file you can see and start hunting for the mechanism that re-creates it. Modern infections are kits, not single files, and the resurrection trick is usually something boring and easy to miss, like a one-line .user.ini that auto-runs a file on every request.

A few more, quickly. Treat every password as compromised the moment you find a logger, and rotate the security keys, not just the passwords, so stolen sessions die too. Do not trust a security plugin's settings screen to tell you what is actually enforced, especially when the useful features sit behind a paywall. And never call a site clean until you have reproduced the exact conditions the malware used to hide, and watched it stay gone.

And one more, about how the work got done, because people keep asking me about the AI part. Here is my honest take after this job: an assistant like Claude is a real force multiplier for this kind of work, because it can read a whole site and surface patterns far faster than I can by hand. But it is a partner, not an oracle. It told me a protection was active when it was not, and my experience caught that, while its speed caught things that would have cost me hours. I am not going to pretend I did this alone, because I didn't, and I am not going to pretend a machine did it either, because it didn't. It was a person and an AI working as a team, and the site is clean because of both.

The popup that would not die finally did. Not because I found a magic file, and not because an AI found it for me, but because the two of us stopped chasing the symptom and pulled the whole root system out.

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Post Search

Follow Us

Feel free to follow us on social media for the latest news and more inspiration.

Related Content