Skip to content

Latest commit

 

History

History
325 lines (243 loc) · 20 KB

rules.md

File metadata and controls

325 lines (243 loc) · 20 KB

Rule reference

This file is generated from the rule registry in src/wp_malware_scan/rules.py. Do not edit it by hand. Regenerate it with python -m wp_malware_scan.ruledoc docs/rules.md.

Indicator values, such as attacker domains and option names, are deliberately not listed here. They live in src/wp_malware_scan/iocs.py.

There are 36 rules. Each has a severity: CRIT, HIGH, MED, LOW or INFO. A rule marked as escalating means one finding from it, on its own, makes the verdict FULLY-COMPROMISED. Escalating rules cannot be allowlisted.

Severity levels

Severity Meaning Effect on the verdict
CRIT Malicious code or content, or something that is almost never harmless. INFECTED, or FULLY-COMPROMISED when the rule escalates.
HIGH Probably harmful, or a risky setting that a person must check. SUSPICIOUS.
MED Worth a manual check. Often legitimate. No change on its own. The report asks for a manual check.
LOW Low risk, or a limit of the scan such as a value that could not be decoded. No change on its own.
INFO Context for the reviewer, such as counts and external domains. No change.

The verdict follows the worst finding that is not allowlisted. An escalating rule gives FULLY-COMPROMISED, otherwise any CRIT gives INFECTED and any HIGH gives SUSPICIOUS. The spam rules (DB-SPAM-040, DB-SPAM-041 and DB-SPAM-042) give SPAM-ONLY when nothing worse was found. With none of these the verdict is CLEAN, which is never proof that a site is clean.

Summary

Id Severity Escalates Title
DB-IOC-001 CRIT yes Known attacker domain in live content
DB-IOC-002 HIGH no Cookie-stealer script marker in live content
DB-IOC-003 INFO no Indicator only in security plugin log tables
DB-OPT-010 CRIT yes Known backdoor option name
DB-OPT-011 CRIT yes Option value decodes to PHP with dangerous calls
DB-OPT-012 CRIT no PHP malware signature in an option value
DB-OPT-013 HIGH no Suspicious option name shape
DB-OPT-014 HIGH no Inline execution or decode call in an option value
DB-OPT-015 CRIT yes Option looks like harvested credentials
DB-OPT-016 LOW no Encoded option value that could not be decoded
DB-POST-020 CRIT no Script injected into posts or post meta
DB-USR-030 INFO no User and administrator counts
DB-USR-031 MED no Unusually many users
DB-SPAM-040 HIGH no SEO spam in published posts
DB-SPAM-041 LOW no Spam in form-submission tables
DB-SPAM-042 MED no Spam text in other tables
DB-DOM-050 INFO no External domains to review
DB-TRG-050 HIGH no Trigger, procedure, function or event in the database
FS-UPL-100 CRIT no Executable PHP in uploads
FS-UPL-101 CRIT no PHP hidden inside a non-PHP file
FS-MU-110 HIGH no Must-use plugin present
FS-DROP-111 MED no Drop-in present
FS-SIG-120 CRIT yes Webshell signature
FS-SIG-121 HIGH no Obfuscated dynamic execution
FS-IOC-130 CRIT yes Known attacker domain in a file
FS-IOC-131 CRIT yes Anti-scanner evasion
FS-IOC-132 CRIT no Fake plugin header or slug
FS-CFG-140 CRIT yes Dangerous code after the settings include in wp-config.php
FS-CFG-141 HIGH no Content before the PHP tag in wp-config.php
FS-CFG-142 HIGH no Statements after the settings include in wp-config.php
FS-HTA-150 CRIT no Traffic-redirect marker in server configuration
FS-HTA-151 HIGH no Prepend or append file hijack
FS-HTA-152 MED no External redirect rewrite rule
FS-HTA-153 CRIT no PHP handler mapped onto non-PHP files
FS-ROOT-160 MED no Non-stock PHP file at the web root
FS-READ-170 MED no PHP file could not be read

Database rules

DB-IOC-001: Known attacker domain in live content

  • Severity: CRIT
  • Escalates: yes, a single finding makes the verdict FULLY-COMPROMISED
  • Matches: A domain from the list of known attacker domains appears in a live database table.
  • Why it matters: Content that loads from or reports to that infrastructure means the site is running attacker code, and false positives are very unlikely apart from a security plugin quoting the domain, which DB-IOC-003 covers.

