WordPress File Integrity Monitoring and Backdoor Detection

Detecting compromised WordPress files before a breach escalates is a critical part of security hardening. WP-CLI’s wp core verify-checksums checks core files against the official WordPress.org checksums API, and the same approach can be extended to plugins and themes.

Problem: A WordPress server has been compromised or there are signs of suspicious file changes — admin accounts appear, PHP files are modified — but there is no baseline to compare against or automated monitoring to detect future changes.

Solution: Create a SHA-256 hash baseline of all PHP files in the WordPress root with find . -name "*.php" | xargs sha256sum > baseline.txt, then schedule a daily cron job that reruns the hash and diffs against the baseline, emailing a report on any changes. Supplement with inotifywait for real-time alerting on production servers.

The examples below verify core and plugin checksums via WP-CLI, build a PHP script that hashes all theme files and emails an alert on change, and set up a daily cron job for automated monitoring.

# Verify WordPress core files against the official checksums API
wp core verify-checksums
# Output:
# Success: WordPress installation verifies against checksums.
# Or:
# Warning: File should not exist: wp-content/uploads/backdoor.php
# Error: File doesn't verify against checksum: wp-login.php

# Verify all installed plugins
wp plugin verify-checksums --all
# Checks each plugin against the wordpress.org plugin directory checksums

# Verify a single plugin
wp plugin verify-checksums woocommerce

# List all files that differ from the official checksums (detailed diff)
wp core verify-checksums --format=json | jq '.[] | select(.status != "ok")'

# Find PHP files in uploads (classic backdoor location)
find /var/www/html/wp-content/uploads -name "*.php" -type f
# Should return nothing — PHP files in uploads are always suspicious

# Find recently modified PHP files in wp-includes or wp-admin
find /var/www/html/wp-includes -name "*.php" -newer /var/www/html/wp-includes/version.php

A PHP file-hash monitoring script to detect changes in your theme:

isFile() ) continue;
        $rel          = str_replace( $dir, '', $file->getPathname() );
        $hashes[ $rel ] = md5_file( $file->getPathname() );
    }
    ksort( $hashes );
    return $hashes;
}

$current = compute_checksums( $theme_dir );

if ( ! file_exists( $snapshot_file ) ) {
    file_put_contents( $snapshot_file, json_encode( $current ) );
    echo "Baseline created.
";
    exit( 0 );
}

$saved   = json_decode( file_get_contents( $snapshot_file ), true );
$added   = array_diff_key( $current, $saved );
$removed = array_diff_key( $saved, $current );
$changed = array_filter( $current, fn( $h, $k ) => isset( $saved[$k] ) && $saved[$k] !== $h, ARRAY_FILTER_USE_BOTH );

if ( $added || $removed || $changed ) {
    $msg  = "Theme file changes detected on " . gethostname() . ":

";
    $msg .= "Added: " . implode( ', ', array_keys( $added ) ) . "
";
    $msg .= "Removed: " . implode( ', ', array_keys( $removed ) ) . "
";
    $msg .= "Changed: " . implode( ', ', array_keys( $changed ) ) . "
";
    mail( $admin_email, '[SECURITY] Theme files changed', $msg );
    // Update the snapshot so the next run only reports new changes
    file_put_contents( $snapshot_file, json_encode( $current ) );
    echo "Alert sent.
";
} else {
    echo "No changes.
";
}

NOTE: Schedule the monitoring script daily via system cron — never via WP-Cron, as an attacker who has compromised WordPress could disable WP-Cron hooks. Add it with crontab -e: 0 3 * * * php /var/www/html/wp-content/mu-plugins/file-monitor.php >> /var/log/wp-monitor.log 2>&1

Leave Comment

Your email address will not be published. Required fields are marked *