Search Blue Canoe

Enter at least two characters.

Incidents · updated 28 September 2026

Beyond the Fake CAPTCHA: A WordPress ClickFix Incident Investigation

A real-world WordPress compromise involving fake reCAPTCHA/ClickFix delivery, database-resident PHP persistence, passwordless administrator impersonation, hidden SEO tooling, and repeated reinfection.

If your WordPress site is currently serving a fake CAPTCHA, fake reCAPTCHA, verification page, or instructions asking visitors to copy and run a command, stop the public site first.

Take WordPress out of the public request path using your hosting provider, web server, reverse proxy, CDN, or a static maintenance page. Confirm externally that visitors can no longer reach the compromised application. Preserve the files, database and logs before beginning cleanup.

Containment stops the harm. Cleaning comes afterwards.

This report documents a real WordPress compromise investigated during September 2026.

The visible symptom was a fake CAPTCHA/ClickFix page.

That was not the important part.

The initial cleanup removed the visible malicious code, removed a rogue administrator, changed passwords and rotated WordPress authentication salts. The site appeared clean.

The attacker came back.

The investigation then changed from:

Where is the fake CAPTCHA?

to:

How are they regaining authenticated access after the credentials and sessions have been replaced?

The answer was executable PHP stored in the WordPress database.

One recovered backdoor could select a WordPress administrator and issue a valid authentication cookie without knowing that administrator's password.

That explained why an apparently sensible first remediation did not stick.


Scope and disclosure

The affected customer and identifying details are deliberately omitted from this public report. Original evidence is retained privately.

This report does not publish database credentials, authentication salts, live session tokens, attacker hard-coded authentication tokens, or complete working backdoor source.

Detection identifiers and behavioural indicators are included where they may help defenders and researchers.


Executive summary

The incident contained several distinct components:

  1. A fake CAPTCHA/ClickFix visitor-facing payload.
  2. Repeated ea_* PHP files installed as WordPress MU-plugins.
  3. WP File Manager present during attacker activity.
  4. Malicious PHP stored in the Code Snippets database table.
  5. A hidden SEO/datafeed publishing system.
  6. A separate authentication backdoor capable of creating a valid WordPress administrator session without verifying that administrator's password.
  7. Persistent token/session state stored in wp_options.
  8. Repeated authenticated access from external IP addresses.
  9. A malicious sitemap endpoint, ?ha_sitemap=datafeed, which continued to be crawled after remediation.
  10. Continued external attempts to reach former administrative/plugin infrastructure after recovery.

The known compromise was removed, WordPress core was replaced from a clean upstream package, required plugins were replaced with clean packages, previous WordPress sessions were destroyed, credentials and salts were replaced, and WordPress administration was removed from the public Internet for this particular site.

After several days of public operation, the known indicators had not returned.


First five minutes: stop the spread

Someone who has just discovered this problem does not need to become a malware analyst before making visitors safe.

1. Stop serving WordPress publicly

Prefer, in order:

  1. your hosting provider's suspend or maintenance facility;
  2. a CDN/reverse-proxy maintenance response;
  3. web-server access controls;
  4. temporarily replacing the public document root with a static maintenance page.

Do not rely on a WordPress maintenance plugin to contain a compromised WordPress installation.

If you have SSH access and know with certainty which directory is the site's document root, a simple static replacement can work:

cd /path/to/site-parent

mv public_html public_html.COMPROMISED
mkdir public_html

cat > public_html/index.html <<'EOF'
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Temporarily unavailable</title></head>
<body>
<h1>Temporarily unavailable</h1>
<p>This website is undergoing maintenance. Please try again later.</p>
</body>
</html>
EOF

Do not use this blindly. Hosting layouts differ. If you are unsure, use the hosting provider's controls instead.

Then test from a device outside your normal network.

2. Preserve before cleaning

sudo mkdir -p /root/wp-incident
sudo chmod 700 /root/wp-incident