DB-IOC-002: Cookie-stealer script marker in live content

  • Severity: HIGH
  • Escalates: no
  • Matches: Live database content contains a JavaScript marker used by a known cookie-stealing script.
  • Why it matters: Stolen session cookies let an attacker take over administrator accounts, but some markers are ordinary browser calls, so a legitimate analytics snippet can match and the surrounding code needs a look.

DB-IOC-003: Indicator only in security plugin log tables

  • Severity: INFO
  • Escalates: no
  • Matches: A known indicator appears only inside the log tables of a security plugin and nowhere in live content.
  • Why it matters: The plugin is reporting a detection rather than carrying live malware, so this is informational, but it is worth confirming that the detected item was cleaned.

DB-OPT-010: Known backdoor option name

  • Severity: CRIT
  • Escalates: yes, a single finding makes the verdict FULLY-COMPROMISED
  • Matches: An options row uses a name, or a name pattern, known from backdoor frameworks that keep their payloads in the options table.
  • Why it matters: Rows like this survive file cleanups and let the attacker return, and legitimate plugins are not known to use these names.

DB-OPT-011: Option value decodes to PHP with dangerous calls

  • Severity: CRIT
  • Escalates: yes, a single finding makes the verdict FULLY-COMPROMISED
  • Matches: An option value decodes through base64, single-byte XOR or layered encodings, up to three layers deep, into PHP that contains dangerous function calls.
  • Why it matters: Code stored in the database and loaded at runtime is the signature of a self-healing backdoor, and the usual false positive, a code-snippet plugin, stores plain PHP rather than encoded PHP.

DB-OPT-012: PHP malware signature in an option value

  • Severity: CRIT
  • Escalates: no
  • Matches: An option value contains PHP that matches a high-confidence malware signature, such as eval of a decoded payload or a command run from request input.
  • Why it matters: Backdoors fetch option values and run them, and the usual false positive is a code-snippet plugin storing raw PHP, which is why a bare PHP tag alone does not match.

DB-OPT-013: Suspicious option name shape

  • Severity: HIGH
  • Escalates: no
  • Matches: An option name ends in a long random hex suffix, or sits under a fake core name with a hex suffix, and does not start with a known legitimate prefix.
  • Why it matters: Attackers hide rows behind names that look like core data, and the usual false positive is a plugin cache with a hashed name, which the prefix exclusions are meant to absorb.

DB-OPT-014: Inline execution or decode call in an option value

  • Severity: HIGH
  • Escalates: no
  • Matches: An option value contains an inline call to eval, base64_decode or gzinflate.
  • Why it matters: Option values should be data rather than code, though some plugins and themes store scripts that mention these calls, so check what the value actually does.

DB-OPT-015: Option looks like harvested credentials

  • Severity: CRIT
  • Escalates: yes, a single finding makes the verdict FULLY-COMPROMISED
  • Matches: An option whose name suggests session or activity data holds several lines in a login and password shape.
  • Why it matters: Backdoors harvest administrator credentials into the database for later collection, so every account on the site must be treated as exposed; the report gives the location and line count and never prints the values.

DB-OPT-016: Encoded option value that could not be decoded

  • Severity: LOW
  • Escalates: no
  • Matches: An option value, or a string embedded in it, carries the XOR prefix but cannot be deciphered, is a long base64 or hex blob that no supported codec turns into text, or needs more encoding layers than the decoder follows.
  • Why it matters: A payload that resists decoding is invisible to the other option rules, so it needs a manual look, but large encoded options such as cached images and fonts are common and harmless, which is why this is a low severity pointer and values that decode to an image or font are left out.

DB-POST-020: Script injected into posts or post meta

  • Severity: CRIT
  • Escalates: no
  • Matches: A post or post meta value holds a script block close to a known indicator, an eval call or a beacon call.
  • Why it matters: Injected scripts steal cookies or redirect visitors, sometimes only for logged-in administrators, and the usual false positive is a legitimate embed or analytics snippet, which is why a script alone does not match.

DB-USR-030: User and administrator counts

  • Severity: INFO
  • Escalates: no
  • Matches: Reports how many users exist and how many hold the administrator role.
  • Why it matters: An unexpected administrator is a common persistence trick, so compare the count with the people who should have access; this is informational and never changes the verdict.

DB-USR-031: Unusually many users

  • Severity: MED
  • Escalates: no
  • Matches: The users table holds far more accounts than a typical site needs.
  • Why it matters: Mass signups can be spam or a staging ground for abuse, though membership and shop sites legitimately have many users, so check the registration settings.

