Web threats / PHP backdoors

StainedHeart PHP backdoor

StainedHeart is a PHP credential logger planted in WordPress authentication. We have matched it on 4 sites since 7 September.

StainedHeart in numbers

Since 7 September, the first StainedHeart match in our records, counted on 12 October 2026. How we count.

Sites with StainedHeart code4
Sites with a match in the last 30 days3
New sites in September4
Still serving it when recaptured at least 25 days later1 of 1 site (100%)
Sites running WordPress (captured since 25 September)100%

A late authentication hook sees working passwords

StainedHeart is our name for a PHP credential logger planted in WordPress code. The name comes from its disguised output file, Stained_Heart_Red-600x500.png. SecureWP published the same authentication hook and file path in an infected theme's functions.php.

The snippet attaches an anonymous callback to WordPress's authenticate filter at priority 999. WordPress passes the callback its authentication result, submitted login and submitted password. StainedHeart waits for a successful result with both fields present, appends the pair to its file and returns the original user object so the login continues normally.

The error-suppression operator hides a failed write. FILE_APPEND and PHP_EOL keep every successful login as a separate record. In retained pages, the PHP source appeared before the site's normal HTML. Some copies were placed beneath ordinary-looking plugin headers, including backup and image-compression descriptions.

The image filename stayed while storage changed

Early copies wrote username:password as plain text. The path was Base64-encoded inside the PHP, then decoded to a dated folder under wp-content/uploads/. Its .png extension and -600x500 suffix make the credential log resemble a resized WordPress image.

A later copy kept the same path and line-by-line storage but changed the contents. It combines a fixed string with the site's host, hashes that value with MD5, XORs each credential record with the resulting key and Base64-encodes the output before appending it. The filename remains a useful marker across both forms.

One retained infection also contained a neighbouring installer marked __plugin_deployer__. It wrote a fake wp-perf-analytics plugin, set a hidden deployment flag and removed its own installer block. That adjacent code supplies a second place to check during cleanup.

What we see

MonthNew sites
September4
October so far0

Of the 1 site recaptured at least 25 days after their first match, 1 were still serving StainedHeart (100%). WordPress runs on 100% of captured sites.

Finding and removing StainedHeart

Search themes, plugins and must-use plugins for WordPress session analytics, an authenticate callback at priority 999, FILE_APPEND, Stained_Heart_Red-600x500.png, or an XOR loop keyed from home_url(). Check for __plugin_deployer__ and wp-perf-analytics beside the logger.

Remove the hook and credential file, replace affected components with trusted copies and inspect the whole uploads directory for executable PHP or disguised records. After the logger is gone, rotate WordPress, hosting and file-transfer credentials used while it was present.