WordPress ClickFix Reinfection Case Study: A Stolen Login, a Forgotten Admin, and a Spoofed Domain

img

A small law firm’s WordPress site had been professionally cleaned more than once, and the ClickFix malware kept coming back within days. There was no self-healing virus underneath this one though…it was just a person with a valid password walking back in through the front door. Here’s how I found the real root cause, locked the attacker out for good, and cleaned up the reputation damage without taking the site offline.


A Cleanup That Never Held

The message came through Upwork. A web developer who looks after the website for a small US law firm had a problem he couldn’t get rid of: the firm’s WordPress site kept getting infected with a fake “verify you are human” prompt that quietly poisoned their clipboard. The host’s malware team had cleaned it (more than once) but each time within a few days it came back.

By the time the developer reached me, the trouble had spread well past the website. Google had begun showing a red “dangerous site” warning to anyone who tried to visit and the firm’s own email address was reportedly turning up in clients’ inboxes sending messages nobody at the firm had written.

Before we started, the developer asked for two things: a 30-day guarantee that the site would remain free of reinfection, and a plan for what would happen if I could not find a persistence mechanism. Both were reasonable so I agreed that if the site was reinfected through the same or a related vector within 30 days of my sign-off, I would re-investigate and remediate it at no additional cost provided the security changes stayed in place, nobody installed nulled software or reintroduced the problem, and I was notified promptly if anything looked wrong.

I also explained that failing to find an active backdoor wouldn’t leave us at a dead end…it would simply change the investigation. If nothing on disk or in the database was restoring the malware, I would pivot to the other ways an attacker could keep returning: stolen credentials, an exposed login, or a vulnerable component being exploited again.

Anyway, I had SSH access to the server, a managed WordPress install on a Cloudways-hosted droplet, and the host’s own incident reports from the previous cleanups. That was a good place to start.


What ClickFix Actually Does

It helps to know what was being served because it shapes where you look.

ClickFix is a social-engineering payload, not a traditional drive-by. The injected code overlays a page with a convincing “verify you are human” or “fix this error” panel, the kind of thing people have been trained to click without thinking. The trick is in the instructions…it tells the visitor to press a keyboard shortcut and paste, and it has already loaded a malicious command onto their clipboard. The victim runs it themselves on their own machine which sidesteps most browser protections because the browser never delivered the malware. The site is only the lure.

That detail matters for an investigation. The malware isn’t sitting in a file being served to browsers in the usual sense. The injection is small, it lives where it can reach every page, and the real prize for the attacker is persistence: the ability to keep re-injecting after someone cleans it up. So persistence was what I went looking for.


Reading the Host’s Own Reports

The previous cleanups had been thorough on paper. The host’s reports described a genuinely nasty setup: a backdoor planted in the WordPress options table acting as a credential harvester and re-infector, dozens of obfuscated JavaScript payloads, and dozens of rogue plugin “stub” directories that according to the report regenerated every six hours. Their team had backed up the database, removed the malicious rows, deleted the rogue files, and verified the homepage rendered cleanly.

Two words in that report told me where the real problem probably was: “self-healing.” Something that regenerates every six hours isn’t a static infection…that implies a generator (a piece of code or a scheduled task that recreates the payload on a timer). If the cleanups kept removing the output but the generator survived, that alone would explain the cycle.

The reports also mentioned that all the evidence had been preserved under a folder in /tmp. That would have been gold but it was long gone, cleared out by a reboot weeks earlier so the live system and its logs were going to have to tell the story themselves.


The Site Wasn’t As Clean As Everyone Thought

The first thing I did was list what was actually on disk. The plugins directory held the usual recognizable names, the theme’s companion plugins, a form plugin, a caching plugin, a security plugin, and then a cluster of directories that didn’t belong: names built from generic technical words with a short random suffix, things like site-render-booster-3c6a, ultra-query-engine-64d8, page-database-scanner-5676…eleven of them altogether. Every legitimate plugin had a normal name and everything with a four-character hex tail matched the exact naming convention the host’s report had attributed to the malware.

So the site wasn’t clean…it still had eleven rogue plugin directories sitting in it, except…

When I opened them each one held a single PHP file and every one of those files was empty so these were husks, and the site’s list of active plugins still contained an entry for one more rogue plugin whose folder had already been deleted, a dangling reference pointing at nothing.

