Check For Malicious WP-Cron Events

WordPress runs its own scheduler out of one database row, where no server crontab and no file scanner can see it. How a planted event is found and removed.
What this check and mySites.guru tool looks at on your site
WordPress ships a scheduler of its own, separate from the one your server runs. It is called WP-Cron, and it does not live in a file: it lives in a single row of the wp_options table, under the name cron. Every scheduled job WordPress or any plugin has ever asked for is in that one row.
On every snapshot mySites.guru reads that row directly out of the database with a plain SQL query, decodes it without letting PHP rebuild any objects inside it, and classifies each event it finds. Reading it through WordPress would be easier and would also be wrong. On a site that has been broken into, the attacker’s code is loaded and their filters are live inside the same request, so anything routed through a WordPress function is answered by the attacker’s runtime.
An event is reported as malicious only on tells with no innocent explanation:
- The event runs a file already flagged as malware elsewhere on the site.
- The file lives in a hidden directory under
wp-content. A leading dot keeps a folder out of an ordinary listing, and nothing legitimate hides there. - The code behind the event was created at runtime rather than loaded from a file, by something no ordinary installed plugin called.
- The event name belongs to a malware family already pulled apart.
Reading this list changes it, and the tool says so
To read the scheduler we have to load WordPress, and as WordPress starts it schedules its own checks for core, plugin and theme updates. Some of what is listed was created by our visit a moment earlier rather than found waiting. Those events are marked, they are never counted as findings, and it is why an empty scheduler is something we can never observe.
What runs an event is read before what it is called. If every file behind an event sits in your ordinary plugins or themes folders, and none of it is hidden, must-use, created at runtime or already known to be hacked, nothing is said about its name at all. Plugin authors name their scheduled jobs however they like, and a version number in a name is not a clue about anything.
Only when an event cannot be tied to an ordinary file, or what it is tied to is itself suspect, does the name come into it. Then an event whose name has no pronounceable structure, no vowels to speak of, long consonant runs, digits scattered through it, is reported as worth a look. That tier never marks a site as hacked.
Three rules that were measured and then left out
A short repeat interval is not suspicious. A widely used backup plugin schedules a job every sixty seconds by design, so an “interval under 300 seconds” rule would fire on a large share of perfectly healthy sites.
A must-use plugin behind an event is not a finding on its own either, because most managed WordPress hosts install must-use plugins as standard. It becomes interesting only when it is also the attributed file of an event flagged by another rule, sits in a dot-directory, or is already flagged.
And a digit inside a word is just a name. Several of the most widely installed plugins in the world put a version marker in their scheduled jobs, which is why the digit rule skips a token that is one alpha stem with one digit run. Measured across 2,719 hook names harvested from WordPress core and from installed plugins, no ordinary name reaches the tier that marks a site as hacked.
Why a planted event turns a clean-up into a losing game
A scheduled event is what makes a hacked WordPress site come back after you have cleaned it. The malware keeps its own source in the database and registers an event that checks whether its files are still in place, rewriting anything missing. Remove every malicious file and the next visitor to the site puts them back.
The timing is what catches people out. WP-Cron is triggered on page load rather than by a system timer, so a ten minute schedule is a ceiling rather than a delay. On the compromise this check was built from, deleted files were back in seconds, and the agency cleaning the site could not work out why.
None of it is visible from the places people look. There is nothing in any crontab, so checking every crontab on the server walks straight past it. There is nothing on disk that a file scanner has not already seen. The whole mechanism is one serialized database row.
Every scheduled event accounted for
Every event in your scheduler belongs to a plugin, a theme or WordPress itself, and you can name which. Nothing is running from a hidden directory under wp-content, nothing was created at runtime by code you did not install, and there are no leftover hooks from an extension that was removed months ago.
How to fix it
- Remove the event before you clean the files. In that order. Clean the files while the event is still scheduled and it will write them back, often within minutes.
- Read the file it runs. mySites.guru shows the path where it can work it out. That file is the thing to remove next, and it usually points at how the site was broken into.
- Check your must-use plugins and drop-ins in the same sitting. When malware plants a scheduled event, it usually plants something in one of the two folders WordPress loads PHP from automatically as well.
- Check your administrator accounts. This family plants an administrator the Users screen will not show you alongside everything else.
- Do not rely on restoring a backup. A restore replaces your files and your database, so it will remove the event, and it will also restore whatever hole let it be created. Fix the hole.
- Run a full audit and work through everything it flags.
If an event is only marked as worth a look, find out which plugin owns it before you touch it. Removing a scheduled event a plugin needs breaks that plugin with no error, and it will not schedule it again until the plugin is reactivated.
What mySites.guru does about it
On every snapshot, twice a day, mySites.guru reads the cron row out of your database and classifies every event in it. A malicious event marks the site as hacked and appears in the red card with everything else found on that site.
Any event can be removed from the tool, not only a flagged one, because an ordinary scheduled job is the operator’s to remove too. Removal goes through WordPress’s own unschedule function rather than rewriting the whole row, because wp-cron.php rewrites that row after every event it runs and the loser of that race is silent. The row is then read again, so what you are shown is the verified state of your scheduler rather than what the call claimed.
On a multisite network WordPress keeps a separate scheduler per site, and mySites.guru reads the one belonging to the address you registered. The rest of the network is not covered.
Check For Malicious WP-Cron Events
mySites.guru checks every connected site for this automatically and flags it the moment it appears. These run twice a day on every connected site.
Further Reading
- Five New Checks for WordPress Hacks a File Scan Cannot See - the compromise this check was built from, and the four tells that separate a planted event from an ordinary one
- The WP-Cron API in the WordPress developer handbook - the official statement that events live in the database and fire on page loads rather than on a timer
