Press Ctrl+Shift+F (or Cmd+Shift+F on Mac). Click the .* button on the right side of the search box to enable regex mode— this is important because most of these patterns are regex.
Also useful: the “files to include” field at the bottom. Use *.php to limit to PHP files, or *.{php,js} for both. The “files to exclude” field helps too — put **/libraries/**, **/libs/**, **/vendor/**, **/node_modules/** there to skip third-party code.
Tier 1 — The patterns that matter most
These are the high-signal searches. If any of them hit, investigate before doing anything else.
1. Code execution primitives
\b(eval|assert|create_function|preg_replace)\s*\(Then manually check each hit. eval( and assert( are almost always malware in a theme/plugin. create_function is deprecated PHP and suspicious. preg_replace is mostly innocent — but look carefully if the pattern string ends with /e (the deprecated “evaluate” modifier, which executes the replacement as PHP).
2. Decoding primitives
\b(base64_decode|gzinflate|gzuncompress|str_rot13|hex2bin)\s*\(Any of these wrapping a long string, or chained together like eval(base64_decode(gzinflate(...))), is a near-certain red flag. Legitimate use (the Instagram widget I saw earlier) decodes your own data — malicious use decodes a payload baked into the file.
3. String-splitting obfuscation — this is what caught the Homeo backdoor
'[a-z]{2,5}'\s*\.\s*'[a-z]{2,5}'\s*\.\s*'[a-z]{2,5}'Matches things like 'admi'.'nis'.'tra'.'tor' or 'wp'.'_au'.'then'.'ticate'. There’s essentially no legitimate reason to split a short word into three literal strings.
4. File operations that create .php files
(rename|copy|file_put_contents|move_uploaded_file)\s*\([^)]{0,200}\.(php|phtml|phar|pht)A theme or plugin writing .php files to disk is how webshells get planted. There are rare legitimate uses (importers, cache generators) but each one needs scrutiny.
5. Outbound HTTP calls
wp_remote_(post|get|request)|curl_exec|file_get_contents\s*\(\s*['"]https?://Each hit should resolve to a domain you recognize. Look especially for URLs built up piece-by-piece with .= or concatenation, or for suspicious POSTs. The Homeo backdoor used wp_remote_post with an obfuscated URL.
Tier 2 — Context-dependent, worth a look
6. Direct database credential access
DB_PASSWORD|DB_USER|DB_NAME|DB_HOSTThemes should never reference these constants. Plugins occasionally do (legit database-export tools) but it’s uncommon.
7. Authentication-related hooks
wp_authenticate|authenticate_user|wp_set_auth_cookie\s*\(|'wp_login'Look for who’s hooking into login and what they do with the credentials. A filter that logs $password somewhere is malware.
8. User creation / role escalation
wp_create_user|wp_insert_user|'administrator'|set_role\s*\(\s*['"]adminThemes don’t create users. Plugins do, but only in documented flows (user registration). Programmatic admin creation hidden in theme code is a backdoor.
9. Dynamic function calls
\$[a-zA-Z_]\w*\s*\(Noisy but useful. Each hit is a variable-variable function call. In legitimate code, the variable is usually a known callback from a config array ($field['callback']). In malware, it’s built from $_GET or $_POST.
10. Dynamic includes
(require|include)(_once)?\s*\(?\s*\$Variable file includes are an LFI vector. Legitimate code almost always uses dirname(__FILE__), __DIR__, get_template_directory(), or similar constants rather than raw variables.
11. $_GET / $_POST with unusually short keys — the ?r=evet pattern
\$_(GET|POST|REQUEST)\[\s*['"][a-z]{1,2}['"]Most legitimate parameters have descriptive names (post_id, action, page). One- or two-letter parameter names are suspicious, especially when they gate execution behind an if.
12. Hidden-file / non-standard-extension files
# Not a regex — use the File Explorer
# Look for:
.htaccess, .user.ini, *.phtml, *.phar, *.pht, *.php5, *.php7VS Code’s explorer shows these. Hidden .htaccess files can override PHP execution rules to let non-.php files run as PHP.
Tier 3 — The “I haven’t thought of that yet” patterns
13. Conditional execution based on user-agent or IP — sandbox evasion
\$_SERVER\[\s*['"]HTTP_USER_AGENT|\$_SERVER\[\s*['"]REMOTE_ADDRLegitimate uses exist (rate limiting, analytics) but check the context: if the code does something different for specific IPs or user-agents, that’s evasion.
14. chr() or pack() chains — a way to build strings character-by-character
chr\s*\(\s*\d+\s*\)\s*\.\s*chr\s*\(15. Hex-escape strings inside PHP literals
"\\\\x[0-9a-fA-F]{2}\\\\x[0-9a-fA-F]{2}"In PHP double-quoted strings, "\x41\x41" decodes to "AA". Rarely legitimate outside binary-protocol parsers.
16. Cryptominers — specific signatures
coinhive|cryptonight|minero|cryptoloot|webminepool|jsecoin17. SEO spam keywords — compromised sites often get their content altered
\b(viagra|cialis|casino|pharmacy|loan|replica|porn|gambling)\bLots of false positives possible (a real estate site has “loan”), but scan anyway.
18. SVG files with JavaScript
<script|javascript:|onload=|onerror=Scope the search to *.svg. SVGs can contain executable JS; icon fonts shouldn’t.
19. Hidden iframes
<iframe[^>]*(display:\s*none|visibility:\s*hidden|width=['"]?0|height=['"]?0)20. URL-building by concatenation — the $fontUrl .= "https"; $fontUrl .= "://" pattern from the Homeo backdoor
\$\w+\s*\.=\s*['"]https?Tier 4 — Non-PHP/non-JS things to check
Inside image files. Open a .jpg or .png in a hex editor (VS Code has the “Hex Editor” extension). Scroll to the end. Real images end with their format’s end-of-file marker (FF D9 for JPEG, 49 45 4E 44 AE 42 60 82 for PNG). PHP code appended after that is a common steganography trick.
Inside zip files bundled in the theme/plugin. Extract each one and run the full scan inside it. Malware authors often leave the top-level code clean and hide payloads in bundled dependencies.
File modification timestamps. ls -la the directory. If all files have the same mtime but one or two are off by a few minutes, those are worth checking — they may have been modified after the rest of the theme was packaged.
How to interpret hits
When one of these patterns matches, don’t panic — most hits are benign. Ask three questions:
- Does the code do what the file’s name suggests? A file called
class-apus-megamenu.phpshould contain menu code. If it containswp_authenticateandwp_remote_post, that’s wrong regardless of how the code is structured. - Does the data flow make sense? Follow the variable. Where does it come from? Where does it go?
base64_decode($_POST['data'])is very different frombase64_decode($stored_instagram_cache). - Is it trying to hide? Obfuscation itself is the signal. Legitimate code doesn’t need string-splitting, hex escapes, or URL building by concatenation. When you see that pattern, the author was trying to evade something — and that something is usually you looking for malware.
One more thing
If you find something and want a second opinion, don’t run the theme/plugin first to “see what it does.” Use a static analyzer. Free options:
- Wordfence has a free scanner (WordPress plugin) that’ll compare your install against known-clean versions from wordpress.org and flag differences
- Sucuri SiteCheck is a free web scanner at
sitecheck.sucuri.net - PHPStan or Psalm can flag suspicious patterns during a normal code review pass
None of those are perfect, but if you’ve already done a search-based pass with the patterns above and then run a signature-based scanner, you’ve covered most of the ground a hand audit would cover.