That combination was the first real clue. The malicious code wasn’t in these files; it had lived in the database and the files were just markers left behind, either scaffolding the generator built before filling them or debris the cleanups had half-removed. The dangling active-plugin entry was worse than debris because leaving it in place meant that if the attacker ever dropped that one folder back onto the server, WordPress would load it automatically with no activation step required.


But the Generator Was Gone

If the site was self-healing the generator had to be somewhere, so I went looking for it.

WordPress core verified cleanly against the official checksums. The plugins that could be checked against the official repository verified too and the premium ones that couldn’t be checked showed no injection signatures when I scanned them. The uploads folder, the theme, and the must-use plugins directory held no web shells, no eval of encoded input, none of the usual backdoor patterns. The database was clean of injected content: no malicious options, no rogue scheduled task rebuilding anything, no leftover application passwords, no active login sessions belonging to an intruder.

This was the part that reframed the whole case; the self-healing generator wasn’t there so whatever had been recreating those stubs every six hours had stopped, most likely broken by the last cleanup which had removed the loader along with the output. The husks were dated to a stretch in June and nothing newer had appeared.

So if the automated re-infector was dead and the site was genuinely clean of active malware, why did the developer say it kept coming back? Something was still walking in, it just wasn’t a file.


The Users Table Had One Account Too Many

I pulled the list of administrators. There were the two I expected (the firm’s owner and the developer) and there was a third… an account named root with a placeholder email address registered earlier than either of the legitimate admins.

What made it interesting was that it had no role. In the normal Users screen and in the site’s public data it didn’t register as an administrator at all and the reason was buried in how WordPress stores permissions. This site used a custom database table prefix (a common hardening step) and every legitimate account’s capabilities were filed under that custom prefix. The root account’s administrator capability was filed under the default wp_ prefix instead. WordPress reads roles from the current prefix so it simply didn’t see this account’s role. The account was invisible in every standard interface, including the public user-listing endpoint that had been leaking the real admin usernames to the internet.

It could still log in, and it had; its saved session history showed logins from France and Indonesia on dates that matched nobody at a US law firm or its developer.

A hidden administrator account with a fake email, foreign logins, and a name like root looked exactly like a planted backdoor.


A Wrong Turn, Corrected

I was partly wrong and the client is the one who corrected me.

When I flagged the root account as an attacker-planted hidden admin, the firm’s side came back with context I didn’t have: that account had been created when the site was first built years earlier by the original developer and had simply never been removed when the newer admin accounts were added.

That actually fit the evidence better than my first read. The odd wp_ prefix on its permissions wasn’t attacker trickery but rather the fingerprint of an account that predated the database-prefix hardening and never got migrated with the others.

But the correction sharpened the finding rather than dissolving it. An old, forgotten administrator account with a stale password, unmonitored and unused, is one of the most reliable ways attackers establish a foothold. The foreign logins were still real and still unexplained by anyone legitimate. Whether or not it was where they first got in, it was a live key that the firm didn’t know it still had so it needed to go regardless. And the timeline with those overseas logins landing months before the reinfection wave the developer had called me about suggested this quiet, leftover account was the likely original point of entry.

I mention the misread deliberately. The honest version of this work includes the moments the first theory is wrong. The point of the investigation is that the evidence, and sometimes the client, keeps you honest.


The Logs Told the Whole Story

The account explained access and it was the server logs that explained the reinfection cycle completely.

I pulled every successful login the access logs still held and lined the timestamps up against the moments the rogue plugin folders had been created on disk and they matched one for one. Over roughly a week in June, each rogue plugin folder was created within seconds of a successful administrator login from a fresh IP address. A login, then a folder; adifferent login from a different address, then another folder. The single address that logged in repeatedly turned out to be the developer doing legitimate work and every one-time login lined up with an infection event with each coming from a different datacenter or VPN address for each visit which is how you dodge IP blocking.

Then I pulled one of those sessions in full, request by request, and watched the technique play out. Log in, try to open the security plugin’s settings, and get blocked. Then the ordinary WordPress workflow: open the “upload plugin” page, post a plugin zip file to the installer, and activate it. That was it, that was the whole attack…no exploit, no clever payload, no vulnerability; the attacker was logging in as an administrator with a valid password and using WordPress’s own built-in feature to upload a plugin, and that plugin re-injected the ClickFix code.

Every reinfection the firm had suffered was a person logging in and clicking upload.

There was one more detail in the logs that mattered. After the last host cleanup, one of those login attempts had started failing. A password had changed somewhere along the way and one of the attacker’s keys had stopped working which is why the site had been quiet in the weeks just before I arrived, but the failed attempts kept coming all through the following weeks from the same infrastructure. If any other harvested credential still worked or they guessed a new one the cycle would resume.