DB-SPAM-040: SEO spam in published posts

  • Severity: HIGH
  • Escalates: no
  • Matches: Published posts contain terms and link markup typical of search engine spam.
  • Why it matters: Spam pages damage search rankings and often come with deeper access, and the usual false positive is a post that quotes or discusses spam.

DB-SPAM-041: Spam in form-submission tables

  • Severity: LOW
  • Escalates: no
  • Matches: Spam text appears in tables that store form submissions.
  • Why it matters: Contact form spam comes from the public, is not shown to visitors and does not harm the site itself, so it only calls for inbox hygiene.

DB-SPAM-042: Spam text in other tables

  • Severity: MED
  • Escalates: no
  • Matches: Spam text appears in tables that are neither posts nor form submissions.
  • Why it matters: Spam in comments, options or custom tables can be shown to visitors or point to wider tampering, and the usual benign case is comment spam waiting in moderation.

DB-DOM-050: External domains to review

  • Severity: INFO
  • Escalates: no
  • Matches: Lists the external domains that appear most often in the dump and are not on the benign list, leaving out the site's own domain.
  • Why it matters: Unfamiliar domains are a quick way to spot injected resources, but most will be ordinary services the site uses, so this never changes the verdict.

DB-TRG-050: Trigger, procedure, function or event in the database

  • Severity: HIGH
  • Escalates: no
  • Matches: The dump defines a trigger, a stored procedure, a stored function or a scheduled event. Each kind is reported with the object name, and the usual indicator and script checks also run over its text.
  • Why it matters: WordPress core and common plugins never create these objects, yet a database trigger can rewrite content or re-add a backdoor each time a table changes, even after the files are cleaned, and nothing else in the scan looks at them. Some hosts and tools do add them, so this asks for a manual check. The logic inside is not analysed.

File rules

FS-UPL-100: Executable PHP in uploads

  • Severity: CRIT
  • Escalates: no
  • Matches: A PHP file sits under the uploads directory, apart from stubs, data files and template caches that known frameworks and plugins write there and whose content has been checked.
  • Why it matters: The uploads folder should hold media only, so PHP there is almost always a planted webshell, and the few frameworks and plugins that write harmless stubs, data files or compiled templates there are excused only when the content checks out. Any other plugin that does this is reported and can be allowlisted by prefix.

FS-UPL-101: PHP hidden inside a non-PHP file

  • Severity: CRIT
  • Escalates: no
  • Matches: A file under uploads with a non-PHP name, including a double extension, contains a PHP opening tag.
  • Why it matters: Attackers disguise code as images to pass upload filters and run it through another flaw, and a text file that merely shows PHP source is a rare false positive.

FS-MU-110: Must-use plugin present

  • Severity: HIGH
  • Escalates: no
  • Matches: A file exists in the must-use plugins directory.
  • Why it matters: Must-use plugins load automatically and stay out of the normal plugin list, which makes them a common hiding place, though hosting providers and agencies add legitimate ones that should be checked against their source.

FS-DROP-111: Drop-in present

  • Severity: MED
  • Escalates: no
  • Matches: A WordPress drop-in file, such as an object cache or advanced cache loader, exists in the content directory.
  • Why it matters: Drop-ins replace core behavior and run on every request, so a malicious one is powerful, while caching and multisite setups use them legitimately and the content simply needs verifying.

FS-SIG-120: Webshell signature

  • Severity: CRIT
  • Escalates: yes, a single finding makes the verdict FULLY-COMPROMISED
  • Matches: A PHP file runs eval or an operating system command taken from request input, rewrites itself, or writes PHP code taken from request input.
  • Why it matters: These behaviors hand an attacker remote control of the server, and legitimate code essentially never has this shape, so a match should be treated as a compromise.

FS-SIG-121: Obfuscated dynamic execution

  • Severity: HIGH
  • Escalates: no
  • Matches: A PHP file combines obfuscation with dynamic execution, for example eval of a decoded payload, hex-escaped function calls, a replace function using the code evaluation modifier, or indirection through the globals array.
  • Why it matters: Obfuscated execution hides intent and is typical of malware droppers, though some commercial plugins obfuscate licensed code, so check the vendor before dismissing it.

FS-IOC-130: Known attacker domain in a file

  • Severity: CRIT
  • Escalates: yes, a single finding makes the verdict FULLY-COMPROMISED
  • Matches: A file contains a domain from the list of known attacker domains.
  • Why it matters: Code that contacts that infrastructure is part of an active compromise, and the only usual false positive is a security tool storing a signature list that mentions the domain.