sudo tar -czf /root/wp-incident/wordpress-files.tar.gz /path/to/wordpress
echo $?

sudo sha256sum /root/wp-incident/wordpress-files.tar.gz

The archive command should return exit status 0.

If WP-CLI is already installed:

wp db export /root/wp-incident/wordpress-database.sql
gzip /root/wp-incident/wordpress-database.sql
sha256sum /root/wp-incident/wordpress-database.sql.gz

Otherwise use the hosting provider's database export facility rather than putting database passwords into unfamiliar commands.

Preserve access/error logs where possible.

3. Do not immediately delete the obvious malware

The visible malicious file may be only the delivery payload, useful evidence, or something that another persistence mechanism can recreate.

That was exactly what happened here.


Incident timeline

4 August 2026

A rogue WordPress administrator named wp was created.

At almost the same time, Code Snippets appeared on the site. Its filesystem timestamps and stock/example database rows align to the same event after accounting for UTC/BST differences.

The rogue administrator followed approximately one minute later.

This establishes suspicious activity by 4 August.

The original entry vector remains unknown.

9 September 2026

Authenticated external WordPress activity was recorded. WP File Manager activity occurred during the same period and the first known ea_* MU-plugin payload was deployed.

A world-writable .tmb directory associated with File Manager activity was also created.

15 September 2026

The first major cleanup removed the rogue administrator and visible payload, changed credentials, rotated WordPress salts and removed unnecessary components.

The site initially appeared clean.

During that cleanup period four new active Code Snippets rows appeared:

ID Name Scope Approx. size
5 Snippet front-end 2.8 KB
6 Plugin global 16.9 KB
7 Wp global 12.3 KB
8 Codesnippet global 12.2 KB

All four were created within roughly two minutes.

External authenticated activity was subsequently visible in the web logs.

16 September 2026

A new ea_* MU-plugin appeared and external authenticated WordPress activity was again recorded.

24 September 2026

Another ea_* MU-plugin appeared and another external administrator session was present.

25 September 2026

The fake CAPTCHA returned.

The four suspicious Code Snippets rows were identified and preserved. Two explained most of the previously puzzling behaviour.


What the database-resident malware did

Hidden SEO/datafeed system

One recovered snippet implemented a hidden content-publishing system with:

  • hard-coded API authentication;
  • private cookie/session handling;
  • a custom datafeed post type;
  • REST endpoints;
  • remote content creation;
  • a custom sitemap;
  • hidden homepage content;
  • off-screen links/text.

Recovered identifiers included:

HA_DF_API_TOKEN
ha_df_auth_
ha_df_generated_token_
ha_df_rewrite_version
fa_homepage_text

No datafeed posts remained when the final investigation was performed.

However, the endpoint:

?ha_sitemap=datafeed

was clearly public. After remediation Ahrefs, PetalBot, GPTBot and other crawlers continued requesting it repeatedly for days.

That independently corroborates that the endpoint had existed long enough to enter external crawl queues.

Passwordless administrator impersonation

Another snippet provided the most important persistence mechanism.

Recovered identifiers included:

TLC_LOGIN_AS_ADMIN_TOKEN
tlc_bootstrap_token_
tlc_session_token_

It implemented its own bootstrap/session-token exchange and could select a WordPress user before invoking WordPress's internal authentication-cookie functionality.

The essential behaviour can be described safely as:

// User selected by the malicious code.
wp_set_auth_cookie($user_id, true);

The selected WordPress user's password did not need to be verified by the backdoor before the authentication cookie was issued.

Why this mattered

Changing the administrator password did not remove the persistence.

Destroying an attacker session did not permanently remove it.

Rotating WordPress salts invalidated existing cookies, but malicious PHP still executing inside WordPress could create another authenticated session later.

It also means an attacker session associated with a legitimate administrator does not, by itself, prove that administrator's password or computer was compromised.