That’s why every previous cleanup failed. They removed the malware which was the symptom and never changed the credentials which were the key. The intruder didn’t need a backdoor because the whole time he simply just had a login.


Changing the Locks

Once the mechanism was clear, the remediation was straightforward and I did it on the live site without an outage. Evidence first, then removal, then the part that actually mattered: invalidating every way back in.

  1. Preserved evidence. Exported the hidden account and its metadata, archived the rogue folders, and saved the before-state, on the server and off it, before deleting anything.

  2. Removed the artifacts. Deleted all eleven empty rogue plugin folders and purged the dangling active-plugin entry that was waiting to auto-load a re-dropped folder.

  3. Deleted the forgotten account. Removed the root administrator entirely, reassigning its (nonexistent) content to the firm owner.

  4. Changed the locks. Reset both legitimate administrators' passwords, regenerated all of the site's secret authentication keys, and destroyed every active session. The moment that ran, every stolen password and every saved login the attacker might have held stopped working at once.

  5. Rotated the rest of the secrets. Coordinated a rotation of the database password with the host panel, and verified the site reconnected cleanly.

  6. Closed the front-of-house exposures. Blocked the public endpoints that had been leaking administrator usernames, and added a set of modern browser security headers across the whole site, delivered through a single small must-use plugin so nothing in the existing configuration was disturbed. I verified the headers were present on cached pages, dynamic pages, and redirects, and confirmed the site still worked exactly as before.

The whole sequence is reversible in the sense that mattered to a working law firm: nothing about it could take the site down and nothing about it changed what visitors saw; it only changed who could get in.


The Email Was Never the Site

That left the most alarming symptom to the firm’s owner: his address apparently emailing his own clients.

The site sends its transactional mail through a third-party service so I pulled that service’s complete sending logs across the entire incident window and read every message. All of it was legitimate: contact-form replies, system notifications, weekly summaries. There were no mass sends, spam, unusual recipients, and none of the bounce and complaint patterns you would see if the account were being used to blast a client list and so it was safe to say the sending key hadn’t been abused.

This meant the impersonation wasn’t coming from the website at all but was rather external spoofing. Anyone on the internet can forge a “From” address, and this domain had almost nothing stopping them: no SPF record at all and a DMARC policy set to take no action. Under those conditions, a forged message claiming to be from the firm sails straight through.

The fix lived entirely in DNS. I added a correct SPF record authorizing only the firm’s real mail providers, confirmed DKIM signing was in place for both the mailbox provider and the transactional service, and stood up DMARC. That combination tells receiving mail servers to reject anything that forges the domain. It also happens to matter for the site because the domain’s own reputation had taken damage and legitimate mail carrying links to it was at risk of being filtered.

I put the anti-spoofing policy into monitoring mode first; before you tell the world’s mail servers to start rejecting anything that fails the check you want to see who is actually sending under the domain’s name so you don’t blindside something legitimate you had forgotten about. The reports came back clean and that cleared the way to tighten the policy from watching to actively rejecting.


Blocked on Reputation

Because the site had spent weeks flagged as a malware host, I checked the domain and the server against the major reputation and email blocklists, dozens of them.

The server’s own IP address came back essentially clean. The one or two lists it appeared on were policy and provider-level listings, the kind that flag entire hosting networks regardless of the individual site and they had no bearing on this case. The domain was a different story: it was listed on two significant blocklists, flagged specifically as a compromised and phishing-associated domain. That was expected as it was the reputation scar left by the infection and it’s exactly why clearing the malware is only half the job.

With the site genuinely clean and secured, those listings could finally be cleared. I submitted the delisting requests which the lists honor once a site is verified clean and no longer serving anything malicious, and started the review to lift the browser warning. Within days the domain dropped off those blocklists and came back clean.

Reputation recovers on a delay though and not every part of it moves at the same pace. Deliverability testing told the fuller story. Mail from the domain now passes every authentication check at the major providers, and both Gmail and Proton deliver it straight to the inbox. Microsoft (arguably the strictest of the large providers) still routed a test message to the junk folder even after the public blocklists had cleared because it keeps its own private reputation history and forgives a recently compromised domain slowly; nothing in a DNS record fixes that one, it heals with time and ordinary, legitimate sending. That’s the long tail of an incident like this: the malware comes out in an afternoon, and the reputation takes weeks to fully mend.

