The site looked clean, until it did not
The first sign was difficult to miss. Some visitors were being shown a fake Cloudflare-style Verify you are human screen instead of the page they expected. Clicking through led to instructions that tried to persuade them to paste a PowerShell command from their clipboard.
That trick is often called ClickFix. It turns a familiar verification step into a piece of social engineering: the visitor is told to run the attacker's command themselves. Microsoft has documented the same broader ClickFix technique, including fake CAPTCHA and human-verification prompts that copy a command for the visitor to run.
We found and removed the files serving the overlay. The website returned to normal, so the result looked promising. Less than 24 hours later, the same malicious pieces had been written back to disk. That return was the useful clue. We were not dealing with one infected plugin file. We were looking at several parts of an infection keeping one another alive.
The files were only the visible part
Self-healing is a descriptive name here, not a special WordPress feature or a formal malware family. The code was not repairing itself in any clever biological sense. It simply had more than one place from which to restart, and every normal page request gave the surviving code another chance to run.
Across the affected sites, the persistence sat in three connected layers:
| Layer | What we found | Why a partial cleanup failed |
|---|---|---|
| Database | Encoded payload data in wp_options, stored under transient names resembling _site_transient_health_* | The database still held enough data to rebuild deleted files |
| Files | Fake plugins, must-use plugins, and copies placed in core-like or writable locations | Removing one copy left other loaders available |
| Boot and scheduling | Restore code in wp-config.php or wp-content/object-cache.php, with unfamiliar scheduled hooks | A page load or cron run could write the malicious plugin back |
WordPress transients are normally harmless temporary data. Without a persistent object cache, they are commonly stored in the options table, as the WordPress Transients API documentation explains. The attacker was using that ordinary storage mechanism as cover. The name looked like routine health or cache data, while the value carried something the surviving loader could use.
Why the infection returned on the next request
The order in which WordPress starts matters. Configuration and drop-in files can run before the normal plugins
you see on the main Plugins screen. Must-use plugins are loaded automatically too. The
WordPress must-use plugin documentation notes that they live in wp-content/mu-plugins by
default and cannot be switched off like an ordinary plugin. They appear separately under
Plugins > Must-Use.
In this infection, code in wp-config.php or an unexpected object-cache.php drop-in
checked whether the malicious plugin files were present. When the cleanup removed those files but left the
restore code and database payload behind, the next request put them back. Hourly cron hooks provided another
way to restart the chain.
That is why the first cleanup appeared to work. The delivery files really had gone, and the bad overlay stopped showing for a while. The quieter persistence layer had not gone with them. A clean homepage proved only that the visible symptom was absent at that moment.
The traces we used to follow it
File names are clues, not a verdict. A host or caching plugin may legitimately install an
object-cache.php drop-in, and a well-managed site may have genuine must-use plugins. We compared
each item with known-good packages, the site's normal configuration, file history, and the code itself before
removing anything.
The more distinctive traces in this incident included:
wp-own-assets.phpand files matchingwp-feed-normalize-*.php,wp-rest-cache-*.php,wp-rest-hardening-*.php, orwp-rest-optimizer-*.phpunderwp-content/mu-plugins- plugin directories beginning
wp-cache-,security-headers-manager-, orsite-performance-toolkit-that did not belong to installed software - files named like
class-walker_<hex>.phpinsidewp-includes/assets, separate from genuine WordPress files - a hidden
.own-releasesdirectory and files such asown-stego.php,own-stg.php,qtt-own-core.php.obf,pc_pack.py, or_w10_up.php - code around
WP_Core_Integrityor functions named_wp_cuh_restoreand_wp_cuh_sqlinsidewp-config.php _site_transient_health_*option rows containing large encoded values, unknown cron hooks, and unfamiliar REST routes shaped likecheck-<hex># own-stego-v4or repeated# BEGIN wp_compat_*blocks in.htaccess
A single matching word is not enough to condemn a file. Broad searches for functions such as
base64_decode are useful for finding places to inspect, but legitimate software uses encoding too.
The stronger evidence came from several traces agreeing with one another and from watching a surviving loader
recreate a deleted component.
The cleanup order changed the outcome
Once we understood the loop, repeatedly deleting the same plugin was no longer useful. The site had to be contained first, then the parts capable of writing files had to be stopped before their output was removed. Working in that order prevented one layer from undoing the next step.
- Contain the site and preserve evidence. We restricted public access where possible and took private copies of the files, database, and relevant logs before changing them. An infected copy is evidence, not a backup to restore blindly.
- Stop the restore path. We removed the confirmed injection from
wp-config.phpand dealt with the malicious drop-in before allowing ordinary page requests to keep running. - Remove every confirmed file component. That included the fake must-use and normal plugins, hidden release copies, core-like files, altered rewrite rules, and any secondary locations found during the review.
- Clean the database and scheduled hooks. We removed only the confirmed malicious transient data and cron entries after preserving a database copy. Guessing at rows in
wp_optionscan break a site. - Restore trusted application code. WordPress core, plugins, and themes were checked against clean packages or known source, with anything unverifiable reinstalled rather than trusted because its name looked familiar.
- Review access and close the entrance. We checked administrators, REST routes, other sites under the same hosting account, logs, and the vulnerable or stolen access that could have allowed the infection in.
- Rotate secrets after the code was clean. That covered every WordPress salt, administrator passwords, hosting and SFTP access, database credentials, and other exposed keys. Rotating too early can hand the replacement secrets to malware that is still running.
- Watch the site start again. We re-enabled access carefully, triggered normal page loads and cron, rescanned the files and database, and monitored for recreation rather than declaring success after one clean response.
If you cannot safely stop PHP execution, inspect configuration files, or distinguish a legitimate cache drop-in
from the malicious one, ask your host or a WordPress security specialist to handle the containment. Removing
object-cache.php on a guess can take a working cache offline without touching the real infection.
A returning file changes the question
When malware comes back, the useful question is no longer, “Where is that file?” It becomes, “What had the ability to write it again?” That shift leads naturally into the database, early-loading configuration, drop-ins, must-use plugins, scheduled events, and sometimes neighbouring sites that share the same hosting account.
It also changes what a scan result means. A scanner can correctly remove a malicious file and still leave the site compromised if its view stops at the filesystem. In the opposite direction, an unfamiliar transient or must-use plugin is not automatically malware. Automated findings need enough context to show how the pieces behave together.
For site owners, the simplest warning sign is repetition. If the same file returns, a disabled plugin silently reactivates, rewrite rules reappear, or a fake verification screen vanishes and then comes back, pause the cycle of deleting symptoms. Keep the evidence and investigate the mechanism doing the restoring.
Clean means it stays clean
The important part of this incident was not the strange file name or the fake verification screen. It was the gap between looking clean and being clean. The first pass removed what visitors could see. The successful pass removed the stored payload, the code that rebuilt it, the scheduled route back, and the access that could let the attacker begin again.
Our WordPress malware removal service covers the files, database, users, scheduled tasks, and likely entry point rather than stopping at the first alert. If you need help with one affected site, you can also request a one-off WordPress fix. Whichever route you choose, leave enough time after the cleanup to prove the site no longer rebuilds what was removed.
Quick answers about self-healing WordPress malware
Why does WordPress malware come back after its files are deleted?
Another part of the infection may still be able to recreate them. In the sites we examined, code loaded from WordPress configuration or an object-cache drop-in used payload data stored in the database, while scheduled hooks provided another way to restart the chain.
What is self-healing WordPress malware?
Self-healing is a practical description for malware with more than one persistence layer. A surviving database payload, loader, must-use plugin, drop-in, or scheduled task can restore another malicious component after it is removed.
Is every object-cache.php file malicious?
No. WordPress caching plugins and hosting platforms can install a legitimate object-cache.php drop-in. Check who installed it, compare it with a trusted copy, and review what the code does before removing it.
Is deleting suspicious WordPress transients safe?
Not by name alone. Transients are a normal WordPress feature, and deleting the wrong options may disrupt the site. Preserve a database backup and remove a row only when its value and relationship to the infection have been confirmed.
How do I know a WordPress malware cleanup worked?
Recheck the files, database, users, scheduled events, and logs after normal page requests and cron have run. The site should remain clean over time, with the original access route closed and affected credentials rotated after the malicious code is gone.