The database is executable territory too

A clean filesystem is not necessarily a clean WordPress installation.

Code Snippets and similar legitimate tools intentionally store executable PHP in the database. Once an attacker has sufficient privileges, that can become a persistence mechanism.

Check the table prefix first:

grep table_prefix wp-config.php

A typical Code Snippets inspection:

SELECT id,name,active,modified,LENGTH(code) AS bytes
FROM wp_snippets
ORDER BY id;

Preserve unfamiliar rows before deleting them.


Useful read-only checks

MU-plugins

find wp-content/mu-plugins -type f   -printf '%TY-%Tm-%Td %TH:%TM:%TS %s %p\n' 2>/dev/null

Executable files in uploads

find wp-content/uploads -type f   \( -name '*.php' -o -name '*.phtml' -o -name '*.phar' \)   -printf '%TY-%Tm-%Td %TH:%TM:%TS %s %p\n' 2>/dev/null

Do not assume every result is malicious. Investigate it.

Recently modified PHP

find . -type f -name '*.php' -mtime -14   -printf '%TY-%Tm-%Td %TH:%TM:%TS %s %p\n' | sort

Administrators

wp user list --role=administrator   --fields=ID,user_login,user_email,user_registered

Destroy existing WordPress sessions

After preservation:

wp user session destroy --all

Useful containment, but not sufficient while malicious PHP is still executing.


Indicators observed

TLC_LOGIN_AS_ADMIN_TOKEN
HA_DF_API_TOKEN
ea_inj

tlc_bootstrap_token_
tlc_session_token_

ha_df_generated_token_
ha_df_auth_
ha_df_rewrite_version
fa_homepage_text

?ha_sitemap=datafeed

Filesystem detection aid:

grep -RIlE 'ea_inj|TLC_LOGIN_AS_ADMIN_TOKEN|HA_DF_API_TOKEN|tlc_bootstrap_token_|tlc_session_token_|ha_df_generated_token_|ha_df_auth_' . --include='*.php' 2>/dev/null

Several important indicators in this incident existed in the database rather than the filesystem.


Why the first cleanup failed

The first cleanup was not careless. It removed the visible payload and rogue administrator, changed passwords, rotated salts and inspected the filesystem.

It failed because executable database persistence had not yet been identified.

Credential replacement cannot remove malicious code that is already executing inside the application.


Recovery

Evidence retained privately included the original ea_* samples, malicious Code Snippets rows, user/capability evidence, web logs, database state, relevant plugin artefacts, SHA-256 hashes and a post-cleanup filesystem/database checkpoint.

The final cleanup removed:

  • malicious Code Snippets rows;
  • associated TLC_* token/session options;
  • associated HA_DF_* options;
  • hidden SEO configuration;
  • all known ea_* MU-plugin payloads;
  • WP File Manager;
  • Code Snippets;
  • stale File Manager artefacts;
  • rogue/unneeded administrators;
  • historical WordPress sessions.

WordPress core was compared against a clean matching upstream distribution and then replaced from that clean package to establish a known-good baseline.

Required presentation plugins were likewise replaced with clean packages rather than simply trusting the existing directories.

The previous large active-plugin set was not restored wholesale.


Administrative hardening

For this site, WordPress administration does not need to be available from the public Internet.

The public frontend therefore remains available while /wp-login.php and /wp-admin/ are restricted to the management LAN.

This is not a universal recommendation to use a LAN address.

The general recommendation is:

Where operationally practical, place an access-control boundary in front of WordPress administration.

Depending on the environment that could mean:

  • management LAN;
  • VPN;
  • identity-aware proxy;
  • reverse-proxy access control;
  • client certificates;
  • another appropriate external authentication boundary.

2FA remains useful, but it addresses a different problem. The persistence recovered here was already executing inside WordPress and could create authentication cookies directly.


What happened after reopening

