Refactor web_scan.py for improved readability and maintainability; add cron job script for npm log backups; update start.ps1 for consistent formatting.
This commit is contained in:
1 parent
0d1cd21468
commit
c2eba88ac8
113 files changed
+772606
-772579
No files matched your search
+190
-190
@@ -1,190 +1,190 @@
|
||||
#!/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)]
|
||||
#!/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)]
|
||||
Reference in new issue
Block a user