
WordPress Malware: Files vs Database (and Why It Matters)
WordPress malware lives in two completely different places: the file system (themes, plugins, uploads, core) and the database (posts, options, users). The two require different scanners, different cleanup techniques, and different verification — and the most common reason a cleanup doesn't stick is that someone cleaned one and not the other. This article is the conceptual primer: where each type hides, why one scan won't catch the other, and which order to clean them in. The execution-level detail lives in /learn/find-wordpress-malware-after-cleanup (file side) and /learn/wordpress-database-malware-detection (database side).
Why the distinction matters at all
Most articles about "WordPress malware" treat it as a single thing. It isn't. WordPress is a runtime that combines PHP files on disk with content stored in a MySQL database. Both can be modified independently, both can be modified by attackers with different access vectors, and a tool designed to scan one is usually blind to the other.
A practical example. Wordfence's free file scanner (the popular standalone tool) reads PHP files on disk, hashes them against known clean versions, and flags differences. Excellent at catching modified core files and obvious shells dropped in `wp-content/`. Completely silent on a planted admin user in `wp_users`, an injected `<script>` in a post body, or a tampered `siteurl` row in `wp_options`. The same site running the same scan can be "clean" by Wordfence's verdict and still serve malware to every visitor.
The reverse also happens. A database scanner that flags content injection in `wp_posts` won't catch a backdoor file dropped in `wp-content/uploads/photo.php`. The two scans are looking at orthogonal surfaces.
Most professional cleanups do both, but consumer-facing security plugins typically default to file-only scans. If you've cleaned a site once and it came back, the missed surface is almost always the one you didn't scan. The diagnostic walkthrough covers the symptoms; this article covers why standard scans can declare a site clean while serving malware.
File-side malware: where it lives, how it persists
Four categories on the file system, ordered by how often they're seen:
Category 1 — modified core files. Attackers append payloads to `wp-config.php`, `index.php`, `wp-load.php`, or files in `wp-includes/`. The payload is often a one-line PHP snippet that decodes a base64 blob and executes it — small, easy to miss in casual review, runs on every page load. Detection: hash comparison against fresh WordPress source. Cleanup: replace with clean copies (the cleanup walkthrough covers this in Stage 1).
Category 2 — backdoors in themes and plugins. A modified theme function, a planted PHP file in a plugin folder, or a suspicious file masquerading as part of a legitimate plugin (`twenty-twenty-four-extended.php` next to the real theme). Detection: file modification times that don't match your last update, files whose names look almost-but-not-quite right, or files containing `eval(`, `base64_decode(`, `assert(`, `gzinflate(`, `system(`. The backdoor-detection walkthrough lists the exact grep patterns.
Category 3 — payloads in `wp-content/uploads/`. The uploads folder should contain images, PDFs, videos — never PHP. Find them with `find wp-content/uploads -name '*.php'` from SSH. Anything it returns is malicious. Common file names: random strings (`hgw8ts.php`), WordPress-lookalikes (`wp-1234.php`, `index2.php`), security-themed (`lock360.php`).
Category 4 — `.htaccess` and server-config tampering. Less common but high-impact. Attackers add redirect rules to `.htaccess` (sending visitors to phishing or pharma sites), or add PHP execution overrides to allow code in `wp-content/uploads/`. Detection: read the `.htaccess` files at the root and in `wp-content/uploads/` and compare to a known-good version. If you don't have a known-good version, stock WordPress doesn't put any rules in `wp-content/uploads/.htaccess` — anything there is suspect.
How file-side malware persists across cleanups: scheduled file-recreation. An infected plugin, an injected WordPress cron job, or a modified `mu-plugins/` (must-use plugins) can re-create deleted files on the next page load. If files keep coming back after deletion, the persistence mechanism is one of these — finding it is the file-side cleanup's hard part.
Database-side malware: the four hiding places
Four locations in the database, ordered by frequency:
Location 1 — `wp_options`. Two specific rows are the highest-value targets: `siteurl` and `home`. If either contains anything other than your actual domain, the site is compromised — visitors get redirected. Beyond those two, attackers persist using arbitrary option_name entries with serialized data containing URLs, base64 blobs, or PHP `<?php` tags. The DB-malware detection guide lists the exact queries to surface these.
Location 2 — `wp_users` and `wp_usermeta`. Planted admin accounts. The signature: a recently-registered user with administrator role and an email address you don't recognise. Cleanup is `DELETE FROM wp_users WHERE ID = X` *plus* `DELETE FROM wp_usermeta WHERE user_id = X` (deleting from `wp_users` alone leaves orphaned meta that some malware uses to re-promote the user).
Location 3 — `wp_posts` (and `wp_postmeta`). Content injection. Look for `<script>` tags pointing to external domains, base64-encoded blobs in post content, or spam-keyword phrases (Viagra, casino, payday loans) embedded in post bodies. Common in pharma-hack and Japanese-SEO-spam infections. The query: `SELECT ID, post_title FROM wp_posts WHERE post_content LIKE '%<script%' OR post_content LIKE '%base64%' OR post_content LIKE '%eval(%';`. Inspect every result.
Location 4 — `wp_term_relationships` and taxonomy injection. Less common but harder to spot. Attackers add hidden categories or tags pointing to spam URLs. The signature: a taxonomy entry that doesn't appear in your published category list but is referenced by post relationships.
How database-side malware persists across cleanups: site-startup hooks. WordPress fires the `init` and `wp_loaded` actions on every page load. Malicious option_name entries can register listeners on these actions that re-create the very rows you just deleted. If `wp_options` rows keep coming back, the persistence is in another row that fires on init — find it with the queries in /learn/wordpress-database-malware-detection.
The asymmetry — why scans systematically miss one or the other
File scanners and database scanners are written by different people for different reasons. File scanners came first historically — the original WordPress security tools were built around hash-matching against the WordPress source repository. That model is still effective for what it covers: known-good baseline, hash differences flagged. It doesn't generalise to a database because there's no "known-good baseline" for your post content, your options, or your users — those are user-generated.
Database scanners therefore work differently: they look for *patterns* indicating malware, not differences from a baseline. They scan for `<script>` tags pointing to known-bad domain reputation lists, base64 blobs above a certain entropy threshold, option_names that don't match a whitelist of known-legitimate plugins, recently-registered users with admin role.
The tradeoff is that pattern-based scanning has false positives (a legitimate plugin that uses base64-encoded configuration in `wp_options` will trigger), and pattern-based scanning has false negatives (a sufficiently novel injection won't match any known pattern). File-side hash-matching has neither false positives nor false negatives in its own domain — but its domain is small.
What this means for you: any scanner you use is partial. The right cleanup combines a file-side scan (Wordfence-style hash matching, or the equivalent built into your host's malware tool) with a database-side scan (manual queries from the DB-malware guide, or a paid scanner like Sucuri's that includes both). Don't trust a single tool's verdict.
How to scan each side properly
File-side scan: hash comparison + pattern grep. Hash comparison is what Wordfence Free, MalCare, and your host's built-in scanner do — install one, run it, address the findings. For pattern grep (catches what hash matching misses), `find wp-content -type f -name '*.php' -newer /tmp/reference.file -exec grep -l 'eval\|base64_decode\|gzinflate\|str_rot13' {} \;` from SSH surfaces files modified recently containing common obfuscation calls. The post-cleanup forensics walkthrough has the full set of commands.
Database-side scan: queries directly. Open phpMyAdmin or use WP-CLI (`wp db query`). The five high-value queries: (1) all admin users, (2) `wp_options` rows with serialized-looking content, (3) post content with script/base64/eval patterns, (4) the `siteurl` and `home` values, (5) recent post revisions on inactive content (a popular hiding place). The DB-malware detection guide has all five with the exact SQL.
Cross-cutting check: scheduled tasks. Both file and database malware can install themselves as WordPress cron events that re-execute the infection on a schedule. `wp cron event list` from WP-CLI shows all scheduled events — anything with a callback function name you don't recognise is suspect. Removing the rogue cron breaks the persistence loop for both sides.
External second opinion: run Sucuri's free SiteCheck. It scans your site externally — sees what a visitor sees — and checks against multiple blocklist databases. Useful for catching client-side injections (JavaScript that only fires for visitors, not for admins) that internal scans miss.
Order of operations: which to clean first
If you're starting from scratch on a known-compromised site, clean the file system first, then the database. Two reasons.
Reason 1: file-side malware often re-creates database-side payloads on every page load (via init hooks in modified plugin files). Clean the database first and the malicious init-hook code in the modified plugin will re-inject the database rows on the next visitor request. You'll find yourself cleaning the same rows twice. Clean the files first — the init hooks are gone — then clean the database, and the cleanup sticks.
Reason 2: file-side cleanup is more destructive (you're replacing whole folders) and is therefore the higher-risk step. Doing it first means if something breaks you discover it before you've also restructured the database. Easier rollback path.
The reverse order makes sense in one specific scenario: if your hosting environment makes file-side replacement difficult (no SFTP, restrictive control panel) but database access is easy, clean what you can in the database first to immediately stop visitor-facing harm, then arrange file-side cleanup with your host's support team. Acceptable temporary measure; not the durable cleanup.
After both sides are cleaned, run the verification step from the post-cleanup walkthrough — the dormant-artifact scan that catches what each side's cleanup missed. Then close the entry-point vulnerability that let attackers in (the cleanup walkthrough Stage 3 covers this). If you were Google-flagged before the cleanup, follow the warning-removal walkthrough to get the warning lifted.
If at any stage you're past hour 4 and finding more, that's the threshold for stopping and using the Forensic Toolkit (DIY with the right queries pre-built) or the Fix service (hands-off cleanup). DIY cost has crossed where it stops being economic.
Related fix guides
Sensitive Files Publicly Accessible
WordPress ships with files that reveal your site version. Learn how to block public access in minutes.
User Enumeration
WordPress is exposing your admin usernames via its REST API. Block it with one code snippet.
XML-RPC Enabled
XML-RPC lets attackers run thousands of login attempts at once. Learn how to disable it in two steps.
Vulnerable Plugins Detected
One or more WordPress plugins has known security vulnerabilities. Learn how to find and update them.
GuardingWP checks your site for the 11 most common WordPress vulnerabilities — plus scans your installed plugins against the known CVE database. Free, no account required.
Scan to see which side of the malware your site is leaking from →Both sides to clean?
The Forensic Toolkit ships the file-side hash queries and the database-side detection SQL together — pre-built, run them in the right order without writing the queries yourself. One-time $25.
Get the Forensic Toolkit — from $25 →Prefer to have this handled for you? Get this fixed — Full Hardening ($149) →