mirror of
https://github.com/mytechnotalent/Threat-Modeling-Toolkit.git
synced 2026-07-06 02:37:48 +02:00
193 lines
9.6 KiB
Python
193 lines
9.6 KiB
Python
"""Scanner for detecting authentication and session management vulnerabilities.
|
|
|
|
Identifies missing authentication decorators, insecure session configuration,
|
|
absent CSRF protection, weak password handling, session fixation risks,
|
|
and missing authorization checks on protected endpoints.
|
|
"""
|
|
|
|
from typing import List
|
|
|
|
from tmt.config import ScannerConfig
|
|
from tmt.models import FindingCategory, Severity
|
|
from tmt.scanners.base_scanner import BaseScanner, VulnerabilityPattern
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Missing authentication on route handlers
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
MISSING_AUTH_DECORATOR = VulnerabilityPattern(
|
|
name="Route Handler Missing Authentication",
|
|
trigger_pattern=r"@(app|router|blueprint)\.(get|post|put|patch|delete)\s*\(",
|
|
defense_pattern=r"(login_required|auth_required|authenticated|Depends.*auth|jwt_required|token_required|IsAuthenticated|@require|@protect|@secured)",
|
|
context_window=8,
|
|
description=(
|
|
"An API route handler does not have a visible authentication "
|
|
"decorator or dependency. Unauthenticated access to endpoints "
|
|
"can expose sensitive data or allow unauthorized operations."
|
|
),
|
|
severity=Severity.HIGH,
|
|
category=FindingCategory.AUTH_SESSION,
|
|
recommendation=(
|
|
"Apply an authentication decorator or dependency to every non-public "
|
|
"endpoint. Use a whitelist approach where routes are authenticated "
|
|
"by default and explicitly marked as public."
|
|
),
|
|
cwe_id="CWE-306",
|
|
confidence=0.6,
|
|
)
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Insecure session configuration
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
INSECURE_SESSION_CONFIG = VulnerabilityPattern(
|
|
name="Insecure Session Cookie Configuration",
|
|
trigger_pattern=r"(SESSION_COOKIE_SECURE|session.*secure|cookie.*secure)\s*=\s*(False|false|0)",
|
|
defense_pattern=None,
|
|
context_window=5,
|
|
description=(
|
|
"Session cookie is configured without the Secure flag. Cookies "
|
|
"will be transmitted over unencrypted HTTP connections, allowing "
|
|
"session hijacking via network sniffing."
|
|
),
|
|
severity=Severity.HIGH,
|
|
category=FindingCategory.AUTH_SESSION,
|
|
recommendation=(
|
|
"Set SESSION_COOKIE_SECURE=True and SESSION_COOKIE_HTTPONLY=True. "
|
|
"Also set SESSION_COOKIE_SAMESITE='Lax' or 'Strict' to prevent "
|
|
"CSRF attacks via cookie inclusion."
|
|
),
|
|
cwe_id="CWE-614",
|
|
confidence=0.95,
|
|
)
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Missing CSRF protection
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
MISSING_CSRF = VulnerabilityPattern(
|
|
name="Missing CSRF Protection on State-Changing Endpoint",
|
|
trigger_pattern=r"@(app|router|blueprint)\.(post|put|patch|delete)\s*\(",
|
|
defense_pattern=r"(csrf|CSRFProtect|CsrfViewMiddleware|csurf|_token|xsrf|anti_forgery|SameSite|Bearer)",
|
|
context_window=20,
|
|
description=(
|
|
"State-changing endpoint lacks visible CSRF protection. Without CSRF "
|
|
"tokens or SameSite cookie policy, an attacker can craft malicious "
|
|
"pages that trigger authenticated actions on behalf of users."
|
|
),
|
|
severity=Severity.MEDIUM,
|
|
category=FindingCategory.AUTH_SESSION,
|
|
recommendation=(
|
|
"For cookie-based auth: implement CSRF tokens on all state-changing "
|
|
"endpoints. For token-based auth (Bearer tokens): ensure tokens are "
|
|
"not stored in cookies. Set SameSite=Lax on session cookies."
|
|
),
|
|
cwe_id="CWE-352",
|
|
confidence=0.5,
|
|
)
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Weak password hashing
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
WEAK_PASSWORD_HASH = VulnerabilityPattern(
|
|
name="Weak Password Hashing Algorithm",
|
|
trigger_pattern=r"(hashlib\.(md5|sha1|sha256)\s*\(|MD5|SHA1|createHash\s*\(\s*['\"](?:md5|sha1)['\"])",
|
|
defense_pattern=r"(bcrypt|argon2|scrypt|pbkdf2|passlib|password_hash|hash_password)",
|
|
context_window=10,
|
|
description=(
|
|
"Password hashing uses a fast, non-salted algorithm like MD5 or SHA1. "
|
|
"These can be reversed with rainbow tables or brute-forced at billions "
|
|
"of attempts per second on modern GPUs."
|
|
),
|
|
severity=Severity.CRITICAL,
|
|
category=FindingCategory.AUTH_SESSION,
|
|
recommendation=(
|
|
"Use bcrypt, argon2id, or scrypt for password hashing. These algorithms "
|
|
"include salting and configurable work factors that resist brute-force. "
|
|
"Migrate existing hashes on next user login."
|
|
),
|
|
cwe_id="CWE-916",
|
|
confidence=0.85,
|
|
)
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Session fixation risk
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
SESSION_FIXATION = VulnerabilityPattern(
|
|
name="Session Not Regenerated After Authentication",
|
|
trigger_pattern=r"(def\s+login|def\s+authenticate|def\s+sign_in|async\s+def\s+login)\s*\(",
|
|
defense_pattern=r"(session\.regenerate|cycle_key|rotate.*session|new_session|session\.clear|flush.*session|create_session)",
|
|
context_window=15,
|
|
description=(
|
|
"Login handler does not regenerate the session ID after successful "
|
|
"authentication. An attacker who sets a session cookie before login "
|
|
"retains access to the authenticated session."
|
|
),
|
|
severity=Severity.HIGH,
|
|
category=FindingCategory.AUTH_SESSION,
|
|
recommendation=(
|
|
"Regenerate the session ID immediately after successful authentication. "
|
|
"In Django use request.session.cycle_key(), in Flask use "
|
|
"session.regenerate(), in Express use req.session.regenerate()."
|
|
),
|
|
cwe_id="CWE-384",
|
|
confidence=0.7,
|
|
)
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Missing authorization (IDOR risk)
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
MISSING_AUTHORIZATION_CHECK = VulnerabilityPattern(
|
|
name="Object Access Without Authorization Check",
|
|
trigger_pattern=r"\.(get|filter|find_one|findById|findOne)\s*\(\s*(request\.(args|params|query|json|form|data)|req\.(params|query|body))",
|
|
defense_pattern=r"(owner|user_id.*current|current_user|request\.user|belongs_to|authorize|permission|can\s*\(|has_perm)",
|
|
context_window=10,
|
|
description=(
|
|
"Database query uses user-supplied ID without checking ownership or "
|
|
"permissions. An attacker can modify the ID parameter to access "
|
|
"other users' data (Insecure Direct Object Reference)."
|
|
),
|
|
severity=Severity.CRITICAL,
|
|
category=FindingCategory.AUTH_SESSION,
|
|
recommendation=(
|
|
"Always filter queries by the authenticated user's ID or check "
|
|
"object ownership before returning data. Use a policy layer or "
|
|
"scope queries: Model.objects.filter(user=request.user, id=obj_id)."
|
|
),
|
|
cwe_id="CWE-639",
|
|
confidence=0.75,
|
|
)
|
|
|
|
|
|
class AuthSessionScanner(BaseScanner):
|
|
"""Scanner specialized in detecting authentication and session vulnerabilities.
|
|
|
|
Detects missing authentication, insecure session configuration,
|
|
CSRF gaps, weak password hashing, session fixation, and IDOR
|
|
risks across Python and JavaScript codebases.
|
|
"""
|
|
|
|
PATTERNS: List[VulnerabilityPattern] = [
|
|
MISSING_AUTH_DECORATOR,
|
|
INSECURE_SESSION_CONFIG,
|
|
MISSING_CSRF,
|
|
WEAK_PASSWORD_HASH,
|
|
SESSION_FIXATION,
|
|
MISSING_AUTHORIZATION_CHECK,
|
|
]
|
|
|
|
def __init__(
|
|
self, config: ScannerConfig, file_extensions: List[str], exclude_dirs: List[str]
|
|
):
|
|
"""Initialize the authentication and session scanner.
|
|
|
|
Args:
|
|
config: Scanner configuration controlling behavior.
|
|
file_extensions: File extensions to include in scanning.
|
|
exclude_dirs: Directory names to skip during traversal.
|
|
"""
|
|
super().__init__(config, file_extensions, exclude_dirs)
|