What I had checked was the reputation layer everyone checks: the email and URI blocklists, and the browser warning. There’s another layer underneath those though, one that almost nothing in the standard toolkit looks at, and it took a few more weeks to show itself.


The Certificate Was Never the Problem

Weeks after sign-off, a new symptom surfaced and on its face it had nothing to do with any of this.

The firm’s owner kept hitting an error on his phone: “This site can’t provide a secure connection.” Underneath it, in smaller grey type, ERR_SSL_PROTOCOL_ERROR. Around the same time Google Ads began refusing the firm’s own web address, reporting the destination as unavailable and leaving the campaigns unusable.

An SSL error, plus an ad platform rejecting the link, reads like a certificate problem. My first instinct was that it was something local to his phone or his network, and that is what I said. It was a comfortable answer and it was half right, but that’s the least useful kind of right because it left him staring at an error he couldn’t get past.

It kept nagging at me, so I went and checked properly instead of waving it off.

The certificate turned out to be in perfect health…valid, issued by Let’s Encrypt, covering both the bare domain and the www version, chaining correctly to a trusted root, two months from expiry, negotiating cleanly on modern protocols and correctly refusing the obsolete ones. I ran fifteen handshakes back to back looking for an intermittent fault and all fifteen succeeded. Both versions of the address served a healthy page. Google’s own advertising crawler fetched the site without complaint.

Nothing was wrong with the certificate but something was still very wrong for the firm’s owner.

The answer wound up not being on the server at all…it was actually in DNS.

A number of the big public DNS resolvers do threat filtering as a feature. Cloudflare’s security resolver, Quad9, AdGuard, Control D and others simply refuse to resolve domains their threat feeds have flagged. Enormous numbers of people use them without ever having chosen to because they ship as defaults in home routers, phone configuration profiles, and privacy and parental-control apps.

So I asked each of them where the site lived: Cloudflare’s filtered resolver answered 0.0.0.0, a null; Quad9 answered that the domain did not exist at all; AdGuard handed back the address of its own sinkhole server; Control D returned a null as well.

Then I did the thing that turns a suspicion into proof: several of these providers run an unfiltered resolver alongside the filtered one so I asked both. The unfiltered ones returned the site’s real address instantly, no hesitation. Same question, same provider, two different answers which means this wasn’t a DNS fault but rather was a policy decision.

And it explained the SSL error exactly. When his phone asked for the address and was handed a null or a sinkhole, the browser went ahead and tried to connect. It opened a secure connection to a machine that held no certificate for this domain, the handshake collapsed, and the browser reported the only failure it could actually see: a secure connection error. The certificate was fine the entire time, it’s just that his phone was simply never talking to the site.

Cloudflare’s public categorization tool removed any remaining doubt. It listed the domain under Phishing.

I assumed the Google Ads rejection was caused by the safe browsing flag the site had earlier but that assumption turned out to be wrong, and I didn’t find out until later. Safe Browsing had already cleared so whatever was stopping the ads was sitting in the ad account rather than in the site’s reputation. I mention it because assuming the obvious cause is the exact mistake this case keeps punishing.

The part worth sitting with is how invisible this was. A DNS-level block leaves no trace on the server…there’s no log entry, no failed request, no traffic dip you can point at because the visitor’s browser never reaches you at all. Uptime monitoring doesn’t catch it either since monitors use ordinary resolvers and see a perfectly healthy site. From the inside, everything looks fine. The only symptom is a person telling you they can’t reach a website that you can load without any trouble at all.

Which is exactly why the reflex to say “it must be something on your end” is so dangerous in this work To be clear, it’s often true and it was even partly true here but it’s also the sentence that stops an investigation one step before the actual answer.

None of this was a new compromise. The site was clean and had stayed clean; every check I ran confirmed it. This was the infection’s shadow still being cast by threat feeds that had never been told the story had ended. Blocklists are quick to add and slow to forget, and none of these providers clear themselves automatically which means somebody has to go and ask.


Thirteen Vendors and a Spam Filter

Soooo I started asking….a categorization change with Cloudflare, a removal request with Quad9, a public issue with AdGuard, a support ticket with Control D. Each submission is stronger with proof attached, and the cleanest proof available was that filtered-versus-unfiltered comparison because it isolates their blocklist as the only variable using their own two servers.

Cloudflare cleared the domain inside a day; both of its filtering resolvers began answering normally and since its security resolver is the one that ships as a default in consumer routers and phone profiles, that was almost certainly the one in the owner’s path. He tried the site again and it loaded with nothing changed on his end. The symptom that started all of this was gone.