FS-IOC-131: Anti-scanner evasion

  • Severity: CRIT
  • Escalates: yes, a single finding makes the verdict FULLY-COMPROMISED
  • Matches: A PHP file checks the visitor's user agent against a list of three or more scanner names that includes the name of a security service.
  • Why it matters: Malware does this to show clean pages to scanners and bad pages to everyone else, whereas security plugins mention scanner names without checking them as a list against the user agent, and a firewall that blocks attack tools lists only those tools.

FS-IOC-132: Fake plugin header or slug

  • Severity: CRIT
  • Escalates: no
  • Matches: A file carries a plugin header or slug known from fake plugins that attackers use to pass as core or session handling code.
  • Why it matters: A fake plugin lets the attacker persist in plain sight in the plugin list, and a name that merely resembles a real plugin can match by coincidence, so confirm where the plugin came from.

FS-CFG-140: Dangerous code after the settings include in wp-config.php

  • Severity: CRIT
  • Escalates: yes, a single finding makes the verdict FULLY-COMPROMISED
  • Matches: The wp-config.php file contains code after the line that loads the WordPress settings, and that code includes another file, evaluates or decodes text, calls a function on an expression, by variable or by constant, passes a callback as a string or variable, reads request input, writes files, starts a process, contacts the network, or produces output or a redirect.
  • Why it matters: Attackers append loaders after that line, and a loader needs one of these capabilities. Ordinary statements such as a define call or a host snippet use none of them and are reported as FS-CFG-142 instead, while comments and closing tags are not code and do not match. Output and redirects count because a loader can print a payload or send visitors elsewhere with no other capability.

FS-CFG-141: Content before the PHP tag in wp-config.php

  • Severity: HIGH
  • Escalates: no
  • Matches: The wp-config.php file has visible content before the PHP opening tag.
  • Why it matters: Injected content ahead of the tag runs or prints before WordPress loads, and the usual false positive is a byte order mark or stray editor artifact.

FS-CFG-142: Statements after the settings include in wp-config.php

  • Severity: HIGH
  • Escalates: no
  • Matches: The wp-config.php file contains statements after the line that loads the WordPress settings, and none of them uses a dangerous capability.
  • Why it matters: This is normal on some host images and after manual edits, so review it once and allowlist it. Anything dangerous in the same place is reported as FS-CFG-140 instead and cannot be allowlisted.

FS-HTA-150: Traffic-redirect marker in server configuration

  • Severity: CRIT
  • Escalates: no
  • Matches: An .htaccess or .user.ini file contains a marker known from traffic-redirect and cache-bypass malware.
  • Why it matters: The marker shows that server rules were rewritten to redirect or cloak visitors, and the rare false positive is a caching setup that uses a similar name, so review the rule itself.

FS-HTA-151: Prepend or append file hijack

  • Severity: HIGH
  • Escalates: no
  • Matches: A server or PHP configuration file sets auto_prepend_file or auto_append_file.
  • Why it matters: These directives run a chosen file on every PHP request, which attackers use for persistence, though a firewall plugin sets them legitimately, so its own loader is excused only when that loader sits at the site root with the expected shape and every other target must be confirmed.

FS-HTA-152: External redirect rewrite rule

  • Severity: MED
  • Escalates: no
  • Matches: A rewrite rule in the server configuration redirects to an external site.
  • Why it matters: Attackers send search visitors or mobile users elsewhere with such rules, but domain migrations and other legitimate redirects match too, so check the destination.

FS-HTA-153: PHP handler mapped onto non-PHP files

  • Severity: CRIT
  • Escalates: no
  • Matches: An .htaccess file under uploads maps a PHP handler, or an .htaccess file anywhere maps a PHP handler onto a media or text file extension.
  • Why it matters: Making an image or a text file run as PHP lets an attacker upload code under a harmless name, and an ordinary host line that maps only PHP extensions to the PHP handler does not match.

FS-ROOT-160: Non-stock PHP file at the web root

  • Severity: MED
  • Escalates: no
  • Matches: A PHP file at the site root is not part of a standard WordPress install.
  • Why it matters: Planted scripts often sit beside core files, and the usual benign cases are site verification files, host tools and custom entry points.

FS-READ-170: PHP file could not be read

  • Severity: MED
  • Escalates: no
  • Matches: A PHP file could not be opened or read during the scan, or it was larger than the read limit and only its head and tail were scanned.
  • Why it matters: An unreadable or partly read file is an unscanned file and permissions or a very large file can be used to hide a payload, though broken permissions on a restored backup are the usual cause, so inspect the file manually.