Within minutes of reopening, external /wp-login.php requests were rejected by Apache before reaching WordPress.

Over the following weekend logs showed:

  • repeated WordPress login probes;
  • POST attempts against /wp-login.php;
  • requests for /wp-admin/;
  • requests for /wp-admin/edit.php?al=true;
  • webshell discovery attempts beneath /wp-admin/;
  • probes for the removed WP File Manager;
  • probes for the removed Code Snippets plugin.

The administrative boundary returned 403 before WordPress handled those requests.

Meanwhile, ?ha_sitemap=datafeed continued to be requested by major crawlers. With the malicious code removed it returned ordinary site content rather than the attacker's sitemap.

Repeated filesystem/database IOC checks remained clean.

This is stronger evidence of successful remediation than a clean scan immediately after cleanup: the site had been exposed to normal Internet traffic for several days without the known persistence returning.


Current attribution

The visitor-facing behaviour strongly matches the current fake-CAPTCHA/ClickFix ecosystem.

Public reporting around the September 2026 Brevo/KongTuke/Web Media Optimizer incident has particularly notable similarities:

  • fake verification/ClickFix delivery;
  • WordPress-specific behaviour;
  • MU-plugin persistence;
  • hidden plugin behaviour;
  • administrator-session creation without the administrator password.

However, this report does not attribute the complete incident to KongTuke or Web Media Optimizer.

No credible public indexed matches have yet been found for the exact recovered persistence identifiers:

TLC_LOGIN_AS_ADMIN_TOKEN
HA_DF_API_TOKEN
ea_inj
tlc_bootstrap_token_
tlc_session_token_
ha_df_generated_token_
ha_df_auth_

The August compromise also predates the September Brevo incident.

Current assessment:

  • High confidence: fake-CAPTCHA/ClickFix ecosystem.
  • Strong behavioural similarity: KongTuke/Web Media Optimizer-style WordPress persistence.
  • Unattributed: the TLC_*, HA_DF_*, Code Snippets and ea_* persistence layer.
  • Unknown: original 4 August entry vector.

This section should be updated if reliable attribution emerges.


Practical lessons

Stop the harm before solving the puzzle

Taking a compromised site offline is not failure.

A static maintenance page for a day is preferable to serving malware while trying to understand it.

Preserve before deleting

The evidence that explains persistence is often exactly what an understandable first cleanup removes.

Check the database

After administrator/code-execution compromise, the filesystem is not the only executable territory.

Password resets are necessary, not magical

If malicious PHP remains active inside WordPress, it may be able to bypass normal authentication flows entirely.

Rebuild trust rather than merely remove detections

Where practical, replace core and required plugins from known-good packages.

Reduce the administrative attack surface

Do not expose WordPress administration more widely than the site's operating model requires.


What remains unknown

The investigation has not established:

  1. the original 4 August entry vector;
  2. whether the exact persistence layer belongs to a publicly named malware family;
  3. whether the SEO/datafeed component successfully published content before discovery;
  4. whether all external authenticated sessions represented the same operator or infrastructure.

These unknowns are deliberately retained rather than filled with speculation.


Research / sample availability

Sanitised indicators and hashes can be shared with established security researchers.

Original samples and complete evidence are retained privately and may be made available to reputable malware/threat-intelligence researchers where appropriate.

If you recognise the TLC_*, HA_DF_* or ea_inj identifiers from another WordPress compromise, contact Blue-Canoe with the context in which you observed them.


Closing note

The fake CAPTCHA was the symptom.

The important finding was the persistence mechanism that survived the first cleanup and could recreate authenticated administrator access without needing the administrator's password.

Once that mechanism was understood, the previously confusing sequence of reinfections became explainable.

That is the central lesson from this incident:

After an attacker has obtained WordPress administrator or code-execution access, do not assume that changing credentials and cleaning the filesystem restores trust. Preserve first, inspect the database as well as the files, and rebuild from known-good components where practical.