Quad9 refused, and it was the most useful thing anybody said to me during the whole stretch.

They stated the domain appears on at least ten separate threat lists so they wouldn’t be adding an exclusion for it and if the site’s administrator wanted it delisted he needed to go to the organizations publishing those lists.

That reframed the problem completely: Quad9, Cloudflare, AdGuard and Control D don’t independently decide that a law firm’s website is dangerous but rather subscribe to threat intelligence from security vendors and act on what they are given…in other words they’re downstream so asking them to make an exception is asking someone to override a signal they didn’t generate and can’t verify.

The upstream was visible in a single place. VirusTotal aggregates the verdicts of ninety-one security vendors against a domain and it showed ten of them calling this one malicious with three more calling it suspicious. Fortinet, Sophos and VIPRE had it filed specifically as phishing which is the same the word Cloudflare’s categorization tool had used.

One detail there reframed how I think about reputation damage generally. Google Safe Browsing had already cleared; the listings still doing real damage were private commercial feeds that no public tool reports and almost nobody thinks to check and that was the part that was still sinkholing visitors weeks later.

So the real work was never four requests but instead was thirteen and this is the part I want to be clear about because it’s the part nobody warns you about: no two of them work the same way.

Fortinet runs a web filter with fixed categories, so theirs is a genuine recategorisation, moving the site from Phishing to Government and Legal Organizations. Webroot turned out to be BrightCloud, which is now OpenText Threat Intelligence, so the documented address redirects before you reach a form whose comment box caps at a hundred and fifty characters. CRDF allows a thousand, and its confirmation page promises a decision within twenty-four hours in one sentence then publishes an average processing time of nearly seventeen days in the next. AdGuard’s report becomes a public GitHub issue, filed on your behalf by a bot and tagged at the lowest priority by default. The smaller vendors are email only, and one of them accepts false-positive reports over Facebook Messenger. The contact addresses themselves are obfuscated on the page that lists them so you have to open it in a browser to read them at all.

Fortinet reviewed both of my submissions inside an hour and declined them and it took me a while to work out why. Their confirmation email quotes your justification back to you, and mine was cut off mid-sentence. The comment field had silently truncated at around two hundred and fifty characters. Everything that mattered (the remediation date, the checksum verification, the webroot scan, the blocklist results) had never reached the reviewer. Whoever looked at it saw an unsupported assertion and made an entirely defensible call on what they had actually been given.

So I sent the identical case through their support channel instead and asked them that if they were keeping it to tell me the specific URL or indicator they were detecting so I could fix it. Either they name something actionable, which you want to know about, or they can’t which makes the listing much harder to justify.

The reply came back the next day. “We have checked the domain woodwardlg.com and we do not detect any malicious content/activity. The malicious rating on the domain has been removed.”

Moving on, SOCRadar closed my request without assessing it at all because their process now requires the request to arrive from an email address on the domain being delisted, and mine is on my own company’s domain. After all the technical work, the blocker was proving I was allowed to ask. The firm’s owner sent four sentences from his own address and it cleared.

AdGuard taught a smaller lesson. The bot files your report at the lowest priority by default and mine sat untouched for two days. One comment adding what had changed since filing moved it to in progress within three hours and it was unblocked and closed the same day. The queue you land in matters as much as the case you make.

Sophos was the one that turned into farce.

Their analysis tool will tell you what it thinks of a URL, and what it thought was genuinely interesting…its machine learning verdict on the live site was “Likely Clean.” Its top content predictions were general business, news and information technology, with phishing appearing nowhere. Its list of compromise indicators contained exactly one flagged item, “Domain classified as high risk,” which is circular. None of that troubled the static rating of “Phishing and fraud” sitting at the top of the same report.

There is no way for a non-customer to dispute it. The appeal button exists but sits behind a paid support entitlement. The documented route is to post the URL in their community forum…which first requires an account that needs manual approval. Once approved, I posted the request.

But…their anti-spam system hid it automatically and the stated reason was the links in the post.

The links were to the domain the post was asking them to reassess. Their abuse automation had blocked a phishing reclassification request on the grounds that it contained links to a domain classified as phishing. Lol.

Naturally I appealed and a human restored it the following day and the community team submitted the reassessment on my behalf noting that it takes three to five business days and that they have no influence over the outcome. SophosLabs cleared the domain shortly afterwards.

