Files

191 lines
5.5 KiB
Python

#!/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)]