#!/usr/bin/env python3 """Malicious-pattern detection rules for the intrusion-detection system. Defines compiled regex patterns and classifiers that identify probing/scanning URLs (e.g. env files, wp-admin, webshells) and suspicious file names. Imported by analyzers to flag IPs hitting these patterns as blocklist candidates. """ from __future__ import annotations import re from dataclasses import dataclass, field from typing import ClassVar, Pattern # --------------------------------------------------------------------------- # Pattern categories # --------------------------------------------------------------------------- @dataclass(frozen=True) class BadPattern: """A single detection rule. Attributes: name: Short identifier (e.g. "env_file", "wp_admin"). label: Human-readable description for reports. regex: Compiled regex matched against the request path. severity: 1 = moderate, 2 = high, 3 = critical (auto-block). """ name: str label: str regex: Pattern[str] severity: int = 2 def matches(self, path: str) -> bool: return bool(self.regex.search(path)) # --------------------------------------------------------------------------- # Compiled pattern bank # --------------------------------------------------------------------------- # -- env / config file probes ------------------------------------------------- ENV_FILE = BadPattern( "env_file", "env/config file probe (.env, .aws/credentials, config.json)", re.compile( r"/\.(?:env|config)(?:\.\w+)*|" r"/\.aws/credentials|" r"/config\.json", re.IGNORECASE, ), severity=3, ) # -- version control probes --------------------------------------------------- VCS_PROBE = BadPattern( "vcs_probe", "version-control probe (.git, .svn)", re.compile( r"/\.(?:git|svn)(?:/|$)", re.IGNORECASE, ), severity=3, ) # -- WordPress / CMS probes --------------------------------------------------- WP_PROBE = BadPattern( "wp_probe", "WordPress/CMS probe (wp-admin, wp-login, wp-content, wp-includes)", re.compile( r"/wp-(?:admin|login|content|includes|load|cron|xmlrpc)(?:/|\.php)", re.IGNORECASE, ), severity=2, ) # -- well-known / common probe paths ------------------------------------------ WELL_KNOWN_PROBE = BadPattern( "well_known_probe", ".well-known probe", re.compile( r"/\.well-known/", re.IGNORECASE, ), severity=1, ) # -- webshell / backdoor probes ----------------------------------------------- WEBSHELL_PROBE = BadPattern( "webshell_probe", "webshell/backdoor probe (known shell filenames, phpinfo, file managers)", re.compile( r"/(?:" r"phpinfo|info|shell|cmd|upload|" r"wp_filemanager|filemanager|" r"xstelth|adminfuns|hellopress|" r"fpwch|crgio|coffexium|" r"ccou|biufile|" r"ms-edit|classwithtostring|" r"this_is_a_new_hello_world|" r"chosen|ops|simple|inputs" r")\.php", re.IGNORECASE, ), severity=3, ) # -- suspicious generic PHP file requests ------------------------------------- GENERIC_PHP_PROBE = BadPattern( "generic_php_probe", "generic/random PHP file probe (single-char, numeric names)", re.compile( r"/(?:" r"[a-z]\.php|" # single letter: a.php, k.php, x.php r"\d{1,3}\.php|" # numeric: 1.php, 222.php, 155.php r"[a-z]{2}\.php|" # two-letter: aa.php, dx.php r"a\d\.php|" # a1.php r"\w+\.php" # generic: anything.php r")", re.IGNORECASE, ), severity=1, ) # -- sensitive file probes ---------------------------------------------------- SENSITIVE_FILE = BadPattern( "sensitive_file", "sensitive file probe (id_rsa, id_dsa, .pem, .key)", re.compile( r"/(?:id_rsa|id_dsa|id_ecdsa|id_ed25519|.*\.(?:pem|key|ppk|pfx|p12))", re.IGNORECASE, ), severity=3, ) # -- suspicious user-agent (optional, path-based only for now) ----------------- # --------------------------------------------------------------------------- # All active patterns (ordered by severity, highest first) # --------------------------------------------------------------------------- ALL_PATTERNS: list[BadPattern] = [ ENV_FILE, VCS_PROBE, SENSITIVE_FILE, WEBSHELL_PROBE, WP_PROBE, GENERIC_PHP_PROBE, WELL_KNOWN_PROBE, ] # Quick lookup by name PATTERNS_BY_NAME: dict[str, BadPattern] = {p.name: p for p in ALL_PATTERNS} # --------------------------------------------------------------------------- # Classification API # --------------------------------------------------------------------------- def classify_path(path: str) -> list[BadPattern]: """Return all patterns that match a given request path.""" return [p for p in ALL_PATTERNS if p.matches(path)] def is_malicious(path: str) -> bool: """Return True if any pattern matches the path.""" return any(p.matches(path) for p in ALL_PATTERNS) def malicious_severity(path: str) -> int: """Return the highest severity among matching patterns, or 0 if none.""" matched = classify_path(path) return max((p.severity for p in matched), default=0) def pattern_names(path: str) -> list[str]: """Return names of all patterns matching the path.""" return [p.name for p in classify_path(path)]