CRDF was the same shape in a different costume. They declined my request the same day I filed it with a message explaining that the domain corresponded to a malicious website in accordance with their detection criteria. What made it worth a second look was the label on the reply: their own interface marked it “AI-generated,” with a note that it reflected the current review state. No person had looked at anything; an automated system had confirmed an automated listing and called it a review.

So, I replied in their thread and opened with that observation: this was generated by your assistant and reflects an existing state rather than a fresh analysis, and I’m asking for a human analyst. Underneath it went the same evidence and the same closing question about which indicator was being detected.

Four days later the domain was removed from their database. Their confirmation page had advertised an average processing time of nearly seventeen days so I was glad it didn’t take longer.

To be clear, that’s the honest shape of this work and it’s worth weighing against how these incidents are usually described. The malware came out in an afternoon; the credential problem underneath it was closed the same day. Clearing the name though took weeks…thirteen separate submissions, four different ideas of what counts as evidence, one self-contradicting service-level promise, one entitlement paywall, one demand that I prove I was authorised to ask, an anti-spam system that had to be appealed before the appeal could be read, and an AI that rejected a correction on behalf of a database no human had checked.

And all of that assumed somebody knew the layer existed. A firm without a security consultant would have no idea their domain sits on a vendor feed, no idea VirusTotal aggregates those feeds, and no way to interpret an SSL error that only some visitors ever see. What they would have is a website that works perfectly whenever they check it and clients who quietly can’t reach it and may never mention it.

There’s something almost funny about the whole sequence in a bleak sort of way. It was the same shape every single time. The malware was downstream of a stolen login. The SSL error was downstream of a DNS block. The DNS block was downstream of a vendor list. Even the appeal was downstream of the listing it was appealing. Every time, the obvious fix would have treated a symptom, and every time the actual lever sat one layer further back.

Quad9 proved the point better than I could have arranged.

They had refused me twice but the second refusal came with something new though which was an answer to the question I had been asking everybody: they actually named the source. Their block came from a specific third-party feed they subscribe to, they gave me the maintainer’s email address, and they told me that once the domain came off that list it would drop out of Quad9 automatically within about two hours.

So I wrote to one person at one email address and explained the situation in a few short paragraphs.

He replied the same day: removed from the feed.

Quad9’s block cleared roughly two hours later, exactly as described. After a fortnight of petitioning resolvers who couldn’t act reaching the one party who could took an afternoon.

That’s the entire argument of this case in miniature. Nothing about the earlier requests was wrong and everyone I dealt with was fairly reasonable…they simply weren’t the ones holding the thing I needed changed. The whole exercise was a search for who actually had their hand on the lever, and every layer I peeled back was another organisation faithfully passing along a judgement it had inherited from somewhere else.


What Was Actually Underneath

The interesting thing about this case is what wasn’t there.

There was no ingenious self-healing virus, no cleverly hidden web shell. By the time I arrived the automated re-infector the earlier reports had described was already dead. The thing that kept a law firm’s website reinfected for weeks…that got it flagged as dangerous by Google and dragged its domain onto phishing blocklists…was a person with a valid administrator password and a forgotten account nobody had closed. The malware was downstream of that, it was the thing the attacker installed once he was inside, over and over, because getting inside cost him nothing.

That’s why the cleanups never held. You can’t clean your way out of a credential problem. As long as the key works, sweeping up the mess just resets the board for the next visit. The fix was changing the locks, removing the forgotten key, and closing the exposures that made the site easy to walk into in the first place.

The site is clean and verified, the intruder’s access is gone, the admin list is down to the two accounts that belong there, the secrets are rotated, the email blocklists are clear, Google’s browser warning is lifted, and the domain is protected against being forged. The owner can reach his own website again, and so can effectively everyone else.

Thirteen of the thirteen threat-intelligence vendors have now reversed their verdicts, including every one whose feed is licensed widely enough to matter. The count of security companies flagging the domain has gone from ten to zero, and the three that remain are small regional operations that block nobody and nne of the nine filtering DNS resolvers resolve the site normally.

This site had been cleaned before. This time the reason it kept coming back is gone, and the damage it left behind has been unwound one request at a time.


Stonegate Web Security remediates compromised WordPress sites without taking them offline, finding the root cause instead of sweeping the symptoms, and proving the fix holds. If your site has been “cleaned” before and the infection keeps returning, that repeating pattern is usually a sign that the real way in was never closed. That’s exactly the problem I specialize in.

Related Reading