adityaarsharma/wordpress-malware-removal/wp-malware-removal
Claude Code and agent skill by adityaarsharma/wordpress-malware-removal
- community
Checking that the SKILL.md file is still in the repository.
Forensic-grade WordPress malware detection and removal that catches the stealth infections a typical scan overlooks. Runs over ANY connected WordPress MCP gateway (SproutOS recommended) or SSH+WP-CLI. Detects and cleans across 38 malware families: web-shells and file-managers, auto-login/cookie backdoors, database and wp_options injection, malicious redirects, cron backdoors, mu-plugins and drop-in abuse, .htaccess / auto_prepend_file persistence, web-root shadow folders, hidden admins, rogue Application Passwords, remote-loaded JS skimmers, and SEO-spam cloaking (gambling/pharma). Sees the site the way Google and an attacker do — not just as a browser. Whole-server (not just wp-content), reads every database table, quarantine-first with live rollback to keep the site up, LLM-verified so it does not act on false positives, and names the entry vector. Use when a WordPress site is hacked, has a suspected web-shell, injected admins, redirect malware, or gambling/pharma spam in Google or link previews — or when the user asks to clean a hacked WordPress site or find how it was breached. For a read-only "how hackable am I?" check use wp-security-audit; to lock the doors after, use wp-hardening.
Install
npx vskill@latest install adityaarsharma/wordpress-malware-removal/wp-malware-removalSKILL.md
WordPress Malware Removal (Remove)
You are operating a forensic-grade WordPress malware detection and removal engine. Find as much as possible — including the stealth infections a typical scan overlooks — verify each finding so you never act on a false positive, remove it quarantine-first so the site stays up, and produce a forensic report that names the entry vector.
Core aims: Minimize false positives (read before you act). Nothing irreversible without a restore point. Keep the site up. Catch the stealth infections a typical scan overlooks. Kill self-healing malware at every reinjection node at once, or it regenerates.
This is one of three sibling skills — wp-security-audit (read-only posture), this wp-malware-removal (clean a hack), wp-hardening (close the door). They share the gateway contract in references/gateway.md.
What this catches that in-WordPress scanners miss (the edge)
Commercial scanners run inside WordPress and scan wp-content as themselves. Real modern attacks defeat that — in 2023, 58,848 infected sites had a security plugin installed before infection, and 14% of the time the malware tampered with that plugin to hide (Patchstack 2025):
- Webroot shadow folders — malware in physical folders shadowing real URLs (
/docs/,/pricing/)
in the web root, not wp-content. You scan the whole server.
- Tiny single-file backdoors — a 500-byte auto-login backdoor (
wp_set_auth_cookieon the first
admin) has no "web-shell" fingerprint; scanners needing ≥2 indicators miss it. You detect by behavior + context.
- Database-resident payloads — code in
wp_options(keys like_hdra_core,
wp_session_tokens_config), injected <script> in posts/postmeta. File-only scanners never see it. You dump and grep every table.
- Cloaking — the payload serves gambling/pharma HTML only to Googlebot/Facebook/mobile, clean to
everyone else (and the scanner). You fetch as Googlebot AND as a browser and diff.
These are four of the 38 malware families this tool covers — it is an end-to-end cleanup engine, not a single-signature or cloaking-only scanner. See references/incident-case-study.md (a real hack that a running commercial scanner missed for 11 days — it doubles as your test fixture).
Step 0 — Establish the gateway
Read references/gateway.md. Detect your access (SproutOS MCP ▸ other WP MCP ▸ SSH+WP-CLI), probe your capabilities (read/list/grep/exec/DB/write/logs/origin), and record the environment shape (WP root vs web root, real $table_prefix, multisite, relocated paths, server type, origin IP). Every layer below degrades gracefully to the capabilities you actually have — never fabricate one you lack. A connected gateway is required to scan the filesystem/database and to clean; only the external cloaking read (scripts/cloaking_diff.py) can run from your own machine.
The scan: 12 layers (run all your gateway supports)
Run in order. Collect candidate findings — delete nothing yet. Each candidate is {path_or_location, layer, signal, severity, evidence}. Verification and removal are dedicated phases after the scan. Full per-layer commands per gateway are in references/detection-playbook.md; machine-readable patterns are in scripts/signatures.json (run scripts/scan_patterns.py).
Layer 1 — External cloaking & exposure (the differentiator; needs only curl)
For every important public URL (home + sitemap + recently-modified posts): fetch as `Googlebot`, as `facebookexternalhit`, and as a desktop browser, and diff <title>, og:title, og:image, body. Cloaking = the bot version differs, especially with gambling/pharma/replica keywords (seo_spam_keywords) or og:image on a foreign host. Also fetch at the origin, bypassing the CDN (curl --resolve host:443:ORIGIN_IP) — a clean CDN cache routinely hides a dirty origin. Test query-string cloaking: fetch junk ?g=/?p=casino URLs as Googlebot (a 2026 hack generated 900k spam pages purely from ?g=, invisible in source/DB/sitemap). Automate with scripts/cloaking_diff.py.
Layer 2 — Web root & above-root sweep (not just wp-content)
List the web root and one level above. Flag anything not stock WordPress and not a known plugin/theme: shadow folders, loose root .php, hex/base64-named files, 777 temp/upload dirs, indexx.php/radio.php/wp-conf*.php-style names, .sess-<hash>.php in uploads.
Layer 3 — Core integrity
wp core verify-checksums (or the WordPress.org checksum API for the exact version). Any modified / "should not exist" core file is a candidate — attackers inject into legitimate core files, so detect modification, not just additions. Flag core-filename typosquats (wp-comments-posts.php, wp-vcd.php, wp-user(s).php) and wp-config.php injection (code AFTER require_once ABSPATH . 'wp-settings.php'; — survives a core reinstall).
Layer 4 — Plugin/theme integrity
wp plugin verify-checksums --all for .org components. Premium/custom/nulled code has no source of truth — for those, fall back to Layer 8 pattern scan + an LLM read + a known-clean vendor zip diff. Flag: nulled plugins, plugins on disk but not in the admin list (hidden plugins), a fake plugin named after the site's own domain, unauthenticated register_rest_route (permission_callback => '__return_true') that writes files / unserializes input, and themes with loose non-template .php.
Layer 5 — Uploads & non-executable dirs
No .php should be executable in wp-content/uploads, cache, etc. Every .php there is a candidate unless it matches a verified known-safe allowlist (index.php guards).
Layer 6 — mu-plugins & drop-ins (auto-loaded, un-deactivatable)
Enumerate wp-content/mu-plugins/*.php and the drop-ins db.php, object-cache.php, advanced-cache.php, sunrise.php, maintenance.php. These auto-load and cannot be disabled from the dashboard — a favorite 2025 persistence spot (wp-index.php in mu-plugins). Read each; anything not from a known plugin is a candidate.
Layer 7 — Web-shell & backdoor detection (single indicator OK)
Match signatures.json → shells (named shells C99/R57/FilesMan/WSO/Matamu, remote-eval loaders, uploaders). Behavioral single-indicator catches: wp_set_auth_cookie( in a loose/non-core file → auto-login backdoor; move_uploaded_file($_ / file_put_contents($_ in a non-plugin file → uploader; password_verify(... $_POST + session_start in a lone file → gated shell. Do not require two indicators.
Layer 8 — Obfuscation & code-exec patterns (all PHP, whole server)
Grep eval(base64_decode, eval(gzinflate, eval(str_rot13, assert($_, system($_, shell_exec($_, passthru($_, create_function(, variable-function calls ($_GET/$_POST/$_COOKIE/$_REQUEST[...](...)), preg_replace('/.*/e', long base64 blobs, hex-encoded \x65\x76\x61\x6c. Exclude `vendor/` and `node_modules/` from the auto-clean path but still list them (legit libs use base64) — they go to the verify gate, never auto-deleted.
Layer 9 — Database injection (every table, not just posts/options)
wp_options: autoloaded options containing code/<script>/foreign iframes;siteurl/homehijack;
malicious cron; injected active_plugins. Flag known IOC keys (_hdra_core, wp_session_tokens_config) and any autoloaded row carrying base64/PHP.
wp_posts/postmeta: gambling/pharma keywords, foreign<script>/iframe, link-farm<a>, base64 meta.wp_users/usermeta: admins with non-owner emails, unexpectedwp_capabilitiesadmins, and **admins
hidden by pre_user_query/user-query filters** (count via SQL, not the admin list).
- Redirect tables (Rank Math, Redirection, AIOSEO), comment tables, log tables (IOCs). For a
definitive pass, export the whole DB and grep every byte (references/remediation-playbook.md). Distinguish active injection (clean it) from passive log records (safe; optionally purge).
Layer 10 — Persistence: cron, htaccess, php config, server-level
- WP-cron (
wp_options.cron) and system cron for hooks hitting external URLs / eval / writing
files. Map every cron→file relationship (see self-heal note below).
.htaccess(root and every subdir): injectedRewriteRuleredirects,php_value auto_prepend_file,
base64 handlers, DirectoryIndex hijacks. Check .user.ini/php.ini/PHP-FPM pool for auto_prepend_file.
- PHP stream-wrapper payloads (
phar://,php://filter…convert.base64,data://) with
include/load_template; payloads hidden in .webp/.jpg referenced by PHP.
- Server-level (if it reinfects within hours, look here):
~/.bashrc, system crontab,/etc/cron.*,
service-startup scripts. Malware rewriting wp-config.php every few hours is re-dropped by something outside WordPress — find it or it never stops.
Layer 11 — Users, sessions, Application Passwords
List all admins + emails + registration dates (flag non-owner-domain, recently created/edited). List Application Passwords and API keys — attackers mint these for persistence and they survive password resets; they must be revoked explicitly. Cross-reference session_tokens IPs against the attacker IPs from Layer 12.
Layer 12 — Log forensics (entry-vector attribution; needs log access)
Read access logs. Correlate the first appearance (mtime) of each malware file with the requests that minute: the login/upload sequence, the plugin used to write files, the real attacker IP (behind a CDN it's in X-Forwarded-For). Determine the vector: stolen credentials (single clean login, knew a hidden URL → infostealer) vs plugin exploit (POST to a known-vulnerable endpoint → cross-ref Layer 4 versions against the Wordfence feed). This forensics is what tells the user how they got in so it stops.
Self-healing malware (do this or the clean fails)
Modern backdoors reinject on a schedule (wp-cron or system cron restores wp-login.php hourly). Before removing any file, build the reinjection graph from Layers 6/9/10/11 — every cron event, drop-in, mu-plugin, auto_prepend_file, and DB option that can rewrite a payload — and neutralize all nodes in one pass (disable the cron + quarantine the writer + clear the option together). Remove a single file first and it comes back within the interval.
Wordfence vulnerability cross-check (find the door)
For every installed plugin/theme + core version, query the free Wordfence Intelligence v3 feed and flag versions in known-vulnerable ranges (prioritize RCE / auth-bypass / arbitrary-file-upload — those are the doors) and abandoned/closed plugins. scripts/wordfence_client.py --match installed-plugins.json; setup in references/wordfence-integration.md. Enrichment — if no key, skip and say so.
The no-false-positive gate (verify before you touch anything)
Never delete on a regex match alone. For every candidate: (1) read the file/row in full context and decide, as an engineer, malicious vs legitimate (minified vendor lib, a plugin that legitimately uses base64, a .dat that legitimately contains pages.dev) — one line of reasoning each. (2) Classify CONFIRMED_MALWARE / NEEDS_HUMAN / FALSE_POSITIVE; only CONFIRMED proceeds. (3) Corroborate: a Layer-8 file that also appears in the Layer-12 timeline at the breach minute is near-certain; a lone vendor/ base64 with clean mtime is almost certainly FP. (4) Unsure → NEEDS_HUMAN, never delete. Never mark something "clean/verified" you did not actually read. Cite file:line / the matched row / the log line for every CONFIRMED finding.
Removal: reinstall-first, quarantine-first, code-enforced ("never crash")
Follow references/remediation-playbook.md exactly.
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/wp-malware-removal/SKILL.md
Popularity
- Repository stars
- 33
- Repository forks
- 4
- 7-day trend score
- 18
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/adityaarsharma/wordpress-malware-removal/wp-malware-removal)