Skip to content
Security
Skill

/hunting-web-backdoors

Hunt planted webshells and backdoors across a web source tree — PHP first (also JSP, ASP, Node) — triaging a directory at scale, statically decoding obfuscation layers without ever executing the payload, finding append-infections and fake plugins, and treating known shell

BOOST
From plugin
secskills
17094 skills
Install
$ npx -y skills add trilwu/secskills --skill hunting-web-backdoors --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/hunting-web-backdoors

Context preview

The summary Claude sees to decide when to auto-load this skill.

Hunt planted webshells and backdoors across a web source tree — PHP first (also JSP, ASP, Node) — triaging a directory at scale, statically decoding obfuscation layers without ever executing the payload, finding append-infections and fake plugins, and treating known shell

SKILL.md

hunting-web-backdoors.SKILL.md
name: hunting-web-backdoors
description: Hunt planted webshells and backdoors across a web source tree — PHP first (also JSP, ASP, Node) — triaging a directory at scale, statically decoding obfuscation layers without ever executing the payload, finding append-infections and fake plugins, and treating known shell families as leads rather than verdicts. Use when a web server is suspected compromised, cleaning a hacked WordPress/Magento/CMS site, vetting a downloaded PHP codebase or plugin for hidden malicious code, or when a file contains eval on decoded input.
verified: 2026-07-27

Hunting Web Backdoors

This is not a vulnerability audit. You are looking for code an attacker **already planted** — a webshell, a one-line eval backdoor, a fake plugin, a malicious line appended to a legitimate file. The weakness that let them in is `auditing-php-applications`' job; this skill finds what they left behind.

Two things make it hard. The malicious code is deliberately hidden — obfuscated, split, or buried in a tree of thousands of legitimate files — so a naive grep misses it. And the tells (`eval`, `base64_decode`, dynamic calls) appear in plenty of benign code, so a naive grep also drowns you in false positives. The work is the discrimination.

When to Use

  • A web server is suspected or confirmed compromised and you need every shell
  • Cleaning a hacked WordPress, Magento, Joomla, or custom PHP site
  • Vetting a downloaded plugin, theme, or codebase before deploying it
  • A file contains `eval`, `assert`, or `system` on decoded or request-derived input
  • Triaging a web root full of unfamiliar PHP for planted malice

When NOT to Use

  • **Finding the vulnerability that allowed the upload** — use

`auditing-php-applications`

  • **Deep single-sample analysis** (a specific shell you want to fully reverse,

its C2, its capabilities) — use `analyzing-malware`

  • **The broader incident** (scoping, timeline, containment) — use

`responding-to-incidents`; this skill is the source-tree sweep within it

  • **Malicious dependencies / package install scripts** — use

`auditing-supply-chain`

  • **Host-level persistence** (cron, systemd, LD_PRELOAD) — use

`analyzing-linux-persistence`

Never Execute to Deobfuscate

The single rule that must not bend: **the obfuscated code is the payload.** Do not run it, do not "just let PHP decode it," do not replace `eval(` with `echo(` and execute the file, do not paste it into an online sandbox that runs it. Deobfuscation is a **static** transformation:

  • Decode the layers by hand or with a decode-only tool (`base64_decode`,

`gzinflate`, `gzuncompress`, `str_rot13`, hex/`\xNN`, `strrev`) applied as string operations, not as executed PHP.

  • If you must automate, use a de-obfuscator that decodes without executing

(UnPHP's decoder, a Python reimplementation of the decode chain) — never the PHP interpreter on the sample.

  • Replacing the final `eval(` with `file_put_contents('out.php', ...)` and

running it *is* executing the payload if any prior layer has side effects. Decode statically, then read.

"Most webshells are three or four layers of encoding around ten lines of logic" — peel to the logic, do not detonate the wrapper.

Triage a Directory at Scale

You cannot read every file. Rank candidates by cheap signals, then read the top of the list:

# 1. PHP where PHP should not be — uploads, cache, images, media dirs
find wp-content/uploads -name '*.php' -o -name '*.php[0-9]' -o -name '*.phtml' 2>/dev/null

# 2. Recently modified, clustered in time (attackers touch many files at once)
find . -name '*.php' -newermt '2026-07-01' -printf '%T+ %p\n' | sort

# 3. High entropy / long single lines — the shape of packed obfuscation
find . -name '*.php' -exec awk 'length>1000{print FILENAME": "length; nextfile}' {} \;

# 4. The dangerous-sink grep — leads, NOT verdicts (see the FP problem below)
grep -rnE '\b(eval|assert|system|passthru|shell_exec|proc_open|popen|create_function)\s*\(' --include='*.php' .
grep -rnE '(base64_decode|gzinflate|gzuncompress|str_rot13|hex2bin)\s*\(' --include='*.php' .

# 5. Request-driven code execution — the webshell core
grep -rnE '\$_(GET|POST|REQUEST|COOKIE|SERVER)\s*\[[^]]*\]\s*\(' --include='*.php' .
grep -rnE '(eval|assert|system|passthru)\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)' --include='*.php' .

The most reliable single lead is **variable execution of request data** — `$_POST['x']()`, `ev[a]l($_GET[...])`, `ass[e]rt($_REQUEST[...])`, `call_user_func($_GET['f'], ...)`. Legitimate code almost never dispatches a function name straight from a superglobal.

The Grep Is Not Clean Just Because It Is Empty

A source tree that returns nothing for `grep eval` can still be backdoored. Attackers defeat the obvious grep deliberately:

  • **Concatenation:** `$a='ev'.'al'; $a($code);` or `('e'.'v'.'a'.'l')(...)`.
  • **Variable variables / indirection:** `$f='system'; $f($_GET['c']);`,

`${'_'.'GET'}`, `$$x`.

  • **Char/hex/octal escapes:** `"\x65\x76\x61\x6c"` is `eval`;

`chr(101).chr(118)...`.

  • **Callback sinks:** `array_map`, `array_filter`, `usort`,

`preg_replace_callback`, `register_shutdown_function`, `ob_start` with a user-controlled callback.

  • **String-eval that is not `eval`:** `assert($str)` (pre-8.0),

`preg_replace('/./e', $str, ...)` (pre-7.0), `create_function('', $str)` (pre-8.0), `mb_ereg_replace` with `e`.

  • **Comment and whitespace insertion** between token pieces to break naive

regex.

So grep is a *lead generator*, not a coverage guarantee. When grep is empty and you still suspect infection, fall back to: entropy/long-line ranking, mtime clustering, and diffing against a known-good copy (below).

Append-Infections Are the Ones You Miss

The hardest infection is a backdoor **appended to or inserted into a legitimate file** — `index.php`, `wp-config.php`, a theme's `functions.php`, `vendor/` autoloaders. The file is 99% real, so it does not stand out by name, location, or entropy overall

Read more
Ships withsecskills

**Security skills for Claude Code that encode the judgment professionals bring, not another wrapper around a scanner.** Offense, defense, and the reverse-engineering and code-audit core both sides share. Every skill is fact-checked against primary sources.

Get the whole plugin

Other skills on secskills.