#!/usr/bin/env python3 """ Vulnerability Scanner - Generate OWASP Top 10 security checklists and scan for common patterns. Table of Contents: VulnerabilityScanner - Main class for vulnerability scanning __init__ - Initialize with target type and scope generate_checklist - Generate OWASP Top 10 checklist for target scan_source - Scan source directory for vulnerability patterns _scan_file - Scan individual file for regex patterns _get_owasp_checks - Return OWASP checks for target type main() - CLI entry point Usage: python vulnerability_scanner.py --target web --scope full python vulnerability_scanner.py --target api --scope quick --json python vulnerability_scanner.py --target web --source /path/to/code --scope full """ import argparse import json import os import re import sys from dataclasses import dataclass, asdict, field from datetime import datetime from pathlib import Path from typing import Dict, List, Optional @dataclass class CheckItem: """A single check item in the OWASP checklist.""" owasp_id: str owasp_category: str check_id: str title: str description: str test_procedure: str severity: str # critical, high, medium, low, info applicable_targets: List[str] = field(default_factory=list) status: str = "pending" # pending, pass, fail, na @dataclass class SourceFinding: """A vulnerability pattern found in source code.""" rule_id: str title: str severity: str owasp_category: str file_path: str line_number: int code_snippet: str recommendation: str class VulnerabilityScanner: """Generate OWASP Top 10 checklists and scan source code for vulnerability patterns.""" SCAN_EXTENSIONS = { ".py", ".js", ".ts", ".jsx", ".tsx", ".java", ".go", ".rb", ".php", ".cs", ".rs", ".html", ".vue", ".svelte", } SKIP_DIRS = { "node_modules", ".git", "__pycache__", ".venv", "venv", "vendor", "dist", "build", ".next", "target", } def __init__(self, target: str = "web", scope: str = "full", source: Optional[str] = None): self.target = target self.scope = scope self.source = source def generate_checklist(self) -> List[CheckItem]: """Generate OWASP Top 10 checklist for the given target and scope.""" all_checks = self._get_owasp_checks() filtered = [] for check in all_checks: if self.target not in check.applicable_targets and "all" not in check.applicable_targets: continue if self.scope == "quick" and check.severity in ("low", "info"): continue filtered.append(check) return filtered def scan_source(self, path: str) -> List[SourceFinding]: """Scan source directory for common vulnerability patterns.""" findings = [] source_path = Path(path) if not source_path.exists(): return findings for root, dirs, files in os.walk(source_path): dirs[:] = [d for d in dirs if d not in self.SKIP_DIRS] for fname in files: fpath = Path(root) / fname if fpath.suffix in self.SCAN_EXTENSIONS: findings.extend(self._scan_file(fpath)) return findings def _scan_file(self, file_path: Path) -> List[SourceFinding]: """Scan a single file for vulnerability patterns.""" findings = [] try: content = file_path.read_text(encoding="utf-8", errors="ignore") except (OSError, PermissionError): return findings patterns = [ { "rule_id": "SQLI-001", "title": "Potential SQL Injection (string concatenation)", "severity": "critical", "owasp_category": "A03:2021 - Injection", "pattern": r'''(?:execute|query|cursor\.execute)\s*\(\s*(?:f["\']|["\'].*%s|["\'].*\+\s*\w+|["\'].*\.format)''', "recommendation": "Use parameterized queries or prepared statements instead of string concatenation.", "extensions": {".py", ".js", ".ts", ".java", ".rb", ".php"}, }, { "rule_id": "SQLI-002", "title": "Potential SQL Injection (template literal)", "severity": "critical", "owasp_category": "A03:2021 - Injection", "pattern": r'''(?:query|execute|raw)\s*\(\s*`[^`]*\$\{''', "recommendation": "Use parameterized queries. Never interpolate user input into SQL strings.", "extensions": {".js", ".ts", ".jsx", ".tsx"}, }, { "rule_id": "XSS-001", "title": "Potential DOM-based XSS (innerHTML)", "severity": "high", "owasp_category": "A03:2021 - Injection", "pattern": r'''\.innerHTML\s*=\s*(?!['"][^'"]*['"])''', "recommendation": "Use textContent or a sanitization library (DOMPurify) instead of innerHTML.", "extensions": {".js", ".ts", ".jsx", ".tsx", ".html", ".vue", ".svelte"}, }, { "rule_id": "XSS-002", "title": "React dangerouslySetInnerHTML usage", "severity": "high", "owasp_category": "A03:2021 - Injection", "pattern": r'''dangerouslySetInnerHTML''', "recommendation": "Sanitize HTML with DOMPurify before using dangerouslySetInnerHTML.", "extensions": {".jsx", ".tsx", ".js", ".ts"}, }, { "rule_id": "CMDI-001", "title": "Potential Command Injection (shell=True)", "severity": "critical", "owasp_category": "A03:2021 - Injection", "pattern": r'''subprocess\.\w+\(.*shell\s*=\s*True''', "recommendation": "Avoid shell=True. Use subprocess with a list of arguments instead.", "extensions": {".py"}, }, { "rule_id": "CMDI-002", "title": "Potential Command Injection (eval/exec)", "severity": "critical", "owasp_category": "A03:2021 - Injection", "pattern": r'''(?:^|\s)(?:eval|exec)\s*\((?!.*(?:#\s*nosec|NOSONAR))''', "recommendation": "Never use eval() or exec() with untrusted input. Use ast.literal_eval() for data parsing.", "extensions": {".py", ".js", ".ts"}, }, { "rule_id": "SEC-001", "title": "Hardcoded Secret or API Key", "severity": "critical", "owasp_category": "A02:2021 - Cryptographic Failures", "pattern": r'''(?i)(?:api[_-]?key|secret[_-]?key|password|passwd|token)\s*[:=]\s*['\"][a-zA-Z0-9+/=]{16,}['\"]''', "recommendation": "Move secrets to environment variables or a secrets manager (Vault, AWS Secrets Manager).", "extensions": {".py", ".js", ".ts", ".jsx", ".tsx", ".java", ".go", ".rb", ".php"}, }, { "rule_id": "SEC-002", "title": "AWS Access Key ID detected", "severity": "critical", "owasp_category": "A02:2021 - Cryptographic Failures", "pattern": r'''AKIA[0-9A-Z]{16}''', "recommendation": "Remove the AWS key immediately. Rotate the credential and use IAM roles or environment variables.", "extensions": None, # scan all files }, { "rule_id": "CRYPTO-001", "title": "Weak hashing algorithm (MD5/SHA1)", "severity": "high", "owasp_category": "A02:2021 - Cryptographic Failures", "pattern": r'''(?:md5|sha1)\s*\(''', "recommendation": "Use bcrypt, scrypt, or argon2 for passwords. Use SHA-256+ for integrity checks.", "extensions": {".py", ".js", ".ts", ".java", ".go", ".rb", ".php"}, }, { "rule_id": "SSRF-001", "title": "Potential SSRF (user-controlled URL in HTTP request)", "severity": "high", "owasp_category": "A10:2021 - SSRF", "pattern": r'''(?:requests\.get|fetch|axios|http\.get|urllib\.request\.urlopen)\s*\(\s*(?:request\.|req\.|params|args|input|user)''', "recommendation": "Validate and allowlist URLs before making outbound requests. Block internal IPs.", "extensions": {".py", ".js", ".ts", ".jsx", ".tsx", ".java", ".go"}, }, { "rule_id": "PATH-001", "title": "Potential Path Traversal", "severity": "high", "owasp_category": "A01:2021 - Broken Access Control", "pattern": r'''(?:open|readFile|readFileSync|Path\.join)\s*\(.*(?:request\.|req\.|params|args|input|user)''', "recommendation": "Sanitize file paths. Use os.path.basename() and validate against an allowlist.", "extensions": {".py", ".js", ".ts", ".java", ".go"}, }, { "rule_id": "DESER-001", "title": "Unsafe Deserialization (pickle/yaml.load)", "severity": "critical", "owasp_category": "A08:2021 - Software and Data Integrity Failures", "pattern": r'''(?:pickle\.load|yaml\.load\s*\([^)]*\)\s*(?!.*Loader\s*=\s*yaml\.SafeLoader))''', "recommendation": "Use yaml.safe_load() instead of yaml.load(). Avoid pickle for untrusted data.", "extensions": {".py"}, }, { "rule_id": "AUTH-001", "title": "JWT with hardcoded secret", "severity": "critical", "owasp_category": "A07:2021 - Identification and Authentication Failures", "pattern": r'''jwt\.(?:encode|sign)\s*\([^)]*['\"][a-zA-Z0-9]{8,}['\"]''', "recommendation": "Load JWT secrets from environment variables. Use RS256 with key pairs for production.", "extensions": {".py", ".js", ".ts"}, }, ] lines = content.split("\n") for i, line in enumerate(lines, 1): for pat in patterns: exts = pat.get("extensions") if exts and file_path.suffix not in exts: continue if re.search(pat["pattern"], line): findings.append(SourceFinding( rule_id=pat["rule_id"], title=pat["title"], severity=pat["severity"], owasp_category=pat["owasp_category"], file_path=str(file_path), line_number=i, code_snippet=line.strip()[:200], recommendation=pat["recommendation"], )) return findings def _get_owasp_checks(self) -> List[CheckItem]: """Return comprehensive OWASP Top 10 checklist items.""" checks = [ # A01: Broken Access Control CheckItem("A01", "Broken Access Control", "A01-01", "Horizontal Privilege Escalation", "Verify users cannot access other users' resources by changing IDs.", "Change resource IDs in API requests (e.g., /users/123 → /users/124). Expect 403.", "critical", ["web", "api", "all"]), CheckItem("A01", "Broken Access Control", "A01-02", "Vertical Privilege Escalation", "Verify regular users cannot access admin endpoints.", "Authenticate as regular user, request admin endpoints. Expect 403.", "critical", ["web", "api", "all"]), CheckItem("A01", "Broken Access Control", "A01-03", "CORS Misconfiguration", "Verify CORS policy does not allow arbitrary origins.", "Send request with Origin: https://evil.com. Check Access-Control-Allow-Origin.", "high", ["web", "api"]), CheckItem("A01", "Broken Access Control", "A01-04", "Forced Browsing", "Check for unprotected admin or debug pages.", "Request /admin, /debug, /api/admin, /.env, /swagger. Expect 403 or 404.", "high", ["web", "all"]), CheckItem("A01", "Broken Access Control", "A01-05", "Directory Listing", "Verify directory listing is disabled on the web server.", "Request directory paths without index file. Should not list contents.", "medium", ["web"]), # A02: Cryptographic Failures CheckItem("A02", "Cryptographic Failures", "A02-01", "TLS Version Check", "Ensure TLS 1.2+ is enforced. Reject TLS 1.0/1.1.", "Run: nmap --script ssl-enum-ciphers -p 443 target.com", "high", ["web", "api", "all"]), CheckItem("A02", "Cryptographic Failures", "A02-02", "Password Hashing Algorithm", "Verify passwords use bcrypt/scrypt/argon2 with adequate cost.", "Review authentication code for hashing implementation.", "critical", ["web", "api", "all"]), CheckItem("A02", "Cryptographic Failures", "A02-03", "Sensitive Data in URLs", "Check for tokens, passwords, or PII in query parameters.", "Review access logs and URL patterns for sensitive query params.", "high", ["web", "api"]), CheckItem("A02", "Cryptographic Failures", "A02-04", "HSTS Header", "Verify Strict-Transport-Security header is present.", "Check response headers for HSTS with max-age >= 31536000.", "medium", ["web"]), # A03: Injection CheckItem("A03", "Injection", "A03-01", "SQL Injection", "Test input fields for SQL injection vulnerabilities.", "Submit ' OR 1=1-- in input fields. Check for errors or unexpected behavior.", "critical", ["web", "api", "all"]), CheckItem("A03", "Injection", "A03-02", "XSS (Cross-Site Scripting)", "Test for reflected, stored, and DOM-based XSS.", "Submit in input fields. Check if rendered.", "high", ["web", "all"]), CheckItem("A03", "Injection", "A03-03", "Command Injection", "Test for OS command injection in input fields.", "Submit ; whoami in fields that may trigger system commands.", "critical", ["web", "api"]), CheckItem("A03", "Injection", "A03-04", "Template Injection", "Test for server-side template injection.", "Submit {{7*7}} and ${7*7} in input fields. Check for 49 in response.", "high", ["web", "api"]), CheckItem("A03", "Injection", "A03-05", "NoSQL Injection", "Test for NoSQL injection in JSON inputs.", "Submit {\"$gt\": \"\"} in JSON fields. Check for data leakage.", "high", ["api"]), # A04: Insecure Design CheckItem("A04", "Insecure Design", "A04-01", "Rate Limiting on Authentication", "Verify rate limiting exists on login and password reset endpoints.", "Send 50+ rapid login requests. Expect 429 after threshold.", "high", ["web", "api", "all"]), CheckItem("A04", "Insecure Design", "A04-02", "Business Logic Abuse", "Test for business logic flaws (negative quantities, state manipulation).", "Try negative values, skip steps in workflows, manipulate client-side calculations.", "high", ["web", "api"]), CheckItem("A04", "Insecure Design", "A04-03", "Account Lockout", "Verify account lockout after repeated failed login attempts.", "Submit 10+ failed login attempts. Check for lockout or CAPTCHA.", "medium", ["web", "api"]), # A05: Security Misconfiguration CheckItem("A05", "Security Misconfiguration", "A05-01", "Default Credentials", "Check for default credentials on admin panels and services.", "Try admin:admin, root:root, admin:password on all login forms.", "critical", ["web", "api", "all"]), CheckItem("A05", "Security Misconfiguration", "A05-02", "Debug Mode in Production", "Verify debug mode is disabled in production.", "Trigger errors and check for stack traces, debug info, or verbose errors.", "high", ["web", "api", "all"]), CheckItem("A05", "Security Misconfiguration", "A05-03", "Security Headers", "Verify all security headers are present and properly configured.", "Check for CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy.", "medium", ["web"]), CheckItem("A05", "Security Misconfiguration", "A05-04", "Unnecessary HTTP Methods", "Verify only required HTTP methods are enabled.", "Send OPTIONS request. Check for TRACE, DELETE on public endpoints.", "low", ["web", "api"]), # A06: Vulnerable Components CheckItem("A06", "Vulnerable and Outdated Components", "A06-01", "Dependency CVE Audit", "Scan all dependencies for known CVEs.", "Run npm audit, pip audit, govulncheck, or bundle audit.", "high", ["web", "api", "mobile", "all"]), CheckItem("A06", "Vulnerable and Outdated Components", "A06-02", "End-of-Life Framework Check", "Verify no EOL frameworks or languages are in use.", "Check framework versions against vendor EOL dates.", "medium", ["web", "api", "all"]), # A07: Authentication Failures CheckItem("A07", "Identification and Authentication Failures", "A07-01", "Brute Force Protection", "Verify brute force protection on authentication endpoints.", "Send 100 rapid login attempts. Expect blocking after threshold.", "high", ["web", "api", "all"]), CheckItem("A07", "Identification and Authentication Failures", "A07-02", "Session Management", "Verify sessions are properly managed (HttpOnly, Secure, SameSite).", "Check cookie flags: HttpOnly, Secure, SameSite=Strict|Lax.", "high", ["web"]), CheckItem("A07", "Identification and Authentication Failures", "A07-03", "Session Invalidation on Logout", "Verify sessions are invalidated on logout.", "Logout, then replay the session cookie. Should receive 401.", "high", ["web", "api"]), CheckItem("A07", "Identification and Authentication Failures", "A07-04", "Username Enumeration", "Check for username enumeration via error messages.", "Submit valid and invalid usernames. Error messages should be identical.", "medium", ["web", "api"]), # A08: Data Integrity CheckItem("A08", "Software and Data Integrity Failures", "A08-01", "Unsafe Deserialization", "Check for unsafe deserialization of user input.", "Review code for pickle.load(), yaml.load(), Java ObjectInputStream.", "critical", ["web", "api"]), CheckItem("A08", "Software and Data Integrity Failures", "A08-02", "Subresource Integrity", "Verify SRI hashes on CDN-loaded scripts and stylesheets.", "Check