v3.3.0 GUI dashboard + reports + model expansion + root fix

Engine:
- Fix: inject IS_SANDBOX=1 so Claude Code's --dangerously-skip-permissions
  works under root (real backend runs were exiting rc=1 immediately)
- models: expand to 40 models / 13 providers, tagged CLI vs API
  (NVIDIA NIM, DeepSeek, Mistral, Qwen/DashScope, Groq, Together, OpenRouter,
  Ollama, Gemini) — Qwen/DeepSeek/Llama usable via API
- backends: on_start callback surfaces the exact argv ("what runs behind it")
- orchestrator: require a Playwright screenshot per confirmed finding; collect
  results/activity.json; auto-generate reports after a run
- report.py: HTML always + PDF via Typst engine (.typ source emitted too)

Web dashboard (webgui/, stdlib only — no npm/build):
- Sidebar dashboard (PentAGI-style): Run / Agents / Insights / Reports / Settings
- Multi-target runs; live execution console + per-task activity; finding cards
  with screenshots; backend+provider+model pickers (CLI & API)
- Agents tab: browse 213 + add new .md agents from the UI
- Insights: interactive RL-weight + severity charts
- Reports: download/preview PDF + HTML
- Settings/API: execution mode, per-provider API keys, orchestrator, verbosity
- Endpoints: /api/agents (GET/POST), /api/rl, /api/config, /api/reports,
  /reports/* + /shots/* static serving

Cleanup: retire replaced web stack (frontend React, FastAPI backend, core
orchestration, old test) to legacy/. Active engine + GUI are fully standalone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
CyberSecurityUP
2026-06-14 23:26:11 -03:00
parent 22a7302a35
commit a5badefc29
205 changed files with 809 additions and 199 deletions
+13
View File
@@ -0,0 +1,13 @@
from backend.core.vuln_engine.registry import VulnerabilityRegistry
from backend.core.vuln_engine.payload_generator import PayloadGenerator
def __getattr__(name):
"""Lazy import for DynamicVulnerabilityEngine (requires database models)"""
if name == "DynamicVulnerabilityEngine":
from backend.core.vuln_engine.engine import DynamicVulnerabilityEngine
return DynamicVulnerabilityEngine
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
__all__ = ["DynamicVulnerabilityEngine", "VulnerabilityRegistry", "PayloadGenerator"]
File diff suppressed because it is too large Load Diff
+287
View File
@@ -0,0 +1,287 @@
"""
NeuroSploit v3 - Dynamic Vulnerability Engine
The core of NeuroSploit v3: prompt-driven vulnerability testing.
Instead of hardcoded tests, this engine dynamically tests based on
what vulnerabilities are extracted from the user's prompt.
"""
import asyncio
import aiohttp
from typing import List, Dict, Optional, Any
from datetime import datetime
from backend.core.vuln_engine.registry import VulnerabilityRegistry
from backend.core.vuln_engine.payload_generator import PayloadGenerator
from backend.models import Endpoint, Vulnerability, VulnerabilityTest
from backend.schemas.prompt import VulnerabilityTypeExtracted
class TestResult:
"""Result of a vulnerability test"""
def __init__(
self,
vuln_type: str,
is_vulnerable: bool,
confidence: float,
payload: str,
request_data: dict,
response_data: dict,
evidence: Optional[str] = None
):
self.vuln_type = vuln_type
self.is_vulnerable = is_vulnerable
self.confidence = confidence
self.payload = payload
self.request_data = request_data
self.response_data = response_data
self.evidence = evidence
class DynamicVulnerabilityEngine:
"""
Prompt-driven vulnerability testing engine.
Key principles:
1. Tests ONLY what the prompt specifies
2. Generates payloads dynamically based on context
3. Uses multiple detection techniques per vulnerability type
4. Adapts based on target responses
"""
def __init__(self, llm_manager=None):
self.llm_manager = llm_manager
self.registry = VulnerabilityRegistry()
self.payload_generator = PayloadGenerator()
self.session: Optional[aiohttp.ClientSession] = None
self.timeout = aiohttp.ClientTimeout(total=30)
async def __aenter__(self):
self.session = aiohttp.ClientSession(timeout=self.timeout)
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
if self.session:
await self.session.close()
async def test_endpoint(
self,
endpoint: Endpoint,
vuln_types: List[VulnerabilityTypeExtracted],
context: Dict[str, Any],
progress_callback=None
) -> List[TestResult]:
"""
Test an endpoint for specified vulnerability types.
Args:
endpoint: The endpoint to test
vuln_types: List of vulnerability types to test for
context: Additional context (technologies, WAF info, etc.)
progress_callback: Optional callback for progress updates
Returns:
List of test results
"""
results = []
if not self.session:
self.session = aiohttp.ClientSession(timeout=self.timeout)
for vuln in vuln_types:
try:
if progress_callback:
await progress_callback(f"Testing {vuln.type} on {endpoint.url}")
# Get tester for this vulnerability type
tester = self.registry.get_tester(vuln.type)
# Get payloads for this vulnerability and endpoint
payloads = await self.payload_generator.get_payloads(
vuln_type=vuln.type,
endpoint=endpoint,
context=context
)
# Test each payload
for payload in payloads:
result = await self._execute_test(
endpoint=endpoint,
vuln_type=vuln.type,
payload=payload,
tester=tester,
context=context
)
results.append(result)
# If vulnerable, try to get more evidence
if result.is_vulnerable:
deeper_results = await self._deep_test(
endpoint=endpoint,
vuln_type=vuln.type,
initial_result=result,
tester=tester,
context=context
)
results.extend(deeper_results)
break # Found vulnerability, move to next type
except Exception as e:
print(f"Error testing {vuln.type}: {e}")
continue
return results
async def _execute_test(
self,
endpoint: Endpoint,
vuln_type: str,
payload: str,
tester,
context: Dict
) -> TestResult:
"""Execute a single vulnerability test"""
request_data = {
"url": endpoint.url,
"method": endpoint.method,
"payload": payload,
"timestamp": datetime.utcnow().isoformat()
}
try:
# Build the test request
test_url, test_params, test_headers, test_body = tester.build_request(
endpoint=endpoint,
payload=payload
)
# Send the request
async with self.session.request(
method=endpoint.method,
url=test_url,
params=test_params,
headers=test_headers,
data=test_body,
ssl=False,
allow_redirects=False
) as response:
response_text = await response.text()
response_data = {
"status": response.status,
"headers": dict(response.headers),
"body_preview": response_text[:2000] if response_text else "",
"content_length": len(response_text) if response_text else 0
}
# Analyze response for vulnerability
is_vulnerable, confidence, evidence = tester.analyze_response(
payload=payload,
response_status=response.status,
response_headers=dict(response.headers),
response_body=response_text,
context=context
)
return TestResult(
vuln_type=vuln_type,
is_vulnerable=is_vulnerable,
confidence=confidence,
payload=payload,
request_data=request_data,
response_data=response_data,
evidence=evidence
)
except asyncio.TimeoutError:
# Timeout might indicate time-based injection
response_data = {"error": "timeout", "timeout_seconds": self.timeout.total}
is_vulnerable = tester.check_timeout_vulnerability(vuln_type)
return TestResult(
vuln_type=vuln_type,
is_vulnerable=is_vulnerable,
confidence=0.7 if is_vulnerable else 0.0,
payload=payload,
request_data=request_data,
response_data=response_data,
evidence="Request timed out - possible time-based vulnerability" if is_vulnerable else None
)
except Exception as e:
response_data = {"error": str(e)}
return TestResult(
vuln_type=vuln_type,
is_vulnerable=False,
confidence=0.0,
payload=payload,
request_data=request_data,
response_data=response_data,
evidence=None
)
async def _deep_test(
self,
endpoint: Endpoint,
vuln_type: str,
initial_result: TestResult,
tester,
context: Dict
) -> List[TestResult]:
"""
Perform deeper testing after initial vulnerability confirmation.
This helps establish higher confidence and better PoC.
"""
results = []
# Get exploitation payloads
deeper_payloads = await self.payload_generator.get_exploitation_payloads(
vuln_type=vuln_type,
initial_payload=initial_result.payload,
context=context
)
for payload in deeper_payloads[:3]: # Limit to 3 deeper tests
result = await self._execute_test(
endpoint=endpoint,
vuln_type=vuln_type,
payload=payload,
tester=tester,
context=context
)
if result.is_vulnerable:
result.confidence = min(result.confidence + 0.1, 1.0)
results.append(result)
return results
async def create_vulnerability_record(
self,
scan_id: str,
endpoint: Endpoint,
result: TestResult
) -> Vulnerability:
"""Create a vulnerability record from a test result"""
# Get severity based on vulnerability type
severity = self.registry.get_severity(result.vuln_type)
# Get CWE ID
cwe_id = self.registry.get_cwe_id(result.vuln_type)
# Get remediation advice
remediation = self.registry.get_remediation(result.vuln_type)
# Generate title
title = self.registry.get_title(result.vuln_type)
return Vulnerability(
scan_id=scan_id,
title=f"{title} on {endpoint.path or endpoint.url}",
vulnerability_type=result.vuln_type,
severity=severity,
cwe_id=cwe_id,
description=self.registry.get_description(result.vuln_type),
affected_endpoint=endpoint.url,
poc_request=str(result.request_data),
poc_response=str(result.response_data.get("body_preview", ""))[:5000],
poc_payload=result.payload,
impact=self.registry.get_impact(result.vuln_type),
remediation=remediation,
ai_analysis=result.evidence
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+616
View File
@@ -0,0 +1,616 @@
"""
NeuroSploit v3 - Vulnerability Registry
Registry of all vulnerability types and their testers.
Provides metadata, severity info, and tester classes.
"""
from typing import Dict, Optional, Tuple
from backend.core.vuln_engine.testers.base_tester import BaseTester
from backend.core.vuln_engine.testers.injection import (
XSSReflectedTester, XSSStoredTester, XSSDomTester,
SQLiErrorTester, SQLiUnionTester, SQLiBlindTester, SQLiTimeTester,
CommandInjectionTester, SSTITester, NoSQLInjectionTester
)
from backend.core.vuln_engine.testers.advanced_injection import (
LdapInjectionTester, XpathInjectionTester, GraphqlInjectionTester,
CrlfInjectionTester, HeaderInjectionTester, EmailInjectionTester,
ELInjectionTester, LogInjectionTester, HtmlInjectionTester,
CsvInjectionTester, OrmInjectionTester
)
from backend.core.vuln_engine.testers.file_access import (
LFITester, RFITester, PathTraversalTester, XXETester, FileUploadTester,
ArbitraryFileReadTester, ArbitraryFileDeleteTester, ZipSlipTester
)
from backend.core.vuln_engine.testers.request_forgery import (
SSRFTester, CSRFTester, GraphqlIntrospectionTester, GraphqlDosTester
)
from backend.core.vuln_engine.testers.auth import (
AuthBypassTester, JWTManipulationTester, SessionFixationTester,
WeakPasswordTester, DefaultCredentialsTester, TwoFactorBypassTester,
OauthMisconfigTester
)
from backend.core.vuln_engine.testers.authorization import (
IDORTester, BOLATester, PrivilegeEscalationTester,
BflaTester, MassAssignmentTester, ForcedBrowsingTester
)
from backend.core.vuln_engine.testers.client_side import (
CORSTester, ClickjackingTester, OpenRedirectTester,
DomClobberingTester, PostMessageVulnTester, WebsocketHijackTester,
PrototypePollutionTester, CssInjectionTester, TabnabbingTester
)
from backend.core.vuln_engine.testers.infrastructure import (
SecurityHeadersTester, SSLTester, HTTPMethodsTester,
DirectoryListingTester, DebugModeTester, ExposedAdminPanelTester,
ExposedApiDocsTester, InsecureCookieFlagsTester
)
from backend.core.vuln_engine.testers.logic import (
RaceConditionTester, BusinessLogicTester, RateLimitBypassTester,
ParameterPollutionTester, TypeJugglingTester, TimingAttackTester,
HostHeaderInjectionTester, HttpSmugglingTester, CachePoisoningTester
)
from backend.core.vuln_engine.testers.data_exposure import (
SensitiveDataExposureTester, InformationDisclosureTester,
ApiKeyExposureTester, SourceCodeDisclosureTester,
BackupFileExposureTester, VersionDisclosureTester
)
from backend.core.vuln_engine.testers.cloud_supply import (
S3BucketMisconfigTester, CloudMetadataExposureTester,
SubdomainTakeoverTester, VulnerableDependencyTester,
ContainerEscapeTester, ServerlessMisconfigTester
)
class VulnerabilityRegistry:
"""
Central registry for all vulnerability types.
Maps vulnerability types to:
- Tester classes
- Severity levels
- CWE IDs
- Descriptions
- Remediation advice
"""
# Vulnerability metadata
VULNERABILITY_INFO = {
# XSS
"xss_reflected": {
"title": "Reflected Cross-Site Scripting (XSS)",
"severity": "medium",
"cwe_id": "CWE-79",
"description": "Reflected XSS occurs when user input is immediately returned by a web application in an error message, search result, or any other response that includes some or all of the input provided by the user as part of the request, without that data being made safe to render in the browser.",
"impact": "An attacker can execute arbitrary JavaScript in the victim's browser, potentially stealing session cookies, capturing credentials, or performing actions on behalf of the user.",
"remediation": "1. Encode all user input when rendering in HTML context\n2. Use Content-Security-Policy headers\n3. Set HttpOnly flag on sensitive cookies\n4. Use modern frameworks with auto-escaping"
},
"xss_stored": {
"title": "Stored Cross-Site Scripting (XSS)",
"severity": "high",
"cwe_id": "CWE-79",
"description": "Stored XSS occurs when malicious script is permanently stored on the target server, such as in a database, message forum, visitor log, or comment field.",
"impact": "All users who view the affected page will execute the malicious script, leading to mass credential theft, session hijacking, or malware distribution.",
"remediation": "1. Sanitize and validate all user input before storage\n2. Encode output when rendering\n3. Implement Content-Security-Policy\n4. Use HttpOnly and Secure flags on cookies"
},
"xss_dom": {
"title": "DOM-based Cross-Site Scripting",
"severity": "medium",
"cwe_id": "CWE-79",
"description": "DOM-based XSS occurs when client-side JavaScript processes user input and writes it to the DOM in an unsafe way.",
"impact": "Attacker can execute JavaScript in the user's browser through malicious links or user interaction.",
"remediation": "1. Avoid using dangerous DOM sinks (innerHTML, eval, document.write)\n2. Use textContent instead of innerHTML\n3. Sanitize user input on the client side\n4. Implement CSP with strict policies"
},
# SQL Injection
"sqli_error": {
"title": "Error-based SQL Injection",
"severity": "critical",
"cwe_id": "CWE-89",
"description": "SQL injection vulnerability that reveals database errors containing query information, allowing attackers to extract data through error messages.",
"impact": "Complete database compromise including data theft, modification, or deletion. May lead to remote code execution on the database server.",
"remediation": "1. Use parameterized queries/prepared statements\n2. Implement input validation with whitelist approach\n3. Apply least privilege principle for database accounts\n4. Disable detailed error messages in production"
},
"sqli_union": {
"title": "Union-based SQL Injection",
"severity": "critical",
"cwe_id": "CWE-89",
"description": "SQL injection allowing UNION-based queries to extract data from other database tables.",
"impact": "Full database extraction capability. Attacker can read all database tables, users, and potentially escalate to RCE.",
"remediation": "1. Use parameterized queries exclusively\n2. Implement strict input validation\n3. Use stored procedures where appropriate\n4. Monitor for unusual query patterns"
},
"sqli_blind": {
"title": "Blind SQL Injection (Boolean-based)",
"severity": "high",
"cwe_id": "CWE-89",
"description": "SQL injection where results are inferred from application behavior changes rather than direct output.",
"impact": "Slower but complete data extraction is possible. Can lead to full database compromise.",
"remediation": "1. Use parameterized queries\n2. Implement WAF rules for SQL injection patterns\n3. Use connection pooling with timeout limits\n4. Implement query logging and monitoring"
},
"sqli_time": {
"title": "Time-based Blind SQL Injection",
"severity": "high",
"cwe_id": "CWE-89",
"description": "SQL injection where attacker can infer information based on time delays in responses.",
"impact": "Complete data extraction possible, though slower. Can determine database structure and content.",
"remediation": "1. Use parameterized queries\n2. Set strict query timeout limits\n3. Monitor for anomalously slow queries\n4. Implement rate limiting"
},
# Command Injection
"command_injection": {
"title": "OS Command Injection",
"severity": "critical",
"cwe_id": "CWE-78",
"description": "Application passes unsafe user-supplied data to a system shell, allowing execution of arbitrary OS commands.",
"impact": "Complete system compromise. Attacker can execute any command with the application's privileges, potentially gaining full server access.",
"remediation": "1. Avoid shell commands; use native library functions\n2. If shell required, use strict whitelist validation\n3. Never pass user input directly to shell\n4. Run with minimal privileges, use containers"
},
# SSTI
"ssti": {
"title": "Server-Side Template Injection",
"severity": "critical",
"cwe_id": "CWE-94",
"description": "User input is unsafely embedded into server-side templates, allowing template code execution.",
"impact": "Often leads to remote code execution. Attacker can read files, execute commands, and compromise the server.",
"remediation": "1. Never pass user input to template engines\n2. Use logic-less templates when possible\n3. Implement sandbox environments for templates\n4. Validate and sanitize all template inputs"
},
# NoSQL Injection
"nosql_injection": {
"title": "NoSQL Injection",
"severity": "high",
"cwe_id": "CWE-943",
"description": "Injection attack targeting NoSQL databases like MongoDB through operator injection.",
"impact": "Authentication bypass, data theft, and potential server compromise depending on database configuration.",
"remediation": "1. Validate and sanitize all user input\n2. Use parameterized queries where available\n3. Disable server-side JavaScript execution\n4. Apply strict typing to query parameters"
},
# File Access
"lfi": {
"title": "Local File Inclusion",
"severity": "high",
"cwe_id": "CWE-98",
"description": "Application includes local files based on user input, allowing access to sensitive files.",
"impact": "Read sensitive configuration files, source code, and potentially achieve code execution via log poisoning.",
"remediation": "1. Avoid dynamic file inclusion\n2. Use whitelist of allowed files\n3. Validate and sanitize file paths\n4. Implement proper access controls"
},
"rfi": {
"title": "Remote File Inclusion",
"severity": "critical",
"cwe_id": "CWE-98",
"description": "Application includes remote files, allowing execution of attacker-controlled code.",
"impact": "Direct remote code execution. Complete server compromise.",
"remediation": "1. Disable allow_url_include in PHP\n2. Use whitelists for file inclusion\n3. Never use user input in include paths\n4. Implement strict input validation"
},
"path_traversal": {
"title": "Path Traversal",
"severity": "high",
"cwe_id": "CWE-22",
"description": "Application allows navigation outside intended directory through ../ sequences.",
"impact": "Access to sensitive files outside web root, including configuration files and source code.",
"remediation": "1. Validate and sanitize file paths\n2. Use basename() to strip directory components\n3. Implement chroot or containerization\n4. Use whitelist of allowed directories"
},
"xxe": {
"title": "XML External Entity Injection",
"severity": "high",
"cwe_id": "CWE-611",
"description": "XML parser processes external entity references, allowing file access or SSRF.",
"impact": "Read local files, perform SSRF attacks, and potentially achieve denial of service.",
"remediation": "1. Disable external entity processing\n2. Use JSON instead of XML where possible\n3. Validate and sanitize XML input\n4. Use updated XML parsers with secure defaults"
},
"file_upload": {
"title": "Arbitrary File Upload",
"severity": "high",
"cwe_id": "CWE-434",
"description": "Application allows uploading of dangerous file types that can be executed.",
"impact": "Upload of web shells leading to remote code execution and complete server compromise.",
"remediation": "1. Validate file type using magic bytes\n2. Rename uploaded files\n3. Store outside web root\n4. Disable execution in upload directory"
},
# Request Forgery
"ssrf": {
"title": "Server-Side Request Forgery",
"severity": "high",
"cwe_id": "CWE-918",
"description": "Application makes requests to attacker-specified URLs, accessing internal resources.",
"impact": "Access to internal services, cloud metadata, and potential for pivoting to internal networks.",
"remediation": "1. Implement URL whitelist\n2. Block requests to internal IPs\n3. Disable unnecessary URL schemes\n4. Use network segmentation"
},
"ssrf_cloud": {
"title": "SSRF to Cloud Metadata",
"severity": "critical",
"cwe_id": "CWE-918",
"description": "SSRF vulnerability allowing access to cloud provider metadata services.",
"impact": "Credential theft, full cloud account compromise, lateral movement in cloud infrastructure.",
"remediation": "1. Block requests to metadata IPs\n2. Use IMDSv2 (AWS) or equivalent\n3. Implement strict URL validation\n4. Use firewall rules for metadata endpoints"
},
"csrf": {
"title": "Cross-Site Request Forgery",
"severity": "medium",
"cwe_id": "CWE-352",
"description": "Application allows state-changing requests without proper origin validation.",
"impact": "Attacker can perform actions as authenticated users, including transfers, password changes, or data modification.",
"remediation": "1. Implement anti-CSRF tokens\n2. Verify Origin/Referer headers\n3. Use SameSite cookie attribute\n4. Require re-authentication for sensitive actions"
},
# Authentication
"auth_bypass": {
"title": "Authentication Bypass",
"severity": "critical",
"cwe_id": "CWE-287",
"description": "Authentication mechanisms can be bypassed through various techniques.",
"impact": "Complete unauthorized access to user accounts and protected resources.",
"remediation": "1. Implement proper authentication checks on all routes\n2. Use proven authentication frameworks\n3. Implement account lockout\n4. Use MFA for sensitive accounts"
},
"jwt_manipulation": {
"title": "JWT Token Manipulation",
"severity": "high",
"cwe_id": "CWE-347",
"description": "JWT implementation vulnerabilities allowing token forgery or manipulation.",
"impact": "Authentication bypass, privilege escalation, and identity impersonation.",
"remediation": "1. Always verify JWT signatures\n2. Use strong signing algorithms (RS256)\n3. Validate all claims including exp and iss\n4. Implement token refresh mechanisms"
},
"session_fixation": {
"title": "Session Fixation",
"severity": "medium",
"cwe_id": "CWE-384",
"description": "Application accepts session tokens from URL parameters or doesn't regenerate after login.",
"impact": "Attacker can hijack user sessions by fixing known session IDs.",
"remediation": "1. Regenerate session ID after login\n2. Only accept session from cookies\n3. Implement secure session management\n4. Use short session timeouts"
},
# Authorization
"idor": {
"title": "Insecure Direct Object Reference",
"severity": "high",
"cwe_id": "CWE-639",
"description": "Application exposes internal object IDs without proper authorization checks.",
"impact": "Unauthorized access to other users' data, potentially exposing sensitive information.",
"remediation": "1. Implement proper authorization checks\n2. Use indirect references or UUIDs\n3. Validate user ownership of resources\n4. Implement access control lists"
},
"bola": {
"title": "Broken Object Level Authorization",
"severity": "high",
"cwe_id": "CWE-639",
"description": "API endpoints don't properly validate object-level permissions.",
"impact": "Access to any object by manipulating IDs, leading to mass data exposure.",
"remediation": "1. Implement object-level authorization\n2. Validate permissions on every request\n3. Use authorization middleware\n4. Log and monitor access patterns"
},
"privilege_escalation": {
"title": "Privilege Escalation",
"severity": "critical",
"cwe_id": "CWE-269",
"description": "User can elevate privileges to access higher-level functionality.",
"impact": "User can gain admin access, access to all data, and full system control.",
"remediation": "1. Implement role-based access control\n2. Validate roles on every request\n3. Use principle of least privilege\n4. Monitor for privilege escalation attempts"
},
# Client-side
"cors_misconfig": {
"title": "CORS Misconfiguration",
"severity": "medium",
"cwe_id": "CWE-942",
"description": "Overly permissive CORS policy allows cross-origin requests from untrusted domains.",
"impact": "Cross-origin data theft and unauthorized API access from malicious websites.",
"remediation": "1. Implement strict origin whitelist\n2. Avoid Access-Control-Allow-Origin: *\n3. Validate Origin header server-side\n4. Don't reflect Origin without validation"
},
"clickjacking": {
"title": "Clickjacking",
"severity": "medium",
"cwe_id": "CWE-1021",
"description": "Application can be framed by malicious pages, tricking users into clicking hidden elements.",
"impact": "Users can be tricked into performing unintended actions like transfers or permission grants.",
"remediation": "1. Set X-Frame-Options: DENY\n2. Implement frame-ancestors CSP directive\n3. Use JavaScript frame-busting as backup\n4. Require confirmation for sensitive actions"
},
"open_redirect": {
"title": "Open Redirect",
"severity": "low",
"cwe_id": "CWE-601",
"description": "Application redirects to user-specified URLs without validation.",
"impact": "Phishing attacks using trusted domain, credential theft, and reputation damage.",
"remediation": "1. Use whitelist for redirect destinations\n2. Validate redirect URLs server-side\n3. Don't use user input directly in redirects\n4. Warn users before redirecting externally"
},
# Infrastructure
"security_headers": {
"title": "Missing Security Headers",
"severity": "low",
"cwe_id": "CWE-693",
"description": "Application doesn't set important security headers like CSP, HSTS, X-Frame-Options.",
"impact": "Increased risk of XSS, clickjacking, and MITM attacks.",
"remediation": "1. Implement Content-Security-Policy\n2. Enable Strict-Transport-Security\n3. Set X-Frame-Options and X-Content-Type-Options\n4. Configure Referrer-Policy"
},
"ssl_issues": {
"title": "SSL/TLS Configuration Issues",
"severity": "medium",
"cwe_id": "CWE-326",
"description": "Weak SSL/TLS configuration including outdated protocols or weak ciphers.",
"impact": "Traffic interception, credential theft, and man-in-the-middle attacks.",
"remediation": "1. Disable SSLv3, TLS 1.0, TLS 1.1\n2. Use strong cipher suites only\n3. Enable HSTS with preload\n4. Implement certificate pinning for mobile apps"
},
"http_methods": {
"title": "Dangerous HTTP Methods Enabled",
"severity": "low",
"cwe_id": "CWE-749",
"description": "Server allows potentially dangerous HTTP methods like TRACE, PUT, DELETE without proper restrictions.",
"impact": "Potential for XST attacks, unauthorized file uploads, or resource manipulation.",
"remediation": "1. Disable unnecessary HTTP methods\n2. Configure web server to reject TRACE/TRACK\n3. Implement proper authorization for PUT/DELETE\n4. Use web application firewall"
},
# Logic
"race_condition": {
"title": "Race Condition",
"severity": "medium",
"cwe_id": "CWE-362",
"description": "Application has race conditions that can be exploited through concurrent requests.",
"impact": "Double-spending, bypassing limits, or corrupting data through timing attacks.",
"remediation": "1. Implement proper locking mechanisms\n2. Use atomic database operations\n3. Implement idempotency keys\n4. Add proper synchronization"
},
"business_logic": {
"title": "Business Logic Vulnerability",
"severity": "varies",
"cwe_id": "CWE-840",
"description": "Flaw in application's business logic allowing unintended behavior.",
"impact": "Varies based on specific flaw - could range from minor to critical impact.",
"remediation": "1. Review business logic flows\n2. Implement comprehensive validation\n3. Add server-side checks for all rules\n4. Test edge cases and negative scenarios"
},
# ===== NEW TYPES (68 additional) =====
# Advanced Injection
"ldap_injection": {"title": "LDAP Injection", "severity": "high", "cwe_id": "CWE-90", "description": "User input injected into LDAP queries allowing directory enumeration or auth bypass.", "impact": "Directory enumeration, authentication bypass, data extraction from LDAP stores.", "remediation": "1. Escape LDAP special characters\n2. Use parameterized LDAP queries\n3. Validate input against whitelist\n4. Apply least privilege to LDAP accounts"},
"xpath_injection": {"title": "XPath Injection", "severity": "high", "cwe_id": "CWE-643", "description": "User input injected into XPath queries manipulating XML data retrieval.", "impact": "Extraction of XML data, authentication bypass via XPath condition manipulation.", "remediation": "1. Use parameterized XPath queries\n2. Validate and sanitize input\n3. Avoid string concatenation in XPath\n4. Limit XPath query privileges"},
"graphql_injection": {"title": "GraphQL Injection", "severity": "high", "cwe_id": "CWE-89", "description": "Injection attacks targeting GraphQL endpoints through malicious queries or variables.", "impact": "Schema exposure, unauthorized data access, denial of service via complex queries.", "remediation": "1. Disable introspection in production\n2. Implement query depth/complexity limits\n3. Use persisted queries\n4. Apply field-level authorization"},
"crlf_injection": {"title": "CRLF Injection / HTTP Response Splitting", "severity": "medium", "cwe_id": "CWE-93", "description": "Injection of CRLF characters to manipulate HTTP response headers or split responses.", "impact": "HTTP header injection, session fixation via Set-Cookie, XSS via response splitting.", "remediation": "1. Strip \\r\\n from user input in headers\n2. Use framework header-setting functions\n3. Validate header values\n4. Implement WAF rules for CRLF patterns"},
"header_injection": {"title": "HTTP Header Injection", "severity": "medium", "cwe_id": "CWE-113", "description": "User input reflected in HTTP headers enabling header manipulation.", "impact": "Password reset poisoning, cache poisoning, access control bypass via header manipulation.", "remediation": "1. Validate Host header against whitelist\n2. Don't use Host header for URL generation\n3. Strip CRLF from header values\n4. Use absolute URLs for sensitive operations"},
"email_injection": {"title": "Email Header Injection", "severity": "medium", "cwe_id": "CWE-93", "description": "Injection of email headers through form fields that feed into mail functions.", "impact": "Spam relay, phishing via injected CC/BCC recipients, email content manipulation.", "remediation": "1. Validate email addresses strictly\n2. Strip CRLF from email inputs\n3. Use email library APIs not raw headers\n4. Implement rate limiting on email features"},
"expression_language_injection": {"title": "Expression Language Injection", "severity": "critical", "cwe_id": "CWE-917", "description": "Injection of EL/SpEL/OGNL expressions evaluated server-side in Java applications.", "impact": "Remote code execution, server compromise, data exfiltration via expression evaluation.", "remediation": "1. Disable EL evaluation on user input\n2. Use strict sandboxing\n3. Update frameworks (Struts2 OGNL patches)\n4. Validate input before template rendering"},
"log_injection": {"title": "Log Injection / Log4Shell", "severity": "high", "cwe_id": "CWE-117", "description": "Injection into application logs enabling log forging or JNDI-based RCE (Log4Shell).", "impact": "Log tampering, JNDI-based RCE (Log4Shell), log analysis tool exploitation.", "remediation": "1. Strip newlines from log input\n2. Update Log4j to 2.17+ (CVE-2021-44228)\n3. Disable JNDI lookups\n4. Use structured logging"},
"html_injection": {"title": "HTML Injection", "severity": "medium", "cwe_id": "CWE-79", "description": "Injection of HTML markup into web pages without script execution.", "impact": "Content spoofing, phishing form injection, defacement, link manipulation.", "remediation": "1. HTML-encode all user output\n2. Use Content-Security-Policy\n3. Implement output encoding libraries\n4. Sanitize HTML with whitelist approach"},
"csv_injection": {"title": "CSV/Formula Injection", "severity": "medium", "cwe_id": "CWE-1236", "description": "Injection of spreadsheet formulas into data exported as CSV/Excel.", "impact": "Code execution when CSV opened in Excel, DDE attacks, data exfiltration via formulas.", "remediation": "1. Prefix cells starting with =,+,-,@ with single quote\n2. Sanitize formula characters\n3. Use safe CSV export libraries\n4. Warn users about untrusted CSV files"},
"orm_injection": {"title": "ORM Injection", "severity": "high", "cwe_id": "CWE-89", "description": "Injection through ORM query builders via operator injection or raw query manipulation.", "impact": "Data extraction, authentication bypass through ORM filter manipulation.", "remediation": "1. Use ORM built-in parameter binding\n2. Avoid raw queries with user input\n3. Validate filter operators\n4. Use field-level whitelists"},
# XSS Advanced
"blind_xss": {"title": "Blind Cross-Site Scripting", "severity": "high", "cwe_id": "CWE-79", "description": "XSS payload stored and executed in backend/admin context not visible to the attacker.", "impact": "Admin session hijacking, backend system compromise, persistent access to admin panels.", "remediation": "1. Sanitize all input regardless of display context\n2. Implement CSP on admin panels\n3. Use HttpOnly cookies\n4. Review admin panel input rendering"},
"mutation_xss": {"title": "Mutation XSS (mXSS)", "severity": "high", "cwe_id": "CWE-79", "description": "XSS via browser HTML mutation where sanitized HTML changes to executable form after DOM processing.", "impact": "Bypasses HTML sanitizers, executes JavaScript through browser parsing quirks.", "remediation": "1. Update DOMPurify/sanitizers\n2. Use textContent not innerHTML\n3. Avoid innerHTML re-serialization\n4. Test with multiple browsers"},
# File Access Advanced
"arbitrary_file_read": {"title": "Arbitrary File Read", "severity": "high", "cwe_id": "CWE-22", "description": "Reading arbitrary files via API or download endpoints outside intended scope.", "impact": "Access to credentials, configuration, source code, private keys.", "remediation": "1. Validate file paths against whitelist\n2. Use chroot/jail\n3. Implement proper access controls\n4. Avoid user input in file paths"},
"arbitrary_file_delete": {"title": "Arbitrary File Delete", "severity": "high", "cwe_id": "CWE-22", "description": "Deleting arbitrary files through path traversal in delete operations.", "impact": "Denial of service, security bypass by deleting .htaccess/config, data destruction.", "remediation": "1. Validate file paths strictly\n2. Use indirect references\n3. Implement soft-delete\n4. Restrict delete operations to specific directories"},
"zip_slip": {"title": "Zip Slip (Archive Path Traversal)", "severity": "high", "cwe_id": "CWE-22", "description": "Path traversal via crafted archive filenames writing files outside extraction directory.", "impact": "Arbitrary file write, web shell deployment, configuration overwrite.", "remediation": "1. Validate archive entry names\n2. Resolve and check extraction paths\n3. Use secure archive extraction libraries\n4. Extract to isolated directories"},
# Auth Advanced
"weak_password": {"title": "Weak Password Policy", "severity": "medium", "cwe_id": "CWE-521", "description": "Application accepts weak passwords that can be easily guessed or brute-forced.", "impact": "Account compromise through password guessing, credential stuffing success.", "remediation": "1. Enforce minimum 8+ character passwords\n2. Check against breached password databases\n3. Implement password strength meter\n4. Follow NIST SP 800-63B guidelines"},
"default_credentials": {"title": "Default Credentials", "severity": "critical", "cwe_id": "CWE-798", "description": "Application or service uses default factory credentials that haven't been changed.", "impact": "Complete unauthorized access to admin or management interfaces.", "remediation": "1. Force password change on first login\n2. Remove default accounts\n3. Implement strong default password generation\n4. Regular credential audits"},
"brute_force": {"title": "Brute Force Vulnerability", "severity": "medium", "cwe_id": "CWE-307", "description": "Login endpoint lacks rate limiting or account lockout allowing unlimited password attempts.", "impact": "Account compromise through automated password guessing.", "remediation": "1. Implement account lockout after N failures\n2. Add rate limiting per IP and per account\n3. Implement CAPTCHA after failures\n4. Use progressive delays"},
"two_factor_bypass": {"title": "Two-Factor Authentication Bypass", "severity": "high", "cwe_id": "CWE-287", "description": "Second authentication factor can be bypassed through implementation flaws.", "impact": "Account takeover even when 2FA is enabled, defeating the purpose of MFA.", "remediation": "1. Enforce 2FA check on all authenticated routes\n2. Use server-side session state for 2FA completion\n3. Rate limit code attempts\n4. Make codes single-use with short expiry"},
"oauth_misconfiguration": {"title": "OAuth Misconfiguration", "severity": "high", "cwe_id": "CWE-601", "description": "OAuth implementation flaws allowing redirect URI manipulation, state bypass, or token theft.", "impact": "Account takeover via stolen OAuth tokens, cross-site request forgery.", "remediation": "1. Strictly validate redirect_uri\n2. Require and validate state parameter\n3. Use PKCE for public clients\n4. Validate all OAuth scopes"},
# Authorization Advanced
"bfla": {"title": "Broken Function Level Authorization", "severity": "high", "cwe_id": "CWE-285", "description": "Admin API functions accessible to regular users without proper role checks.", "impact": "Privilege escalation to admin functionality, system configuration changes.", "remediation": "1. Implement role-based access control on all endpoints\n2. Deny by default\n3. Centralize authorization logic\n4. Audit all admin endpoints"},
"mass_assignment": {"title": "Mass Assignment", "severity": "high", "cwe_id": "CWE-915", "description": "Application binds user-supplied data to internal model fields without filtering.", "impact": "Privilege escalation, data manipulation, bypassing business rules.", "remediation": "1. Use explicit field whitelists\n2. Implement DTOs for input\n3. Validate all bound fields\n4. Use strong parameter filtering"},
"forced_browsing": {"title": "Forced Browsing / Broken Access Control", "severity": "medium", "cwe_id": "CWE-425", "description": "Direct URL access to restricted resources that should require authorization.", "impact": "Access to admin panels, sensitive files, debug interfaces, and internal tools.", "remediation": "1. Implement authentication on all protected routes\n2. Return 404 instead of 403 for sensitive paths\n3. Remove unnecessary files\n4. Use web server access controls"},
# Client-Side Advanced
"dom_clobbering": {"title": "DOM Clobbering", "severity": "medium", "cwe_id": "CWE-79", "description": "HTML injection that overrides JavaScript DOM properties through named elements.", "impact": "JavaScript logic bypass, potential XSS through clobbered variables.", "remediation": "1. Use strict variable declarations (const/let)\n2. Avoid global variable references\n3. Use safe DOM APIs\n4. Sanitize HTML input"},
"postmessage_vulnerability": {"title": "postMessage Vulnerability", "severity": "medium", "cwe_id": "CWE-346", "description": "postMessage handlers that don't validate message origin allowing cross-origin data injection.", "impact": "Cross-origin data injection, XSS via injected data, sensitive data exfiltration.", "remediation": "1. Always validate event.origin\n2. Validate message data structure\n3. Use specific target origins\n4. Minimize data sent via postMessage"},
"websocket_hijacking": {"title": "Cross-Site WebSocket Hijacking", "severity": "high", "cwe_id": "CWE-1385", "description": "WebSocket endpoints accepting connections from arbitrary origins without validation.", "impact": "Real-time data theft, message injection, session hijacking via WebSocket.", "remediation": "1. Validate Origin header on WebSocket upgrade\n2. Require authentication per-message\n3. Implement CSRF protection for handshake\n4. Use WSS (encrypted)"},
"prototype_pollution": {"title": "Prototype Pollution", "severity": "high", "cwe_id": "CWE-1321", "description": "Injection of properties into JavaScript Object.prototype through merge/extend operations.", "impact": "Authentication bypass, RCE via gadget chains, denial of service.", "remediation": "1. Freeze Object.prototype\n2. Sanitize __proto__ and constructor keys\n3. Use Map instead of plain objects\n4. Update vulnerable libraries"},
"css_injection": {"title": "CSS Injection", "severity": "medium", "cwe_id": "CWE-79", "description": "Injection of CSS code through user input reflected in style contexts.", "impact": "Data exfiltration via CSS selectors, UI manipulation, phishing.", "remediation": "1. Sanitize CSS properties\n2. Use CSP style-src\n3. Avoid user input in style attributes\n4. Whitelist safe CSS properties"},
"tabnabbing": {"title": "Reverse Tabnabbing", "severity": "low", "cwe_id": "CWE-1022", "description": "Links with target=_blank without rel=noopener allowing opener tab navigation.", "impact": "Phishing via original tab replacement with fake login page.", "remediation": "1. Add rel='noopener noreferrer' to target=_blank links\n2. Use frameworks that add it automatically\n3. Audit user-generated links"},
# Infrastructure Advanced
"directory_listing": {"title": "Directory Listing Enabled", "severity": "low", "cwe_id": "CWE-548", "description": "Web server auto-indexing enabled exposing directory file structure.", "impact": "Exposure of file structure, sensitive files, backup files, and configuration.", "remediation": "1. Disable directory listing (Options -Indexes)\n2. Add index files to all directories\n3. Review web server configuration\n4. Use custom error pages"},
"debug_mode": {"title": "Debug Mode Enabled", "severity": "high", "cwe_id": "CWE-489", "description": "Application running in debug/development mode in production.", "impact": "Source code exposure, interactive console access, credential disclosure.", "remediation": "1. Disable debug mode in production\n2. Use environment-specific configuration\n3. Implement custom error pages\n4. Remove debug endpoints"},
"exposed_admin_panel": {"title": "Exposed Administration Panel", "severity": "medium", "cwe_id": "CWE-200", "description": "Admin panel accessible from public internet without IP restrictions.", "impact": "Brute force target, credential theft, administration access if default creds.", "remediation": "1. Restrict admin access by IP/VPN\n2. Use strong authentication + 2FA\n3. Change default admin paths\n4. Implement rate limiting"},
"exposed_api_docs": {"title": "Exposed API Documentation", "severity": "low", "cwe_id": "CWE-200", "description": "API documentation (Swagger/OpenAPI/GraphQL playground) publicly accessible.", "impact": "Complete API endpoint mapping, parameter discovery, potential unauthorized access.", "remediation": "1. Disable API docs in production\n2. Require authentication for docs\n3. Disable GraphQL introspection\n4. Use API gateway access controls"},
"insecure_cookie_flags": {"title": "Insecure Cookie Configuration", "severity": "medium", "cwe_id": "CWE-614", "description": "Session cookies missing security flags (Secure, HttpOnly, SameSite).", "impact": "Cookie theft via XSS (no HttpOnly), MITM (no Secure), CSRF (no SameSite).", "remediation": "1. Set HttpOnly on session cookies\n2. Set Secure flag on HTTPS sites\n3. Set SameSite=Lax or Strict\n4. Review all cookie configurations"},
"http_smuggling": {"title": "HTTP Request Smuggling", "severity": "high", "cwe_id": "CWE-444", "description": "Discrepancy between front-end and back-end HTTP parsing enabling request smuggling.", "impact": "Cache poisoning, request hijacking, authentication bypass, response queue poisoning.", "remediation": "1. Use HTTP/2 end-to-end\n2. Normalize Content-Length/Transfer-Encoding\n3. Reject ambiguous requests\n4. Update proxy/server software"},
"cache_poisoning": {"title": "Web Cache Poisoning", "severity": "high", "cwe_id": "CWE-444", "description": "Manipulation of cached responses via unkeyed inputs to serve malicious content.", "impact": "Mass XSS via cached responses, redirect poisoning, denial of service.", "remediation": "1. Include all inputs in cache key\n2. Validate unkeyed headers\n3. Use Vary header correctly\n4. Implement cache key normalization"},
# Logic & Data
"rate_limit_bypass": {"title": "Rate Limit Bypass", "severity": "medium", "cwe_id": "CWE-770", "description": "Rate limiting can be bypassed through header manipulation or request variation.", "impact": "Enables brute force attacks, API abuse, and denial of service.", "remediation": "1. Rate limit by authenticated user, not just IP\n2. Don't trust X-Forwarded-For for rate limiting\n3. Implement at multiple layers\n4. Use sliding window algorithms"},
"parameter_pollution": {"title": "HTTP Parameter Pollution", "severity": "medium", "cwe_id": "CWE-235", "description": "Duplicate parameters exploit parsing differences between front-end and back-end.", "impact": "WAF bypass, logic bypass, access control circumvention.", "remediation": "1. Normalize parameters server-side\n2. Reject duplicate parameters\n3. Use consistent parsing\n4. Test with duplicate params"},
"type_juggling": {"title": "Type Juggling / Type Coercion", "severity": "high", "cwe_id": "CWE-843", "description": "Loose type comparison exploited to bypass authentication or security checks.", "impact": "Authentication bypass, security check circumvention via type confusion.", "remediation": "1. Use strict comparison (=== in PHP/JS)\n2. Validate input types\n3. Use strong typing\n4. Hash comparison with timing-safe functions"},
"insecure_deserialization": {"title": "Insecure Deserialization", "severity": "critical", "cwe_id": "CWE-502", "description": "Untrusted data deserialized without validation enabling code execution.", "impact": "Remote code execution, denial of service, authentication bypass.", "remediation": "1. Don't deserialize untrusted data\n2. Use JSON instead of native serialization\n3. Implement integrity checks\n4. Restrict deserialization types"},
"subdomain_takeover": {"title": "Subdomain Takeover", "severity": "high", "cwe_id": "CWE-284", "description": "Dangling DNS records pointing to unclaimed cloud resources.", "impact": "Domain impersonation, phishing, cookie theft, authentication bypass.", "remediation": "1. Audit DNS records regularly\n2. Remove dangling CNAME records\n3. Monitor cloud resource lifecycle\n4. Use DNS monitoring tools"},
"host_header_injection": {"title": "Host Header Injection", "severity": "medium", "cwe_id": "CWE-644", "description": "Host header value used in URL generation enabling poisoning attacks.", "impact": "Password reset poisoning, cache poisoning, SSRF via Host header.", "remediation": "1. Validate Host against allowed values\n2. Use absolute URLs from configuration\n3. Don't use Host header for URL generation\n4. Implement ALLOWED_HOSTS"},
"timing_attack": {"title": "Timing Attack", "severity": "medium", "cwe_id": "CWE-208", "description": "Response time variations leak information about valid usernames or secret values.", "impact": "Username enumeration, token/password character extraction.", "remediation": "1. Use constant-time comparison for secrets\n2. Normalize response times\n3. Add random delays\n4. Use same code path for valid/invalid input"},
"improper_error_handling": {"title": "Improper Error Handling", "severity": "low", "cwe_id": "CWE-209", "description": "Verbose error messages disclosing internal information in production.", "impact": "Source path disclosure, database details, technology stack exposure aiding further attacks.", "remediation": "1. Use custom error pages in production\n2. Log errors server-side only\n3. Return generic error messages\n4. Disable debug/stack trace output"},
"sensitive_data_exposure": {"title": "Sensitive Data Exposure", "severity": "high", "cwe_id": "CWE-200", "description": "Sensitive data (PII, credentials, tokens) exposed in responses, URLs, or storage.", "impact": "Identity theft, account compromise, regulatory violations (GDPR, HIPAA).", "remediation": "1. Minimize data in API responses\n2. Encrypt sensitive data at rest/transit\n3. Remove sensitive data from URLs\n4. Implement data classification"},
"information_disclosure": {"title": "Information Disclosure", "severity": "low", "cwe_id": "CWE-200", "description": "Unintended exposure of internal details: versions, paths, technology stack.", "impact": "Aids further attacks with technology-specific exploits and internal knowledge.", "remediation": "1. Remove version headers\n2. Disable directory listing\n3. Remove HTML comments\n4. Secure .git and config files"},
"api_key_exposure": {"title": "API Key Exposure", "severity": "high", "cwe_id": "CWE-798", "description": "API keys or secrets hardcoded in client-side code or public files.", "impact": "Unauthorized API access, financial impact, data breach via exposed keys.", "remediation": "1. Use environment variables for secrets\n2. Implement key rotation\n3. Use backend proxy for API calls\n4. Monitor key usage for anomalies"},
"source_code_disclosure": {"title": "Source Code Disclosure", "severity": "high", "cwe_id": "CWE-540", "description": "Application source code accessible through misconfigured servers, backups, or VCS exposure.", "impact": "White-box attack surface, credential discovery, vulnerability identification.", "remediation": "1. Block .git, .svn access\n2. Remove source maps in production\n3. Delete backup files\n4. Configure web server to block sensitive extensions"},
"backup_file_exposure": {"title": "Backup File Exposure", "severity": "high", "cwe_id": "CWE-530", "description": "Backup files, database dumps, or archives accessible from web server.", "impact": "Full source code access, database contents including credentials.", "remediation": "1. Store backups outside web root\n2. Remove old backup files\n3. Block backup extensions in web server\n4. Encrypt backup files"},
"version_disclosure": {"title": "Software Version Disclosure", "severity": "low", "cwe_id": "CWE-200", "description": "Specific software versions exposed enabling targeted CVE exploitation.", "impact": "Targeted exploitation of known vulnerabilities for the specific version.", "remediation": "1. Remove version from headers\n2. Update software regularly\n3. Remove version-disclosing files\n4. Customize error pages"},
# Crypto & Supply
"weak_encryption": {"title": "Weak Encryption Algorithm", "severity": "medium", "cwe_id": "CWE-327", "description": "Use of weak/deprecated encryption algorithms (DES, RC4, ECB mode).", "impact": "Data decryption, MITM attacks, breaking confidentiality protections.", "remediation": "1. Use AES-256-GCM or ChaCha20\n2. Disable weak cipher suites\n3. Use TLS 1.2+ only\n4. Regular cryptographic review"},
"weak_hashing": {"title": "Weak Hashing Algorithm", "severity": "medium", "cwe_id": "CWE-328", "description": "Use of weak hash algorithms (MD5, SHA1) for security-critical purposes.", "impact": "Password cracking, hash collision attacks, integrity bypass.", "remediation": "1. Use bcrypt/scrypt/argon2 for passwords\n2. Use SHA-256+ for integrity\n3. Always use salts\n4. Implement key stretching"},
"weak_random": {"title": "Weak Random Number Generation", "severity": "medium", "cwe_id": "CWE-330", "description": "Predictable random numbers used for security tokens or session IDs.", "impact": "Token prediction, session hijacking, CSRF token bypass.", "remediation": "1. Use cryptographic PRNG (secrets module, SecureRandom)\n2. Avoid Math.random() for security\n3. Use sufficient entropy\n4. Regular token rotation"},
"cleartext_transmission": {"title": "Cleartext Transmission of Sensitive Data", "severity": "medium", "cwe_id": "CWE-319", "description": "Sensitive data transmitted over unencrypted HTTP connections.", "impact": "Credential theft via MITM, session hijacking, data exposure.", "remediation": "1. Enforce HTTPS everywhere\n2. Implement HSTS with preload\n3. Redirect HTTP to HTTPS\n4. Set Secure flag on cookies"},
"vulnerable_dependency": {"title": "Vulnerable Third-Party Dependency", "severity": "varies", "cwe_id": "CWE-1104", "description": "Third-party library with known CVEs in use.", "impact": "Depends on specific CVE - from XSS to RCE.", "remediation": "1. Regular dependency updates\n2. Use automated vulnerability scanning\n3. Monitor CVE advisories\n4. Implement SCA in CI/CD"},
"outdated_component": {"title": "Outdated Software Component", "severity": "medium", "cwe_id": "CWE-1104", "description": "Significantly outdated CMS, framework, or server with multiple known CVEs.", "impact": "Multiple exploitable vulnerabilities, targeted attacks.", "remediation": "1. Update to latest stable version\n2. Enable automatic security updates\n3. Monitor end-of-life announcements\n4. Implement patch management"},
"insecure_cdn": {"title": "Insecure CDN Resource Loading", "severity": "low", "cwe_id": "CWE-829", "description": "External scripts loaded without Subresource Integrity (SRI) hashes.", "impact": "Supply chain attack via CDN compromise, mass XSS.", "remediation": "1. Add integrity= attribute to script/link tags\n2. Use crossorigin attribute\n3. Self-host critical resources\n4. Implement CSP with hash sources"},
"container_escape": {"title": "Container Escape / Misconfiguration", "severity": "critical", "cwe_id": "CWE-250", "description": "Container running with elevated privileges or exposed host resources.", "impact": "Host system compromise, lateral movement, data access across containers.", "remediation": "1. Don't use --privileged\n2. Drop unnecessary capabilities\n3. Don't mount Docker socket\n4. Use seccomp/AppArmor profiles"},
# Cloud & API
"s3_bucket_misconfiguration": {"title": "S3/Cloud Storage Misconfiguration", "severity": "high", "cwe_id": "CWE-284", "description": "Cloud storage bucket with public read/write access.", "impact": "Data exposure, data tampering, hosting malicious content.", "remediation": "1. Enable S3 Block Public Access\n2. Review bucket policies\n3. Use IAM policies for access\n4. Enable access logging"},
"cloud_metadata_exposure": {"title": "Cloud Metadata Exposure", "severity": "critical", "cwe_id": "CWE-918", "description": "Cloud instance metadata service accessible exposing credentials.", "impact": "IAM credential theft, cloud account compromise, lateral movement.", "remediation": "1. Use IMDSv2 (token-required)\n2. Block metadata endpoint in firewall\n3. Implement SSRF protection\n4. Use minimal IAM roles"},
"serverless_misconfiguration": {"title": "Serverless Misconfiguration", "severity": "medium", "cwe_id": "CWE-284", "description": "Serverless function with excessive permissions or missing auth.", "impact": "Unauthorized function execution, environment variable exposure, privilege escalation.", "remediation": "1. Apply least privilege IAM roles\n2. Require authentication\n3. Don't expose secrets in env vars\n4. Implement function authorization"},
"graphql_introspection": {"title": "GraphQL Introspection Enabled", "severity": "low", "cwe_id": "CWE-200", "description": "GraphQL introspection enabled in production exposing full API schema.", "impact": "Complete API mapping, discovery of sensitive types and mutations.", "remediation": "1. Disable introspection in production\n2. Use persisted queries\n3. Implement field-level authorization\n4. Use query allowlisting"},
"graphql_dos": {"title": "GraphQL Denial of Service", "severity": "medium", "cwe_id": "CWE-400", "description": "GraphQL endpoint vulnerable to resource-exhaustion via complex/nested queries.", "impact": "Service unavailability, resource exhaustion, increased infrastructure costs.", "remediation": "1. Implement query depth limits\n2. Add query complexity analysis\n3. Set timeout on queries\n4. Use persisted/allowlisted queries"},
"rest_api_versioning": {"title": "Insecure API Version Exposure", "severity": "low", "cwe_id": "CWE-284", "description": "Older API versions with weaker security controls still accessible.", "impact": "Bypass newer security controls via old API versions.", "remediation": "1. Deprecate and remove old API versions\n2. Apply same security to all versions\n3. Monitor old version usage\n4. Set deprecation timelines"},
"soap_injection": {"title": "SOAP/XML Web Service Injection", "severity": "high", "cwe_id": "CWE-91", "description": "Injection in SOAP/XML web service parameters manipulating queries.", "impact": "Data extraction, XXE via SOAP, SOAP action spoofing for unauthorized operations.", "remediation": "1. Validate SOAP input\n2. Disable XML external entities\n3. Validate SOAPAction header\n4. Use WS-Security"},
"api_rate_limiting": {"title": "Missing API Rate Limiting", "severity": "medium", "cwe_id": "CWE-770", "description": "API endpoints lacking rate limiting allowing unlimited requests.", "impact": "Brute force, scraping, DoS, API abuse at scale.", "remediation": "1. Implement rate limiting per user/IP\n2. Return 429 with Retry-After\n3. Use API gateway throttling\n4. Implement sliding window algorithm"},
"excessive_data_exposure": {"title": "Excessive Data Exposure", "severity": "medium", "cwe_id": "CWE-213", "description": "APIs returning more data than the client needs, including sensitive fields.", "impact": "Exposure of sensitive fields (password hashes, tokens, PII) to clients.", "remediation": "1. Use response DTOs/serializers\n2. Implement field-level filtering\n3. Apply least-data principle\n4. Separate admin and user endpoints"}
}
# Tester class mappings (100 types)
TESTER_CLASSES = {
# Injection (10 original + 11 advanced)
"xss_reflected": XSSReflectedTester,
"xss_stored": XSSStoredTester,
"xss_dom": XSSDomTester,
"sqli_error": SQLiErrorTester,
"sqli_union": SQLiUnionTester,
"sqli_blind": SQLiBlindTester,
"sqli_time": SQLiTimeTester,
"command_injection": CommandInjectionTester,
"ssti": SSTITester,
"nosql_injection": NoSQLInjectionTester,
"ldap_injection": LdapInjectionTester,
"xpath_injection": XpathInjectionTester,
"graphql_injection": GraphqlInjectionTester,
"crlf_injection": CrlfInjectionTester,
"header_injection": HeaderInjectionTester,
"email_injection": EmailInjectionTester,
"expression_language_injection": ELInjectionTester,
"log_injection": LogInjectionTester,
"html_injection": HtmlInjectionTester,
"csv_injection": CsvInjectionTester,
"orm_injection": OrmInjectionTester,
# XSS Advanced
"blind_xss": XSSStoredTester, # Similar detection pattern
"mutation_xss": XSSReflectedTester, # Similar detection pattern
# File Access (5 original + 3 new)
"lfi": LFITester,
"rfi": RFITester,
"path_traversal": PathTraversalTester,
"xxe": XXETester,
"file_upload": FileUploadTester,
"arbitrary_file_read": ArbitraryFileReadTester,
"arbitrary_file_delete": ArbitraryFileDeleteTester,
"zip_slip": ZipSlipTester,
# Request Forgery (3 original + 2 new)
"ssrf": SSRFTester,
"ssrf_cloud": SSRFTester,
"csrf": CSRFTester,
"cors_misconfig": CORSTester,
"graphql_introspection": GraphqlIntrospectionTester,
"graphql_dos": GraphqlDosTester,
# Auth (3 original + 5 new)
"auth_bypass": AuthBypassTester,
"jwt_manipulation": JWTManipulationTester,
"session_fixation": SessionFixationTester,
"weak_password": WeakPasswordTester,
"default_credentials": DefaultCredentialsTester,
"brute_force": AuthBypassTester, # Similar pattern
"two_factor_bypass": TwoFactorBypassTester,
"oauth_misconfiguration": OauthMisconfigTester,
# Authorization (3 original + 3 new)
"idor": IDORTester,
"bola": BOLATester,
"privilege_escalation": PrivilegeEscalationTester,
"bfla": BflaTester,
"mass_assignment": MassAssignmentTester,
"forced_browsing": ForcedBrowsingTester,
# Client-Side (3 original + 6 new)
"clickjacking": ClickjackingTester,
"open_redirect": OpenRedirectTester,
"dom_clobbering": DomClobberingTester,
"postmessage_vulnerability": PostMessageVulnTester,
"websocket_hijacking": WebsocketHijackTester,
"prototype_pollution": PrototypePollutionTester,
"css_injection": CssInjectionTester,
"tabnabbing": TabnabbingTester,
# Infrastructure (3 original + 7 new)
"security_headers": SecurityHeadersTester,
"ssl_issues": SSLTester,
"http_methods": HTTPMethodsTester,
"directory_listing": DirectoryListingTester,
"debug_mode": DebugModeTester,
"exposed_admin_panel": ExposedAdminPanelTester,
"exposed_api_docs": ExposedApiDocsTester,
"insecure_cookie_flags": InsecureCookieFlagsTester,
"http_smuggling": HttpSmugglingTester,
"cache_poisoning": CachePoisoningTester,
# Logic (9 types)
"race_condition": RaceConditionTester,
"business_logic": BusinessLogicTester,
"rate_limit_bypass": RateLimitBypassTester,
"parameter_pollution": ParameterPollutionTester,
"type_juggling": TypeJugglingTester,
"timing_attack": TimingAttackTester,
"host_header_injection": HostHeaderInjectionTester,
"insecure_deserialization": BaseTester, # AI-driven
"subdomain_takeover": SubdomainTakeoverTester,
"improper_error_handling": BaseTester, # AI-driven
# Data Exposure (6 types)
"sensitive_data_exposure": SensitiveDataExposureTester,
"information_disclosure": InformationDisclosureTester,
"api_key_exposure": ApiKeyExposureTester,
"source_code_disclosure": SourceCodeDisclosureTester,
"backup_file_exposure": BackupFileExposureTester,
"version_disclosure": VersionDisclosureTester,
# Crypto & Supply (8 types - mostly inspection/AI-driven)
"weak_encryption": BaseTester,
"weak_hashing": BaseTester,
"weak_random": BaseTester,
"cleartext_transmission": BaseTester,
"vulnerable_dependency": VulnerableDependencyTester,
"outdated_component": VulnerableDependencyTester,
"insecure_cdn": BaseTester,
"container_escape": ContainerEscapeTester,
# Cloud & API (7 types)
"s3_bucket_misconfiguration": S3BucketMisconfigTester,
"cloud_metadata_exposure": CloudMetadataExposureTester,
"serverless_misconfiguration": ServerlessMisconfigTester,
"rest_api_versioning": BaseTester, # AI-driven
"soap_injection": BaseTester, # AI-driven
"api_rate_limiting": RateLimitBypassTester,
"excessive_data_exposure": SensitiveDataExposureTester,
}
def __init__(self):
self._tester_cache = {}
def get_tester(self, vuln_type: str) -> BaseTester:
"""Get tester instance for a vulnerability type"""
if vuln_type in self._tester_cache:
return self._tester_cache[vuln_type]
tester_class = self.TESTER_CLASSES.get(vuln_type, BaseTester)
tester = tester_class()
self._tester_cache[vuln_type] = tester
return tester
def get_severity(self, vuln_type: str) -> str:
"""Get severity for a vulnerability type"""
info = self.VULNERABILITY_INFO.get(vuln_type, {})
return info.get("severity", "medium")
def get_cwe_id(self, vuln_type: str) -> str:
"""Get CWE ID for a vulnerability type"""
info = self.VULNERABILITY_INFO.get(vuln_type, {})
return info.get("cwe_id", "")
def get_title(self, vuln_type: str) -> str:
"""Get title for a vulnerability type"""
info = self.VULNERABILITY_INFO.get(vuln_type, {})
return info.get("title", vuln_type.replace("_", " ").title())
def get_description(self, vuln_type: str) -> str:
"""Get description for a vulnerability type"""
info = self.VULNERABILITY_INFO.get(vuln_type, {})
return info.get("description", "")
def get_impact(self, vuln_type: str) -> str:
"""Get impact for a vulnerability type"""
info = self.VULNERABILITY_INFO.get(vuln_type, {})
return info.get("impact", "")
def get_remediation(self, vuln_type: str) -> str:
"""Get remediation advice for a vulnerability type"""
info = self.VULNERABILITY_INFO.get(vuln_type, {})
return info.get("remediation", "")
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
from backend.core.vuln_engine.testers.base_tester import BaseTester
__all__ = ["BaseTester"]
@@ -0,0 +1,532 @@
"""
NeuroSploit v3 - Advanced Injection Vulnerability Testers
Testers for LDAP, XPath, GraphQL, CRLF, Header, Email, EL, Log, HTML, CSV, and ORM injection.
"""
import re
from typing import Tuple, Dict, Optional
from backend.core.vuln_engine.testers.base_tester import BaseTester
class LdapInjectionTester(BaseTester):
"""Tester for LDAP Injection vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "ldap_injection"
self.error_patterns = [
r"javax\.naming\.NamingException",
r"LDAPException",
r"ldap_search\(\)",
r"ldap_bind\(\)",
r"Invalid DN syntax",
r"Bad search filter",
r"DSA is unavailable",
r"LDAP error code \d+",
r"cn=.*,\s*ou=.*,\s*dc=",
r"objectClass=",
r"No such object",
r"invalid attribute description",
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for LDAP injection indicators"""
# Check for LDAP error messages
for pattern in self.error_patterns:
match = re.search(pattern, response_body, re.IGNORECASE)
if match:
return True, 0.8, f"LDAP error detected: {match.group(0)[:100]}"
# Wildcard injection - check if directory listing returned
if "*" in payload:
# Multiple DN entries suggest directory enumeration
dn_count = len(re.findall(r"dn:\s+\S+", response_body, re.IGNORECASE))
if dn_count > 1:
return True, 0.85, f"LDAP wildcard returned {dn_count} directory entries"
# Filter manipulation - check for unexpected data volume
if ")(|" in payload or "*)(objectClass" in payload:
if response_status == 200 and len(response_body) > 5000:
return True, 0.6, "LDAP filter manipulation may have returned extra data"
return False, 0.0, None
class XpathInjectionTester(BaseTester):
"""Tester for XPath Injection vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "xpath_injection"
self.error_patterns = [
r"XPathException",
r"Invalid XPath",
r"xpath syntax error",
r"javax\.xml\.xpath",
r"XPathEvalError",
r"xmlXPathEval:",
r"XPATH syntax error",
r"DOMXPath",
r"SimpleXMLElement::xpath\(\)",
r"lxml\.etree\.XPathEvalError",
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for XPath injection indicators"""
# XPath error messages
for pattern in self.error_patterns:
match = re.search(pattern, response_body, re.IGNORECASE)
if match:
return True, 0.85, f"XPath error detected: {match.group(0)[:100]}"
# Boolean-based XPath injection - true condition returning data
if ("' or '1'='1" in payload or "or 1=1" in payload):
if response_status == 200:
# Check for XML-like data in response
xml_tags = re.findall(r"<[a-zA-Z][^>]*>", response_body)
if len(xml_tags) > 5:
return True, 0.65, "XPath boolean injection may have returned XML data"
# Check for exposed XML node data
if "' | //" in payload or "extractvalue(" in payload.lower():
if re.search(r"<\?xml\s+version=", response_body):
return True, 0.7, "XML document exposed via XPath injection"
return False, 0.0, None
class GraphqlInjectionTester(BaseTester):
"""Tester for GraphQL Injection / Introspection vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "graphql_injection"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for GraphQL introspection and injection indicators"""
body_lower = response_body.lower()
# Introspection query response - schema exposure
if "__schema" in payload or "__type" in payload or "introspection" in payload.lower():
if '"__schema"' in response_body or '"__type"' in response_body:
return True, 0.9, "GraphQL introspection enabled - schema exposed"
if '"types"' in response_body and '"queryType"' in response_body:
return True, 0.9, "GraphQL schema types exposed via introspection"
# GraphQL error messages revealing structure
graphql_errors = [
r'"errors"\s*:\s*\[',
r"Cannot query field",
r"Unknown argument",
r"Field .* not found in type",
r"Syntax Error.*GraphQL",
r"GraphQL error",
]
for pattern in graphql_errors:
if re.search(pattern, response_body, re.IGNORECASE):
# Error messages can reveal field/type names
if re.search(r'"message"\s*:\s*".*(?:field|type|argument)', response_body, re.IGNORECASE):
return True, 0.7, "GraphQL error reveals schema information"
# Mutation that returned success unexpectedly
if "mutation" in payload.lower() and response_status == 200:
if '"data"' in response_body and '"errors"' not in response_body:
return True, 0.5, "GraphQL mutation succeeded - verify authorization"
return False, 0.0, None
class CrlfInjectionTester(BaseTester):
"""Tester for CRLF Injection vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "crlf_injection"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for CRLF injection - injected headers appearing in response"""
# Check if our injected header appeared in response headers
headers_lower = {k.lower(): v for k, v in response_headers.items()}
if "x-test" in headers_lower:
return True, 0.95, f"CRLF injection confirmed: X-Test header injected with value '{headers_lower['x-test']}'"
if "x-injected" in headers_lower:
return True, 0.95, f"CRLF injection confirmed: X-Injected header present"
# Check for Set-Cookie injection
if "set-cookie" in headers_lower and "neurosploit" in str(headers_lower.get("set-cookie", "")).lower():
return True, 0.9, "CRLF injection: injected Set-Cookie header detected"
# Check if payload characters are reflected unencoded in Location header
if "location" in headers_lower:
location = headers_lower["location"]
if "\r\n" in location or "%0d%0a" in location.lower():
return True, 0.8, "CRLF characters in Location header"
return False, 0.0, None
class HeaderInjectionTester(BaseTester):
"""Tester for Host Header Injection vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "header_injection"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for Host header reflected in response URLs"""
body_lower = response_body.lower()
# Check if injected host value appears in response links
evil_markers = ["evil.com", "attacker.com", "neurosploit.test"]
for marker in evil_markers:
if marker in payload.lower():
if marker in body_lower:
# Check if it appears in URLs, links, or redirects
url_pattern = rf'(?:href|src|action|url|link|redirect)\s*[=:]\s*["\']?[^"\']*{re.escape(marker)}'
if re.search(url_pattern, response_body, re.IGNORECASE):
return True, 0.9, f"Host header injected into response URL: {marker}"
return True, 0.7, f"Injected host value '{marker}' reflected in response"
# Password reset poisoning check
if "password" in body_lower and "reset" in body_lower:
headers_lower = {k.lower(): v for k, v in response_headers.items()}
if response_status in [200, 302]:
return True, 0.5, "Password reset response may use Host header for link generation"
return False, 0.0, None
class EmailInjectionTester(BaseTester):
"""Tester for Email Header Injection vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "email_injection"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for email header injection success indicators"""
body_lower = response_body.lower()
# Check for CC/BCC injection indicators
if any(h in payload.lower() for h in ["cc:", "bcc:", "\r\nto:", "%0acc:", "%0abcc:"]):
# Successful email send with injected headers
if response_status == 200:
success_indicators = [
"email sent", "message sent", "mail sent",
"successfully sent", "email delivered", "sent successfully",
]
for indicator in success_indicators:
if indicator in body_lower:
return True, 0.75, f"Email injection: '{indicator}' after CC/BCC injection attempt"
# Check for multiple recipient confirmation
if re.search(r"(?:sent to|delivered to|recipients?)\s*:?\s*\d+", response_body, re.IGNORECASE):
return True, 0.7, "Email sent to multiple recipients after injection"
# SMTP error leak
smtp_errors = [r"SMTP error", r"550 \d+", r"relay access denied", r"mail\(\).*failed"]
for pattern in smtp_errors:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.6, "SMTP error revealed - email injection attempted"
return False, 0.0, None
class ELInjectionTester(BaseTester):
"""Tester for Expression Language (EL) Injection vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "el_injection"
self.math_results = {
"${7*7}": "49",
"#{7*7}": "49",
"${3*11}": "33",
"#{3*11}": "33",
}
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for EL injection - expression evaluation in response"""
# Check mathematical expression results
for expr, result in self.math_results.items():
if expr in payload and result in response_body:
# Make sure the result isn't just the expression echoed
if expr not in response_body:
return True, 0.95, f"EL injection confirmed: {expr} evaluated to {result}"
# Java class names exposed via EL
java_indicators = [
r"java\.lang\.\w+",
r"java\.io\.File",
r"Runtime\.getRuntime",
r"ProcessBuilder",
r"javax\.\w+\.\w+",
r"org\.apache\.\w+",
r"getClass\(\)\.forName",
]
for pattern in java_indicators:
if re.search(pattern, response_body):
return True, 0.8, f"Java class exposure via EL injection: {pattern}"
# Spring EL specific
if "T(java.lang" in payload:
if re.search(r"class\s+\w+|java\.\w+", response_body):
return True, 0.7, "Spring EL injection indicator - Java class reference in response"
return False, 0.0, None
class LogInjectionTester(BaseTester):
"""Tester for Log Injection / Log4Shell / JNDI Injection vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "log_injection"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for log injection and JNDI callback indicators"""
# JNDI/Log4Shell detection via context callback
if "${jndi:" in payload.lower():
# Check context for callback confirmation
if context.get("callback_received"):
return True, 0.95, "JNDI injection confirmed via callback"
# Check for Log4j error in response
log4j_indicators = [
r"log4j", r"Log4jException", r"JNDI lookup",
r"javax\.naming", r"InitialContext",
]
for pattern in log4j_indicators:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.7, f"Log4j/JNDI indicator in response: {pattern}"
# Newline injection in log-like responses
if "\n" in payload or "%0a" in payload.lower() or "\\n" in payload:
# Check if response includes log-format lines with our injected content
log_patterns = [
r"\[\d{4}-\d{2}-\d{2}.*\].*neurosploit",
r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}.*neurosploit",
r"(?:INFO|WARN|ERROR|DEBUG)\s+.*neurosploit",
]
for pattern in log_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.7, "Log injection: injected content appears in log-format output"
# Generic log forging check
if "neurosploit" in payload and response_status == 200:
if re.search(r"(?:log|audit|event).*neurosploit", response_body, re.IGNORECASE):
return True, 0.5, "Injected marker appears in log/audit output"
return False, 0.0, None
class HtmlInjectionTester(BaseTester):
"""Tester for HTML Injection vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "html_injection"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for rendered HTML tags in response"""
if response_status >= 400:
return False, 0.0, None
# Check for injected HTML tags rendered in response
html_tests = [
(r"<b>neurosploit</b>", "Bold tag rendered"),
(r"<i>neurosploit</i>", "Italic tag rendered"),
(r"<u>neurosploit</u>", "Underline tag rendered"),
(r'<a\s+href=["\']?[^"\']*["\']?>neurosploit</a>', "Anchor tag rendered"),
(r'<img\s+src=["\']?[^"\']*["\']?', "Image tag rendered"),
(r'<form\s+[^>]*action=', "Form tag rendered"),
(r'<iframe\s+', "IFrame tag rendered"),
(r'<marquee>', "Marquee tag rendered"),
(r'<h1>neurosploit</h1>', "H1 tag rendered"),
(r'<div\s+style=', "Styled div rendered"),
]
for pattern, description in html_tests:
if re.search(pattern, response_body, re.IGNORECASE):
# Verify it wasn't already there (check if payload was actually injected)
if any(tag in payload.lower() for tag in ["<b>", "<i>", "<u>", "<a ", "<img", "<form", "<iframe", "<marquee", "<h1>", "<div"]):
return True, 0.8, f"HTML injection: {description}"
# Check for payload reflection without encoding
if "<" in payload and ">" in payload:
# Find the injected tag in response
tag_match = re.search(r"<(\w+)[^>]*>", payload)
if tag_match:
tag_name = tag_match.group(1)
if f"<{tag_name}" in response_body and f"&lt;{tag_name}" not in response_body:
return True, 0.75, f"HTML tag <{tag_name}> reflected without encoding"
return False, 0.0, None
class CsvInjectionTester(BaseTester):
"""Tester for CSV Injection (Formula Injection) vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "csv_injection"
self.formula_chars = ["=", "+", "-", "@", "\t", "\r"]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for formula characters preserved in CSV export responses"""
headers_lower = {k.lower(): v.lower() for k, v in response_headers.items()}
content_type = headers_lower.get("content-type", "")
# Check if response is CSV or spreadsheet
is_csv = "text/csv" in content_type or "spreadsheet" in content_type
is_export = "content-disposition" in headers_lower and any(
ext in headers_lower.get("content-disposition", "")
for ext in [".csv", ".xls", ".xlsx"]
)
if is_csv or is_export:
# Check if formula characters are preserved without escaping
for char in self.formula_chars:
if char in payload and payload in response_body:
return True, 0.8, f"CSV injection: formula character '{char}' preserved in export"
# Check for specific formula patterns
formula_patterns = [
r'[=+\-@].*(?:HYPERLINK|IMPORTXML|IMPORTDATA|cmd|powershell)',
r'=\w+\(.*\)',
]
for pattern in formula_patterns:
if re.search(pattern, response_body):
return True, 0.7, "CSV injection: formula pattern found in export data"
# Non-CSV response but payload was stored
if response_status in [200, 201] and any(c in payload for c in self.formula_chars[:4]):
if payload in response_body:
return True, 0.4, "Formula characters accepted and stored - verify CSV export"
return False, 0.0, None
class OrmInjectionTester(BaseTester):
"""Tester for ORM Injection vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "orm_injection"
self.error_patterns = [
r"Hibernate.*Exception",
r"javax\.persistence",
r"org\.hibernate",
r"ActiveRecord::.*Error",
r"Sequelize.*Error",
r"SQLAlchemy.*Error",
r"Doctrine.*Exception",
r"TypeORM.*Error",
r"Prisma.*Error",
r"EntityFramework.*Exception",
r"LINQ.*Exception",
r"django\.db.*Error",
r"peewee\.\w+Error",
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for ORM injection indicators"""
# ORM error messages
for pattern in self.error_patterns:
match = re.search(pattern, response_body, re.IGNORECASE)
if match:
return True, 0.8, f"ORM error detected: {match.group(0)[:100]}"
# Filter manipulation - unexpected data returned
if any(op in payload for op in ["__gt", "__lt", "__ne", "$ne", "$gt", ">=", "!="]):
if response_status == 200:
# Check context for baseline comparison
if "baseline_length" in context:
diff = abs(len(response_body) - context["baseline_length"])
if diff > 500:
return True, 0.6, f"ORM filter manipulation: response size differs by {diff} bytes"
# Check for data volume suggesting bypassed filters
if "__all" in payload or "objects.all" in payload:
if response_status == 200 and len(response_body) > 10000:
return True, 0.5, "ORM injection may have bypassed query filters - large data returned"
return False, 0.0, None
+347
View File
@@ -0,0 +1,347 @@
"""
NeuroSploit v3 - Authentication Vulnerability Testers
Testers for Auth Bypass, JWT, Session Fixation
"""
import re
import base64
import json
from typing import Tuple, Dict, Optional
from backend.core.vuln_engine.testers.base_tester import BaseTester
class AuthBypassTester(BaseTester):
"""Tester for Authentication Bypass"""
def __init__(self):
super().__init__()
self.name = "auth_bypass"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for authentication bypass"""
# Check for successful auth indicators after bypass payload
auth_success = [
"welcome", "dashboard", "logged in", "authenticated",
"success", "admin", "profile"
]
if response_status == 200:
body_lower = response_body.lower()
for indicator in auth_success:
if indicator in body_lower:
# Check if this was with a bypass payload
bypass_indicators = ["' or '1'='1", "admin'--", "' or 1=1"]
if any(bp in payload.lower() for bp in bypass_indicators):
return True, 0.8, f"Auth bypass possible: '{indicator}' found after injection"
# Check for redirect to authenticated area
location = response_headers.get("Location", "")
if response_status in [301, 302]:
if "dashboard" in location or "admin" in location or "home" in location:
return True, 0.7, f"Auth bypass: Redirect to {location}"
return False, 0.0, None
class JWTManipulationTester(BaseTester):
"""Tester for JWT Token Manipulation"""
def __init__(self):
super().__init__()
self.name = "jwt_manipulation"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for JWT manipulation vulnerabilities"""
# Check if manipulated JWT was accepted
if response_status == 200:
# Algorithm none attack
if '"alg":"none"' in payload or '"alg": "none"' in payload:
return True, 0.9, "JWT 'none' algorithm accepted"
# Check for elevated privileges response
elevated_indicators = ["admin", "administrator", "role.*admin"]
for pattern in elevated_indicators:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.8, "JWT manipulation: Elevated privileges detected"
# Check for JWT-specific errors
jwt_errors = [
r"invalid.*token", r"jwt.*expired", r"signature.*invalid",
r"token.*malformed", r"unauthorized"
]
for pattern in jwt_errors:
if re.search(pattern, response_body, re.IGNORECASE):
# Error means it's checking - note for further testing
return False, 0.0, None
return False, 0.0, None
class SessionFixationTester(BaseTester):
"""Tester for Session Fixation"""
def __init__(self):
super().__init__()
self.name = "session_fixation"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for session fixation vulnerability"""
# Check Set-Cookie header
set_cookie = response_headers.get("Set-Cookie", "")
# If session ID in URL was accepted
if "JSESSIONID=" in payload or "PHPSESSID=" in payload:
if response_status == 200:
# Check if session was NOT regenerated
if not set_cookie or "JSESSIONID" not in set_cookie:
return True, 0.7, "Session ID from URL accepted without regeneration"
# Check for session in URL
if re.search(r'[?&](?:session|sid|PHPSESSID|JSESSIONID)=', response_body):
return True, 0.6, "Session ID exposed in URL"
return False, 0.0, None
class WeakPasswordTester(BaseTester):
"""Tester for Weak Password acceptance"""
def __init__(self):
super().__init__()
self.name = "weak_password"
self.weak_passwords = [
"123456", "password", "12345678", "qwerty", "abc123",
"111111", "123123", "admin", "letmein", "welcome",
"1234", "1", "a"
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for successful login/registration with weak passwords"""
body_lower = response_body.lower()
# Check if payload contains a weak password
payload_has_weak = any(wp in payload for wp in self.weak_passwords)
if not payload_has_weak:
return False, 0.0, None
# Check for successful auth with weak password
if response_status in [200, 201, 302]:
success_indicators = [
r'"(?:access_)?token"\s*:', r'"session"\s*:',
r"(?:login|registration|signup)\s+successful",
r'"authenticated"\s*:\s*true', r'"success"\s*:\s*true',
r"welcome", r"dashboard", r"logged\s*in",
]
for pattern in success_indicators:
if re.search(pattern, response_body, re.IGNORECASE):
matched_pw = next((wp for wp in self.weak_passwords if wp in payload), "unknown")
return True, 0.85, f"Weak password accepted: '{matched_pw}' allowed for authentication"
# Redirect to authenticated area
location = response_headers.get("Location", "")
if response_status == 302 and any(x in location.lower() for x in ["dashboard", "home", "profile", "account"]):
matched_pw = next((wp for wp in self.weak_passwords if wp in payload), "unknown")
return True, 0.8, f"Weak password accepted: Redirect to authenticated area with '{matched_pw}'"
return False, 0.0, None
class DefaultCredentialsTester(BaseTester):
"""Tester for Default Credentials acceptance"""
def __init__(self):
super().__init__()
self.name = "default_credentials"
self.default_creds = [
("admin", "admin"), ("admin", "password"), ("admin", "admin123"),
("root", "root"), ("root", "toor"), ("root", "password"),
("administrator", "administrator"), ("admin", "1234"),
("test", "test"), ("guest", "guest"), ("user", "user"),
("admin", "changeme"), ("admin", "default"),
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for successful login with default credentials"""
body_lower = response_body.lower()
# Check if payload matches default creds
payload_lower = payload.lower()
matched_cred = None
for username, password in self.default_creds:
if username in payload_lower and password in payload_lower:
matched_cred = f"{username}/{password}"
break
if not matched_cred:
return False, 0.0, None
# Check for successful login
if response_status in [200, 201, 302]:
auth_success = [
r'"(?:access_)?token"\s*:', r'"session"\s*:',
r"(?:login|auth)\s+successful", r'"success"\s*:\s*true',
r'"authenticated"\s*:\s*true', r"welcome",
r"dashboard", r"admin\s*panel",
]
for pattern in auth_success:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.9, f"Default credentials accepted: {matched_cred}"
# Redirect to admin/dashboard
location = response_headers.get("Location", "")
if response_status == 302 and any(x in location.lower() for x in ["dashboard", "admin", "home", "panel"]):
return True, 0.85, f"Default credentials accepted: {matched_cred} (redirect to {location})"
return False, 0.0, None
class TwoFactorBypassTester(BaseTester):
"""Tester for Two-Factor Authentication Bypass"""
def __init__(self):
super().__init__()
self.name = "two_factor_bypass"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for authenticated access without completing 2FA"""
body_lower = response_body.lower()
# Check if we reached authenticated content without 2FA
if response_status == 200:
# Authenticated area indicators
auth_area_patterns = [
r"dashboard", r"my\s*account", r"profile",
r"settings", r"admin\s*panel", r'"user"\s*:\s*\{',
r'"email"\s*:\s*"[^"]+"', r'"role"\s*:',
]
# 2FA page indicators (we should NOT see these if bypassed)
twofa_page_patterns = [
r"(?:enter|verify)\s+(?:your\s+)?(?:otp|code|token|2fa)",
r"two.?factor", r"verification\s+code",
r"authenticator", r"sms\s+code",
]
has_auth_content = any(re.search(p, response_body, re.IGNORECASE) for p in auth_area_patterns)
is_twofa_page = any(re.search(p, response_body, re.IGNORECASE) for p in twofa_page_patterns)
if has_auth_content and not is_twofa_page:
# Check if payload suggests 2FA bypass attempt
bypass_indicators = [
"2fa", "otp", "mfa", "verify", "code",
"step2", "second", "challenge",
]
if any(bi in payload.lower() for bi in bypass_indicators):
return True, 0.85, "2FA bypass: Authenticated area accessed without completing 2FA"
# Direct navigation bypass
if context.get("skip_2fa") or context.get("direct_access"):
return True, 0.9, "2FA bypass: Direct navigation to authenticated page bypassed 2FA"
# Redirect skipping 2FA step
if response_status in [301, 302]:
location = response_headers.get("Location", "").lower()
if any(x in location for x in ["dashboard", "home", "account"]):
if "verify" not in location and "2fa" not in location and "otp" not in location:
return True, 0.7, "2FA bypass: Redirect to authenticated area skipping verification"
return False, 0.0, None
class OauthMisconfigTester(BaseTester):
"""Tester for OAuth Misconfiguration"""
def __init__(self):
super().__init__()
self.name = "oauth_misconfig"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for OAuth misconfiguration (open redirect, token leakage)"""
# Check for open redirect in OAuth flow
if response_status in [301, 302, 303, 307]:
location = response_headers.get("Location", "")
# External redirect in OAuth callback
if "redirect_uri" in payload.lower() or "callback" in payload.lower():
# Check if redirecting to attacker-controlled domain
evil_domains = ["evil.com", "attacker.com", "malicious.com"]
if any(domain in location for domain in evil_domains):
return True, 0.9, f"OAuth misconfig: Open redirect in OAuth flow to {location}"
# Check if arbitrary redirect_uri accepted
if payload in location:
return True, 0.85, "OAuth misconfig: Arbitrary redirect_uri accepted"
# Check for token in URL parameters (should be in fragment or POST)
if response_status in [200, 302]:
location = response_headers.get("Location", "")
# Token in query string instead of fragment
token_in_url = re.search(
r'[?&](?:access_token|token|code)=([A-Za-z0-9._-]+)',
location
)
if token_in_url:
return True, 0.8, "OAuth misconfig: Token/code exposed in URL query parameters"
# Token in response body URL
token_in_body = re.search(
r'(?:redirect|callback|return)["\']?\s*[:=]\s*["\']?https?://[^"\'>\s]*[?&]access_token=',
response_body, re.IGNORECASE
)
if token_in_body:
return True, 0.75, "OAuth misconfig: Access token in redirect URL"
# Check for missing state parameter (CSRF in OAuth)
if "state=" not in response_body and "state=" not in response_headers.get("Location", ""):
if re.search(r"(?:authorize|oauth|auth)\?", response_body, re.IGNORECASE):
return True, 0.6, "OAuth misconfig: Missing state parameter (CSRF risk)"
return False, 0.0, None
@@ -0,0 +1,293 @@
"""
NeuroSploit v3 - Authorization Vulnerability Testers
Testers for IDOR, BOLA, Privilege Escalation
"""
import re
from typing import Tuple, Dict, Optional
from backend.core.vuln_engine.testers.base_tester import BaseTester
class IDORTester(BaseTester):
"""Tester for Insecure Direct Object Reference"""
def __init__(self):
super().__init__()
self.name = "idor"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for IDOR vulnerability"""
# Check if we got data for a different ID
if response_status == 200:
# Look for user data indicators
user_data_patterns = [
r'"user_?id"\s*:\s*\d+',
r'"email"\s*:\s*"[^"]+"',
r'"name"\s*:\s*"[^"]+"',
r'"account"\s*:',
r'"profile"\s*:'
]
for pattern in user_data_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
# Check if ID in payload differs from context user
if "original_id" in context:
if context["original_id"] not in payload:
return True, 0.8, f"IDOR: Accessed different user's data"
# Generic data access check
if len(response_body) > 50:
return True, 0.6, "IDOR: Response contains data - verify authorization"
return False, 0.0, None
class BOLATester(BaseTester):
"""Tester for Broken Object Level Authorization"""
def __init__(self):
super().__init__()
self.name = "bola"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for BOLA in APIs"""
# BOLA in REST APIs
if response_status == 200:
# Check for successful data access
data_indicators = [
r'"data"\s*:\s*\{',
r'"items"\s*:\s*\[',
r'"result"\s*:\s*\{',
r'"id"\s*:\s*\d+'
]
for pattern in data_indicators:
if re.search(pattern, response_body):
return True, 0.7, "BOLA: API returned object data - verify authorization"
# Check for enumeration possibilities
if response_status in [200, 404]:
# Different status for valid vs invalid IDs indicates BOLA risk
return True, 0.5, "BOLA: Different responses for IDs - enumeration possible"
return False, 0.0, None
class PrivilegeEscalationTester(BaseTester):
"""Tester for Privilege Escalation"""
def __init__(self):
super().__init__()
self.name = "privilege_escalation"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for privilege escalation"""
if response_status == 200:
# Check for admin/elevated access indicators
elevated_access = [
r'"role"\s*:\s*"admin"',
r'"is_?admin"\s*:\s*true',
r'"admin"\s*:\s*true',
r'"privilege"\s*:\s*"(?:admin|root|superuser)"',
r'"permissions"\s*:\s*\[.*"admin".*\]'
]
for pattern in elevated_access:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.9, f"Privilege escalation: Elevated role in response"
# Check for admin functionality access
admin_functions = [
"user management", "delete user", "admin panel",
"system settings", "all users", "user list"
]
body_lower = response_body.lower()
for func in admin_functions:
if func in body_lower:
return True, 0.7, f"Privilege escalation: Admin functionality '{func}' accessible"
return False, 0.0, None
class BflaTester(BaseTester):
"""Tester for Broken Function Level Authorization (BFLA)"""
def __init__(self):
super().__init__()
self.name = "bfla"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for admin functionality accessible to regular user"""
if response_status == 200:
# Admin-specific data patterns
admin_data_patterns = [
r'"users"\s*:\s*\[',
r'"all_users"\s*:',
r'"admin_settings"\s*:',
r'"system_config"\s*:',
r'"audit_log"\s*:',
r'"role"\s*:\s*"admin"',
r'"permissions"\s*:\s*\[',
r'"api_keys"\s*:\s*\[',
]
for pattern in admin_data_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
# Check if this was an admin endpoint accessed as regular user
admin_url_indicators = [
"/admin", "/manage", "/users", "/settings",
"/config", "/system", "/audit", "/logs",
]
if any(ind in payload.lower() for ind in admin_url_indicators):
return True, 0.85, f"BFLA: Admin data returned for non-admin request"
# Admin page content
admin_content = [
"user management", "system configuration", "admin dashboard",
"manage users", "all accounts", "server status",
"delete user", "create admin",
]
body_lower = response_body.lower()
for content in admin_content:
if content in body_lower:
return True, 0.8, f"BFLA: Admin functionality '{content}' accessible to regular user"
# Admin endpoint should return 403 for non-admin
if response_status not in [401, 403] and context.get("is_admin_endpoint"):
if response_status == 200:
return True, 0.7, "BFLA: Admin endpoint returned 200 instead of 403"
return False, 0.0, None
class MassAssignmentTester(BaseTester):
"""Tester for Mass Assignment vulnerability"""
def __init__(self):
super().__init__()
self.name = "mass_assignment"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for extra parameters being accepted and modifying model state"""
if response_status in [200, 201]:
# Check if privileged fields were accepted
privileged_fields = [
(r'"(?:is_)?admin"\s*:\s*true', "admin flag"),
(r'"role"\s*:\s*"admin"', "admin role"),
(r'"role"\s*:\s*"superuser"', "superuser role"),
(r'"verified"\s*:\s*true', "verified status"),
(r'"is_staff"\s*:\s*true', "staff flag"),
(r'"is_superuser"\s*:\s*true', "superuser flag"),
(r'"balance"\s*:\s*\d{4,}', "balance modification"),
(r'"credits"\s*:\s*\d{3,}', "credits modification"),
(r'"discount"\s*:\s*\d+', "discount field"),
(r'"price"\s*:\s*0', "price zeroed"),
]
# Check if payload attempted mass assignment
mass_assign_indicators = [
"admin", "role", "is_staff", "is_superuser",
"verified", "balance", "credits", "price", "discount",
]
payload_has_mass_assign = any(ind in payload.lower() for ind in mass_assign_indicators)
if payload_has_mass_assign:
for pattern, field_name in privileged_fields:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.85, f"Mass assignment: Privileged field '{field_name}' accepted and reflected"
# Check if response changed compared to baseline
if context.get("baseline_body"):
baseline = context["baseline_body"]
if response_body != baseline and len(response_body) > len(baseline):
return True, 0.6, "Mass assignment: Response differs from baseline after extra parameters"
return False, 0.0, None
class ForcedBrowsingTester(BaseTester):
"""Tester for Forced Browsing (direct access to restricted URLs)"""
def __init__(self):
super().__init__()
self.name = "forced_browsing"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for direct access to restricted URLs without proper authorization"""
# Should get 401/403/302 for restricted content, not 200
if response_status == 200:
# Sensitive content indicators
sensitive_patterns = [
(r"(?:admin|management)\s+(?:panel|dashboard|console)", "admin panel"),
(r'"(?:password|secret|api_key|token)"\s*:\s*"[^"]+"', "sensitive data"),
(r"(?:backup|dump|export)\s+(?:file|data|database)", "backup files"),
(r"phpinfo\(\)", "PHP info page"),
(r"(?:configuration|config)\s+(?:file|settings)", "configuration page"),
(r"(?:internal|private)\s+(?:api|endpoint|documentation)", "internal docs"),
(r"(?:debug|diagnostic)\s+(?:info|page|console)", "debug page"),
(r"(?:user|customer)\s+(?:list|database|records)", "user records"),
]
for pattern, desc in sensitive_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.8, f"Forced browsing: Restricted content accessible - {desc}"
# Check for restricted URL patterns in payload
restricted_paths = [
"/admin", "/backup", "/config", "/internal",
"/debug", "/private", "/management", "/phpinfo",
"/.git", "/.env", "/wp-admin", "/server-status",
]
if any(path in payload.lower() for path in restricted_paths):
if len(response_body) > 200:
return True, 0.7, "Forced browsing: Restricted URL returned content (200 OK)"
# Check that redirect isn't to the same restricted page (false positive)
if response_status in [301, 302]:
location = response_headers.get("Location", "").lower()
if "login" in location or "signin" in location or "auth" in location:
return False, 0.0, None # Properly redirecting to login
return False, 0.0, None
@@ -0,0 +1,107 @@
"""
NeuroSploit v3 - Base Vulnerability Tester
Base class for all vulnerability testers.
"""
from typing import Tuple, Dict, List, Optional, Any
from urllib.parse import urlparse, urlencode, parse_qs, urlunparse
class BaseTester:
"""Base class for vulnerability testers"""
def __init__(self):
self.name = "base"
def build_request(
self,
endpoint,
payload: str
) -> Tuple[str, Dict, Dict, Optional[str]]:
"""
Build a test request with the payload.
Returns:
Tuple of (url, params, headers, body)
"""
url = endpoint.url
params = {}
headers = {"User-Agent": "NeuroSploit/3.0"}
body = None
# Inject payload into parameters if endpoint has them
if endpoint.parameters:
for param in endpoint.parameters:
param_name = param.get("name", param) if isinstance(param, dict) else param
params[param_name] = payload
else:
# Try to inject into URL query string
parsed = urlparse(url)
if parsed.query:
query_params = parse_qs(parsed.query)
for key in query_params:
query_params[key] = [payload]
new_query = urlencode(query_params, doseq=True)
url = urlunparse(parsed._replace(query=new_query))
else:
# Add as query parameter
params["test"] = payload
return url, params, headers, body
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""
Analyze response to determine if vulnerable.
Returns:
Tuple of (is_vulnerable, confidence, evidence)
"""
return False, 0.0, None
def check_timeout_vulnerability(self, vuln_type: str) -> bool:
"""Check if timeout indicates vulnerability for this type"""
return False
def get_injection_points(self, endpoint) -> List[Dict]:
"""Get all injection points for an endpoint"""
points = []
# URL parameters
if endpoint.parameters:
for param in endpoint.parameters:
param_name = param.get("name", param) if isinstance(param, dict) else param
points.append({
"type": "parameter",
"name": param_name,
"location": "query"
})
# Parse URL for query params
parsed = urlparse(endpoint.url)
if parsed.query:
query_params = parse_qs(parsed.query)
for key in query_params:
if not any(p.get("name") == key for p in points):
points.append({
"type": "parameter",
"name": key,
"location": "query"
})
# Headers that might be injectable
injectable_headers = ["User-Agent", "Referer", "X-Forwarded-For", "Cookie"]
for header in injectable_headers:
points.append({
"type": "header",
"name": header,
"location": "header"
})
return points
@@ -0,0 +1,430 @@
"""
NeuroSploit v3 - Client-Side Vulnerability Testers
Testers for CORS, Clickjacking, Open Redirect
"""
import re
from typing import Tuple, Dict, Optional
from backend.core.vuln_engine.testers.base_tester import BaseTester
class CORSTester(BaseTester):
"""Tester for CORS Misconfiguration"""
def __init__(self):
super().__init__()
self.name = "cors_misconfig"
def build_request(self, endpoint, payload: str) -> Tuple[str, Dict, Dict, Optional[str]]:
"""Build CORS test request with Origin header"""
headers = {
"User-Agent": "NeuroSploit/3.0",
"Origin": payload # payload is the test origin
}
return endpoint.url, {}, headers, None
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for CORS misconfiguration"""
acao = response_headers.get("Access-Control-Allow-Origin", "")
acac = response_headers.get("Access-Control-Allow-Credentials", "")
# Wildcard with credentials
if acao == "*" and acac.lower() == "true":
return True, 0.95, "CORS: Wildcard origin with credentials allowed"
# Origin reflection
if acao == payload:
if acac.lower() == "true":
return True, 0.9, f"CORS: Arbitrary origin '{payload}' reflected with credentials"
return True, 0.7, f"CORS: Arbitrary origin '{payload}' reflected"
# Wildcard (without credentials still risky)
if acao == "*":
return True, 0.5, "CORS: Wildcard origin allowed"
# Null origin accepted
if acao == "null":
return True, 0.8, "CORS: Null origin accepted"
return False, 0.0, None
class ClickjackingTester(BaseTester):
"""Tester for Clickjacking vulnerability"""
def __init__(self):
super().__init__()
self.name = "clickjacking"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for clickjacking protection"""
# Check X-Frame-Options
xfo = response_headers.get("X-Frame-Options", "").upper()
# Check CSP frame-ancestors
csp = response_headers.get("Content-Security-Policy", "")
has_frame_ancestors = "frame-ancestors" in csp.lower()
if not xfo and not has_frame_ancestors:
return True, 0.8, "Clickjacking: No X-Frame-Options or frame-ancestors CSP"
if xfo and xfo not in ["DENY", "SAMEORIGIN"]:
return True, 0.7, f"Clickjacking: Weak X-Frame-Options: {xfo}"
# Check for JS frame busting that can be bypassed
frame_busters = [
r"if\s*\(\s*top\s*[!=]=",
r"if\s*\(\s*self\s*[!=]=\s*top",
r"if\s*\(\s*parent\s*[!=]="
]
for pattern in frame_busters:
if re.search(pattern, response_body):
if not xfo and not has_frame_ancestors:
return True, 0.6, "Clickjacking: Only JS frame-busting (bypassable)"
return False, 0.0, None
class OpenRedirectTester(BaseTester):
"""Tester for Open Redirect vulnerability"""
def __init__(self):
super().__init__()
self.name = "open_redirect"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for open redirect"""
# Check redirect status and Location header
if response_status in [301, 302, 303, 307, 308]:
location = response_headers.get("Location", "")
# Check if our payload URL is in Location
if payload in location:
return True, 0.9, f"Open redirect: Redirecting to {location}"
# Check for partial match (domain)
if "evil.com" in payload and "evil.com" in location:
return True, 0.9, "Open redirect: External domain in redirect"
# Check for meta refresh redirect
meta_refresh = re.search(
r'<meta[^>]+http-equiv=["\']?refresh["\']?[^>]+content=["\']?\d+;\s*url=([^"\'>\s]+)',
response_body, re.IGNORECASE
)
if meta_refresh:
redirect_url = meta_refresh.group(1)
if payload in redirect_url:
return True, 0.8, f"Open redirect via meta refresh: {redirect_url}"
# Check for JavaScript redirect
js_redirects = [
rf'location\.href\s*=\s*["\']?{re.escape(payload)}',
rf'location\.assign\s*\(["\']?{re.escape(payload)}',
rf'location\.replace\s*\(["\']?{re.escape(payload)}'
]
for pattern in js_redirects:
if re.search(pattern, response_body):
return True, 0.7, "Open redirect via JavaScript"
return False, 0.0, None
class DomClobberingTester(BaseTester):
"""Tester for DOM Clobbering vulnerability"""
def __init__(self):
super().__init__()
self.name = "dom_clobbering"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for HTML injection that could override JS variables via DOM clobbering"""
if response_status == 200:
# Check if injected HTML with id/name attributes is reflected
clobber_patterns = [
r'<(?:a|form|img|input|iframe|embed|object)\s+[^>]*(?:id|name)\s*=\s*["\']?(?:' + re.escape(payload.split("=")[0] if "=" in payload else payload) + r')',
r'<a\s+[^>]*id=["\'][^"\']+["\'][^>]*href=["\']',
r'<form\s+[^>]*name=["\'][^"\']+["\']',
]
for pattern in clobber_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.8, "DOM Clobbering: Injected HTML with id/name attribute reflected"
# Check for common clobberable global variables
clobber_targets = [
r'<[^>]+id=["\'](?:location|document|window|self|top|frames|opener|parent)["\']',
r'<[^>]+name=["\'](?:location|document|window|self|top|frames|opener|parent)["\']',
]
for pattern in clobber_targets:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.85, "DOM Clobbering: HTML element with JS global variable name injected"
return False, 0.0, None
class PostMessageVulnTester(BaseTester):
"""Tester for postMessage vulnerability (missing origin check)"""
def __init__(self):
super().__init__()
self.name = "postmessage_vuln"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for addEventListener('message') without origin validation"""
if response_status == 200:
# Find message event listeners
message_listener = re.search(
r'addEventListener\s*\(\s*["\']message["\']',
response_body
)
if message_listener:
# Check if origin is NOT validated nearby
# Get surrounding context (500 chars after listener)
listener_pos = message_listener.start()
handler_block = response_body[listener_pos:listener_pos + 500]
origin_checks = [
r'\.origin\s*[!=]==?\s*["\']',
r'event\.origin',
r'e\.origin',
r'msg\.origin',
r'origin\s*===',
]
has_origin_check = any(re.search(p, handler_block) for p in origin_checks)
if not has_origin_check:
return True, 0.85, "postMessage vulnerability: Message listener without origin validation"
else:
# Origin check exists but might be weak
if re.search(r'\.origin\s*[!=]==?\s*["\']["\']', handler_block):
return True, 0.7, "postMessage vulnerability: Origin check appears to be empty string"
# Check for postMessage with wildcard origin
wildcard_post = re.search(
r'\.postMessage\s*\([^)]+,\s*["\']\*["\']',
response_body
)
if wildcard_post:
return True, 0.75, "postMessage vulnerability: postMessage with wildcard '*' target origin"
return False, 0.0, None
class WebsocketHijackTester(BaseTester):
"""Tester for WebSocket Cross-Origin Hijacking"""
def __init__(self):
super().__init__()
self.name = "websocket_hijack"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for WebSocket connections accepting cross-origin requests"""
# WebSocket upgrade accepted (101 Switching Protocols)
if response_status == 101:
# Check if Origin header was sent and accepted
upgrade = response_headers.get("Upgrade", "").lower()
if upgrade == "websocket":
return True, 0.8, "WebSocket hijack: Cross-origin WebSocket upgrade accepted"
# Check for WebSocket endpoint in response without origin validation
if response_status == 200:
ws_patterns = [
r'new\s+WebSocket\s*\(\s*["\']wss?://',
r'ws://[^"\'>\s]+',
r'wss://[^"\'>\s]+',
]
for pattern in ws_patterns:
if re.search(pattern, response_body):
# Check for lack of CORS-like origin checking
if "origin" not in response_body.lower() or context.get("cross_origin_accepted"):
return True, 0.7, "WebSocket hijack: WebSocket endpoint found without apparent origin validation"
# 403 on WebSocket with wrong origin is good (not vulnerable)
if response_status == 403:
return False, 0.0, None
return False, 0.0, None
class PrototypePollutionTester(BaseTester):
"""Tester for JavaScript Prototype Pollution"""
def __init__(self):
super().__init__()
self.name = "prototype_pollution"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for __proto__ pollution indicators"""
if response_status == 200:
# Check if __proto__ payload was processed
proto_indicators = [
r'__proto__',
r'constructor\.prototype',
r'Object\.prototype',
]
# Payload should contain proto pollution attempt
is_proto_payload = any(
ind in payload for ind in ["__proto__", "constructor", "prototype"]
)
if is_proto_payload:
# Check for pollution effect in response
pollution_effects = [
r'"__proto__"\s*:\s*\{',
r'"polluted"\s*:\s*true',
r'"isAdmin"\s*:\s*true',
r'"__proto__":\s*\{[^}]*\}',
]
for pattern in pollution_effects:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.85, "Prototype pollution: __proto__ property accepted and reflected"
# Check if server processed the prototype chain modification
if response_status == 200 and "__proto__" in response_body:
return True, 0.7, "Prototype pollution: __proto__ present in server response"
# Check for error indicating proto processing
if re.search(r"(?:cannot|unable to).*(?:merge|assign|extend).*proto", response_body, re.IGNORECASE):
return True, 0.6, "Prototype pollution: Server attempted to process __proto__"
return False, 0.0, None
class CssInjectionTester(BaseTester):
"""Tester for CSS Injection vulnerability"""
def __init__(self):
super().__init__()
self.name = "css_injection"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for CSS code rendered in style context"""
if response_status == 200:
# Check if CSS payload is reflected in style context
css_contexts = [
# Inside <style> tags
r'<style[^>]*>(?:[^<]*?)' + re.escape(payload)[:30].replace("\\", "\\\\"),
# Inside style attribute
r'style\s*=\s*["\'][^"\']*' + re.escape(payload)[:30].replace("\\", "\\\\"),
]
for pattern in css_contexts:
try:
if re.search(pattern, response_body, re.IGNORECASE | re.DOTALL):
return True, 0.85, "CSS injection: Payload reflected in style context"
except re.error:
continue
# Check for common CSS injection payloads reflected
css_attack_patterns = [
r'expression\s*\(',
r'url\s*\(\s*["\']?javascript:',
r'@import\s+["\']?https?://',
r'background:\s*url\s*\(\s*["\']?https?://[^"\')\s]*attacker',
r'behavior:\s*url\s*\(',
r'-moz-binding:\s*url\s*\(',
]
for pattern in css_attack_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.8, "CSS injection: Dangerous CSS property reflected"
return False, 0.0, None
class TabnabbingTester(BaseTester):
"""Tester for Reverse Tabnabbing vulnerability"""
def __init__(self):
super().__init__()
self.name = "tabnabbing"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for target=_blank links without rel=noopener"""
if response_status == 200:
# Find all target=_blank links
blank_links = re.finditer(
r'<a\s+[^>]*target\s*=\s*["\']_blank["\'][^>]*>',
response_body,
re.IGNORECASE
)
vulnerable_count = 0
for match in blank_links:
link_tag = match.group(0)
# Check for rel=noopener or rel=noreferrer
has_protection = re.search(
r'rel\s*=\s*["\'][^"\']*(?:noopener|noreferrer)[^"\']*["\']',
link_tag,
re.IGNORECASE
)
if not has_protection:
vulnerable_count += 1
if vulnerable_count > 0:
confidence = min(0.5 + vulnerable_count * 0.1, 0.8)
return True, confidence, f"Tabnabbing: {vulnerable_count} target=_blank link(s) without rel=noopener/noreferrer"
return False, 0.0, None
@@ -0,0 +1,405 @@
"""
NeuroSploit v3 - Cloud & Supply Chain Vulnerability Testers
Testers for S3 misconfiguration, cloud metadata, subdomain takeover,
vulnerable dependencies, container escape, and serverless misconfiguration.
"""
import re
from typing import Tuple, Dict, Optional
from backend.core.vuln_engine.testers.base_tester import BaseTester
class S3BucketMisconfigTester(BaseTester):
"""Tester for S3 Bucket Misconfiguration vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "s3_bucket_misconfig"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for S3 bucket listing or misconfiguration"""
# Bucket listing XML response
if re.search(r"<ListBucketResult\s", response_body):
return True, 0.95, "S3 bucket listing enabled - bucket contents exposed"
# Bucket listing with objects
if "<Contents>" in response_body and "<Key>" in response_body:
keys = re.findall(r"<Key>([^<]+)</Key>", response_body)
if keys:
return True, 0.95, f"S3 bucket listing: {len(keys)} objects exposed (e.g., {keys[0][:50]})"
# NoSuchBucket - potential subdomain takeover
if "NoSuchBucket" in response_body:
bucket_match = re.search(r"<BucketName>([^<]+)</BucketName>", response_body)
bucket_name = bucket_match.group(1) if bucket_match else "unknown"
return True, 0.8, f"S3 bucket '{bucket_name}' does not exist - potential takeover"
# AccessDenied with bucket info
if "AccessDenied" in response_body and "s3.amazonaws.com" in response_body:
return True, 0.5, "S3 bucket exists but access denied - bucket enumerated"
# Public write/upload success
if response_status in [200, 204] and context.get("is_upload_test"):
headers_lower = {k.lower(): v for k, v in response_headers.items()}
if "x-amz-request-id" in headers_lower:
return True, 0.9, "S3 bucket allows public write access"
# Bucket policy exposed
if '"Statement"' in response_body and '"Effect"' in response_body:
if '"s3:' in response_body:
return True, 0.85, "S3 bucket policy exposed"
return False, 0.0, None
class CloudMetadataExposureTester(BaseTester):
"""Tester for Cloud Metadata Exposure vulnerabilities (SSRF to metadata)"""
def __init__(self):
super().__init__()
self.name = "cloud_metadata_exposure"
self.metadata_indicators = {
# AWS
"aws": [
(r"ami-[0-9a-f]{8,17}", "AWS AMI ID"),
(r"i-[0-9a-f]{8,17}", "AWS Instance ID"),
(r"arn:aws:[a-z0-9-]+:[a-z0-9-]*:\d{12}:", "AWS ARN"),
(r"AKIA[0-9A-Z]{16}", "AWS Access Key"),
(r"169\.254\.169\.254", "AWS metadata endpoint"),
(r"\"(?:AccessKeyId|SecretAccessKey|Token)\"", "AWS IAM credentials"),
(r"ec2\.internal", "AWS internal hostname"),
(r"\"accountId\"\s*:\s*\"\d{12}\"", "AWS Account ID"),
],
# GCP
"gcp": [
(r"projects/\d+/", "GCP Project reference"),
(r"metadata\.google\.internal", "GCP metadata endpoint"),
(r"\"access_token\"\s*:\s*\"ya29\.", "GCP OAuth token"),
(r"compute\.googleapis\.com", "GCP Compute API"),
(r"serviceAccounts/[^/]+/token", "GCP service account token"),
],
# Azure
"azure": [
(r"metadata\.azure\.com", "Azure metadata endpoint"),
(r"(?:subscriptionId|resourceGroupName)\"\s*:\s*\"", "Azure resource info"),
(r"\.blob\.core\.windows\.net", "Azure Blob Storage"),
(r"\.vault\.azure\.net", "Azure Key Vault"),
(r"\"access_token\"\s*:\s*\"eyJ", "Azure JWT token"),
],
}
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for cloud metadata in response"""
findings = []
for provider, patterns in self.metadata_indicators.items():
for pattern, description in patterns:
if re.search(pattern, response_body):
findings.append(f"{provider.upper()}: {description}")
if findings:
# IAM credentials or tokens are critical
critical = any(k in f for f in findings for k in ["credentials", "Access Key", "token", "Token"])
confidence = 0.95 if critical else 0.8
return True, confidence, f"Cloud metadata exposure: {', '.join(findings[:3])}"
# Check for metadata endpoint access (SSRF to cloud metadata)
metadata_urls = ["169.254.169.254", "metadata.google.internal",
"metadata.azure.com", "100.100.100.200"]
for url in metadata_urls:
if url in payload and response_status == 200 and len(response_body) > 50:
return True, 0.85, f"Cloud metadata accessible via SSRF ({url})"
return False, 0.0, None
class SubdomainTakeoverTester(BaseTester):
"""Tester for Subdomain Takeover vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "subdomain_takeover"
self.takeover_fingerprints = [
# AWS S3
(r"NoSuchBucket", "S3 bucket - NoSuchBucket"),
(r"The specified bucket does not exist", "S3 bucket does not exist"),
# GitHub Pages
(r"There isn't a GitHub Pages site here", "GitHub Pages - unclaimed"),
(r"For root URLs.*GitHub Pages", "GitHub Pages not configured"),
# Heroku
(r"No such app", "Heroku - app not found"),
(r"herokucdn\.com/error-pages/no-such-app", "Heroku - no such app"),
# Shopify
(r"Sorry, this shop is currently unavailable", "Shopify - shop unavailable"),
# Tumblr
(r"There's nothing here\.", "Tumblr - unclaimed"),
(r"Whatever you were looking for doesn't currently exist", "Tumblr not found"),
# WordPress.com
(r"Do you want to register.*wordpress\.com", "WordPress.com - unclaimed"),
# Azure
(r"404 Web Site not found", "Azure - web app not found"),
# Fastly
(r"Fastly error: unknown domain", "Fastly - unknown domain"),
# Pantheon
(r"404 error unknown site", "Pantheon - unknown site"),
# Zendesk
(r"Help Center Closed", "Zendesk - closed"),
# Unbounce
(r"The requested URL was not found on this server.*unbounce", "Unbounce - not found"),
# Surge.sh
(r"project not found", "Surge.sh - not found"),
# Fly.io
(r"404.*fly\.io", "Fly.io - not found"),
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for cloud provider error pages indicating takeover opportunity"""
for pattern, description in self.takeover_fingerprints:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.9, f"Subdomain takeover: {description}"
# CNAME pointing to unclaimed resource
if context.get("cname_target"):
cname = context["cname_target"]
unclaimed_domains = [
".s3.amazonaws.com", ".herokuapp.com", ".github.io",
".azurewebsites.net", ".cloudfront.net", ".fastly.net",
".ghost.io", ".myshopify.com", ".surge.sh",
]
for domain in unclaimed_domains:
if cname.endswith(domain) and response_status in [404, 0]:
return True, 0.85, f"Subdomain takeover: CNAME to {cname} returns {response_status}"
# NXDOMAIN with CNAME
if context.get("dns_nxdomain") and context.get("has_cname"):
return True, 0.8, "Subdomain takeover: CNAME exists but target domain is NXDOMAIN"
return False, 0.0, None
class VulnerableDependencyTester(BaseTester):
"""Tester for Vulnerable Dependency detection"""
def __init__(self):
super().__init__()
self.name = "vulnerable_dependency"
self.vulnerable_libs = [
# JavaScript
(r"jquery[/.-](?:1\.\d+|2\.\d+|3\.[0-4]\.\d+)", "jQuery < 3.5.0 (XSS)"),
(r"angular[/.-]1\.[0-5]\.\d+", "AngularJS < 1.6 (sandbox escape)"),
(r"lodash[/.-](?:[0-3]\.\d+|4\.(?:1[0-6]|[0-9])\.\d+)", "Lodash < 4.17.21 (prototype pollution)"),
(r"bootstrap[/.-](?:[1-3]\.\d+|4\.[0-3]\.\d+)", "Bootstrap < 4.3.1 (XSS)"),
(r"moment[/.-](?:[01]\.\d+|2\.(?:[0-9]|1[0-8])\.\d+)", "Moment.js < 2.19.3 (ReDoS)"),
(r"handlebars[/.-](?:[0-3]\.\d+|4\.[0-6]\.\d+)", "Handlebars < 4.7.7 (prototype pollution)"),
# Python
(r"Django[/=](?:1\.\d+|2\.[01]\.\d+|3\.0\.\d+)", "Django < 3.1 (multiple CVEs)"),
(r"Flask[/=](?:0\.\d+|1\.[01]\.\d+)", "Flask < 2.0 (known issues)"),
(r"requests[/=]2\.(?:[0-9]|1\d|2[0-4])\.\d+", "Requests < 2.25 (CVE-2023-32681)"),
# Java
(r"log4j[/-]2\.(?:[0-9]|1[0-4])\.\d+", "Log4j < 2.15 (Log4Shell CVE-2021-44228)"),
(r"spring-core[/-](?:[1-4]\.\d+|5\.[0-2]\.\d+)", "Spring < 5.3 (Spring4Shell)"),
(r"jackson-databind[/-]2\.(?:[0-8]|9\.[0-9])\.\d*", "Jackson < 2.9.10 (deserialization)"),
# PHP
(r"laravel/framework[/:]\s*v?(?:[1-7]\.\d+|8\.[0-7]\d)", "Laravel < 8.80 (known issues)"),
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for known vulnerable library version strings"""
findings = []
# Check response body (JS files, package.json, error pages)
for pattern, description in self.vulnerable_libs:
match = re.search(pattern, response_body, re.IGNORECASE)
if match:
findings.append(f"{match.group(0)} - {description}")
# Check headers for version info
headers_str = "\n".join(f"{k}: {v}" for k, v in response_headers.items())
for pattern, description in self.vulnerable_libs:
match = re.search(pattern, headers_str, re.IGNORECASE)
if match and description not in str(findings):
findings.append(f"{match.group(0)} - {description}")
if findings:
# Log4Shell and Spring4Shell are critical
critical = any(k in f for f in findings for k in ["Log4Shell", "Spring4Shell", "deserialization"])
confidence = 0.9 if critical else 0.75
return True, confidence, f"Vulnerable dependency: {'; '.join(findings[:3])}"
return False, 0.0, None
class ContainerEscapeTester(BaseTester):
"""Tester for Container Escape / Container Misconfiguration vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "container_escape"
self.container_indicators = [
# Docker
(r"\.dockerenv", "Docker environment file accessible"),
(r"docker\.sock", "Docker socket exposed"),
(r"/var/run/docker\.sock", "Docker socket path"),
# Cgroup
(r"docker[/-][0-9a-f]{12,64}", "Docker container cgroup"),
(r"/proc/self/cgroup.*docker", "Docker cgroup detected"),
(r"/proc/self/cgroup.*kubepods", "Kubernetes pod cgroup"),
# Kubernetes
(r"KUBERNETES_SERVICE_HOST", "Kubernetes service host env"),
(r"KUBERNETES_PORT", "Kubernetes port env"),
(r"/var/run/secrets/kubernetes\.io", "Kubernetes secrets path"),
(r"serviceaccount/token", "Kubernetes service account token"),
(r"kube-system", "Kubernetes system namespace"),
# Container runtime
(r"containerd", "containerd runtime"),
(r"runc", "runc runtime"),
# Process namespace
(r"process\s+1\b.*(?:init|systemd|tini|dumb-init)", "Container init process"),
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for Docker/container indicators in response"""
findings = []
for pattern, description in self.container_indicators:
if re.search(pattern, response_body, re.IGNORECASE):
findings.append(description)
if findings:
# Docker socket or K8s secrets are critical
critical = any(k in f for f in findings for k in ["socket", "secrets", "token"])
confidence = 0.9 if critical else 0.7
return True, confidence, f"Container exposure: {', '.join(findings[:3])}"
# Privileged container detection
if context.get("is_capability_check"):
# Check for extra capabilities
cap_patterns = [
r"cap_sys_admin", r"cap_sys_ptrace", r"cap_net_admin",
r"cap_dac_override", r"cap_sys_rawio",
]
for pattern in cap_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.8, f"Privileged container: {pattern} capability detected"
# Mount namespace check
if re.search(r"/dev/(?:sda|xvda|nvme)\d*\s", response_body):
return True, 0.6, "Host block devices visible from container"
return False, 0.0, None
class ServerlessMisconfigTester(BaseTester):
"""Tester for Serverless Misconfiguration vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "serverless_misconfig"
self.env_patterns = [
# AWS Lambda
(r"AWS_LAMBDA_FUNCTION_NAME\s*[=:]\s*(\S+)", "Lambda function name"),
(r"AWS_SECRET_ACCESS_KEY\s*[=:]\s*(\S+)", "Lambda AWS secret key"),
(r"AWS_SESSION_TOKEN\s*[=:]\s*(\S+)", "Lambda session token"),
(r"AWS_LAMBDA_LOG_GROUP_NAME", "Lambda log group"),
(r"_HANDLER\s*[=:]\s*(\S+)", "Lambda handler path"),
(r"LAMBDA_TASK_ROOT\s*[=:]\s*(\S+)", "Lambda task root"),
(r"AWS_EXECUTION_ENV\s*[=:]\s*(\S+)", "Lambda execution environment"),
# Google Cloud Functions
(r"FUNCTION_NAME\s*[=:]\s*(\S+)", "Cloud Function name"),
(r"GCLOUD_PROJECT\s*[=:]\s*(\S+)", "GCP project ID"),
(r"GOOGLE_CLOUD_PROJECT\s*[=:]\s*(\S+)", "GCP project"),
(r"GCP_PROJECT\s*[=:]\s*(\S+)", "GCP project"),
(r"FUNCTION_REGION\s*[=:]\s*(\S+)", "Cloud Function region"),
# Azure Functions
(r"FUNCTIONS_WORKER_RUNTIME\s*[=:]\s*(\S+)", "Azure Function runtime"),
(r"AzureWebJobsStorage\s*[=:]\s*(\S+)", "Azure storage connection string"),
(r"WEBSITE_SITE_NAME\s*[=:]\s*(\S+)", "Azure site name"),
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for serverless environment variable exposure"""
findings = []
for pattern, description in self.env_patterns:
match = re.search(pattern, response_body)
if match:
value = match.group(1) if match.lastindex else ""
# Redact sensitive values
if "key" in description.lower() or "token" in description.lower() or "secret" in description.lower():
display = f"{description} (REDACTED)"
else:
display = f"{description}: {value[:30]}"
findings.append(display)
if findings:
# Credentials are critical
critical = any(k in f for f in findings for k in ["secret", "token", "REDACTED"])
confidence = 0.95 if critical else 0.75
return True, confidence, f"Serverless misconfiguration: {', '.join(findings[:3])}"
# Function source code exposure
if context.get("is_source_request"):
source_indicators = [
r"exports\.handler\s*=", r"def lambda_handler\(",
r"def main\(req:", r"module\.exports",
r"def hello_http\(request\):",
]
for pattern in source_indicators:
if re.search(pattern, response_body):
return True, 0.8, "Serverless misconfiguration: function source code exposed"
# Invocation error details
error_patterns = [
r"\"errorType\"\s*:\s*\"(\w+)\"",
r"\"stackTrace\"\s*:\s*\[",
r"Runtime\.HandlerNotFound",
r"\"errorMessage\"\s*:\s*\".*(?:import|require|module)",
]
for pattern in error_patterns:
match = re.search(pattern, response_body)
if match:
return True, 0.7, f"Serverless misconfiguration: detailed error exposed ({match.group(0)[:60]})"
return False, 0.0, None
@@ -0,0 +1,388 @@
"""
NeuroSploit v3 - Data Exposure Vulnerability Testers
Testers for sensitive data exposure, information disclosure, API key exposure,
source code disclosure, backup file exposure, and version disclosure.
"""
import re
from typing import Tuple, Dict, Optional
from backend.core.vuln_engine.testers.base_tester import BaseTester
class SensitiveDataExposureTester(BaseTester):
"""Tester for Sensitive Data Exposure (PII leakage)"""
def __init__(self):
super().__init__()
self.name = "sensitive_data_exposure"
self.pii_patterns = [
# SSN (US)
(r"\b\d{3}-\d{2}-\d{4}\b", "SSN pattern"),
# Credit card numbers (Visa, MC, Amex, Discover)
(r"\b4\d{3}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b", "Visa card number"),
(r"\b5[1-5]\d{2}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b", "MasterCard number"),
(r"\b3[47]\d{2}[\s-]?\d{6}[\s-]?\d{5}\b", "Amex card number"),
(r"\b6(?:011|5\d{2})[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b", "Discover card number"),
# Email addresses in bulk (10+ suggests a data leak)
(r"[\w.+-]+@[\w-]+\.[\w.-]+", "email address"),
# Phone numbers (US format)
(r"\b\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}\b", "phone number"),
# Passport numbers
(r"\b[A-Z]\d{8}\b", "passport number pattern"),
# Private keys
(r"-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----", "private key"),
# Password hashes
(r"\$2[aby]?\$\d{1,2}\$[./A-Za-z0-9]{53}", "bcrypt hash"),
(r"\b[a-f0-9]{32}\b", "MD5 hash"),
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for PII patterns in response"""
if response_status >= 400:
return False, 0.0, None
findings = []
for pattern, description in self.pii_patterns:
matches = re.findall(pattern, response_body)
if matches:
# Private keys and password hashes are always significant
if "private key" in description:
return True, 0.95, f"Sensitive data exposure: {description} found in response"
if "bcrypt hash" in description:
return True, 0.9, f"Sensitive data exposure: {description} found in response"
# For patterns like emails, phone numbers - check for bulk exposure
if description in ["email address", "phone number"]:
if len(matches) >= 5:
findings.append(f"{len(matches)} {description}s")
elif description == "MD5 hash":
if len(matches) >= 3:
findings.append(f"{len(matches)} {description}es")
else:
findings.append(f"{description} ({matches[0][:20]}...)")
if findings:
confidence = min(0.9, 0.6 + 0.1 * len(findings))
return True, confidence, f"Sensitive data exposure: {', '.join(findings[:3])}"
return False, 0.0, None
class InformationDisclosureTester(BaseTester):
"""Tester for Information Disclosure vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "information_disclosure"
self.disclosure_patterns = [
# Server version headers
(r"Server:\s*(.+)", "header", "Server version"),
(r"X-Powered-By:\s*(.+)", "header", "Technology stack"),
(r"X-AspNet-Version:\s*(.+)", "header", "ASP.NET version"),
(r"X-AspNetMvc-Version:\s*(.+)", "header", "ASP.NET MVC version"),
]
self.body_patterns = [
# Path disclosure
(r"(?:/var/www|/home/\w+|/srv/|/opt/\w+|C:\\inetpub|C:\\Users\\\w+)[/\\]\S+", "Internal path"),
# Stack traces
(r"Traceback \(most recent call last\)", "Python stack trace"),
(r"at \w+\.\w+\([\w.]+:\d+\)", "Java stack trace"),
(r"(?:Fatal error|Warning|Notice):\s+.*\sin\s+/\S+\s+on line \d+", "PHP error with path"),
(r"Microsoft \.NET Framework Version:\d+", ".NET framework version"),
# Database info
(r"(?:MySQL|PostgreSQL|Oracle|MSSQL)\s+\d+\.\d+", "Database version"),
# Debug info
(r"(?:DEBUG|TRACE)\s*=\s*(?:true|True|1)", "Debug mode enabled"),
(r"(?:SECRET_KEY|DB_PASSWORD|API_SECRET)\s*[=:]\s*\S+", "Secret in debug output"),
# Internal IPs
(r"\b(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})\b", "Internal IP address"),
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for information disclosure in headers and body"""
findings = []
# Check headers
headers_str = "\n".join(f"{k}: {v}" for k, v in response_headers.items())
for pattern, location, description in self.disclosure_patterns:
match = re.search(pattern, headers_str, re.IGNORECASE)
if match:
findings.append(f"{description}: {match.group(1)[:50]}")
# Check body
for pattern, description in self.body_patterns:
match = re.search(pattern, response_body, re.IGNORECASE)
if match:
findings.append(f"{description}: {match.group(0)[:80]}")
if findings:
# Stack traces and secrets are higher severity
high_severity = any("stack trace" in f.lower() or "secret" in f.lower() or "debug" in f.lower() for f in findings)
confidence = 0.85 if high_severity else 0.7
return True, confidence, f"Information disclosure: {'; '.join(findings[:3])}"
return False, 0.0, None
class ApiKeyExposureTester(BaseTester):
"""Tester for API Key Exposure vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "api_key_exposure"
self.key_patterns = [
# AWS
(r"AKIA[0-9A-Z]{16}", "AWS Access Key"),
(r"(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY)\s*[=:]\s*[A-Za-z0-9/+=]{40}", "AWS Secret Key"),
# Google
(r"AIza[0-9A-Za-z\-_]{35}", "Google API Key"),
(r"ya29\.[0-9A-Za-z\-_]+", "Google OAuth Token"),
# Stripe
(r"sk_live_[0-9a-zA-Z]{24,}", "Stripe Secret Key"),
(r"pk_live_[0-9a-zA-Z]{24,}", "Stripe Publishable Key"),
(r"rk_live_[0-9a-zA-Z]{24,}", "Stripe Restricted Key"),
# GitHub
(r"gh[pousr]_[A-Za-z0-9_]{36,}", "GitHub Token"),
# Slack
(r"xox[baprs]-[0-9]{10,13}-[0-9]{10,13}[a-zA-Z0-9-]*", "Slack Token"),
# Twilio
(r"SK[0-9a-fA-F]{32}", "Twilio API Key"),
# SendGrid
(r"SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}", "SendGrid API Key"),
# Heroku
(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}", "Heroku API Key / UUID"),
# Generic patterns
(r"(?:api[_-]?key|apikey|api[_-]?secret)\s*[=:]\s*['\"]?([A-Za-z0-9_\-]{20,})['\"]?", "Generic API Key"),
(r"(?:access[_-]?token|auth[_-]?token)\s*[=:]\s*['\"]?([A-Za-z0-9_\-.]{20,})['\"]?", "Access Token"),
# Bearer tokens in JS
(r"['\"]Bearer\s+[A-Za-z0-9_\-\.]{20,}['\"]", "Hardcoded Bearer Token"),
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for API key patterns in response"""
findings = []
for pattern, description in self.key_patterns:
matches = re.findall(pattern, response_body)
if matches:
# Skip UUIDs unless in specific context (too many false positives)
if "UUID" in description:
continue
# Redact the actual key in evidence
sample = matches[0] if isinstance(matches[0], str) else matches[0]
redacted = sample[:8] + "..." + sample[-4:] if len(sample) > 12 else sample[:4] + "..."
findings.append(f"{description} ({redacted})")
if findings:
# AWS/Stripe secret keys are critical
critical = any(k in f for f in findings for k in ["AWS Secret", "Stripe Secret", "Secret Key"])
confidence = 0.95 if critical else 0.85
return True, confidence, f"API key exposure: {', '.join(findings[:3])}"
return False, 0.0, None
class SourceCodeDisclosureTester(BaseTester):
"""Tester for Source Code Disclosure vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "source_code_disclosure"
self.source_indicators = [
# Git
(r"\[core\]\s*\n\s*repositoryformatversion", "Git config exposed"),
(r"\[remote \"origin\"\]", "Git config with remote"),
(r"ref: refs/heads/", "Git HEAD reference exposed"),
# Source maps
(r"\"version\"\s*:\s*3,\s*\"sources\"", "JavaScript source map"),
(r"//[#@]\s*sourceMappingURL=", "Source map reference"),
# PHP source
(r"<\?php\s", "PHP source code"),
(r"<\?=", "PHP short tag source"),
# Python source
(r"^(?:import |from \w+ import |def \w+\(|class \w+)", "Python source code"),
# Java/JSP
(r"<%@?\s*page\s+", "JSP source code"),
(r"package\s+\w+\.\w+;", "Java package declaration"),
# Environment files
(r"(?:DB_PASSWORD|SECRET_KEY|DATABASE_URL)\s*=\s*\S+", "Environment file content"),
# Composer/package files with private repos
(r"\"require\".*\"private/", "Private package reference"),
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for source code indicators in response"""
if response_status >= 400:
return False, 0.0, None
for pattern, description in self.source_indicators:
match = re.search(pattern, response_body, re.MULTILINE)
if match:
# Git config is high confidence
if "Git" in description:
return True, 0.95, f"Source code disclosure: {description}"
# Environment file is critical
if "Environment" in description:
return True, 0.95, f"Source code disclosure: {description}"
# Source maps lower confidence (often intentional)
if "source map" in description.lower():
return True, 0.6, f"Source code disclosure: {description}"
return True, 0.8, f"Source code disclosure: {description}"
# Check for common source file access patterns
source_paths = [".git/config", ".env", ".htaccess", "web.config",
"wp-config.php", "config.php", "settings.py"]
for path in source_paths:
if path in payload and response_status == 200 and len(response_body) > 50:
return True, 0.7, f"Source code disclosure: {path} accessible"
return False, 0.0, None
class BackupFileExposureTester(BaseTester):
"""Tester for Backup File Exposure vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "backup_file_exposure"
self.file_signatures = [
# SQL dumps
(r"-- MySQL dump \d+", "MySQL database dump"),
(r"-- PostgreSQL database dump", "PostgreSQL database dump"),
(r"CREATE TABLE\s+[`\"]\w+[`\"]", "SQL DDL statements"),
(r"INSERT INTO\s+[`\"]\w+[`\"]", "SQL data dump"),
# Archive signatures (in text responses)
(r"PK\x03\x04", "ZIP archive"),
# Tar
(r"ustar\s", "TAR archive"),
# Config backups
(r"<\?xml.*<configuration>", "XML configuration backup"),
(r"server\s*\{[^}]*listen\s+\d+", "Nginx config backup"),
(r"<VirtualHost\s+", "Apache config backup"),
]
self.backup_extensions = [
".bak", ".backup", ".old", ".orig", ".save",
".swp", ".swo", ".tmp", ".temp", ".copy",
"~", ".sql", ".tar.gz", ".zip", ".dump",
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for backup file content in response"""
if response_status >= 400:
return False, 0.0, None
# Check file signatures
for pattern, description in self.file_signatures:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.9, f"Backup file exposure: {description} detected"
# Check if backup extension in request returned content
for ext in self.backup_extensions:
if ext in payload and response_status == 200 and len(response_body) > 100:
headers_lower = {k.lower(): v for k, v in response_headers.items()}
content_type = headers_lower.get("content-type", "").lower()
# Non-HTML responses to backup file requests are suspicious
if "text/html" not in content_type:
return True, 0.75, f"Backup file exposure: {ext} file served ({content_type})"
# HTML response but contains code-like content
if re.search(r"(?:function |class |import |require\(|define\()", response_body):
return True, 0.7, f"Backup file exposure: {ext} file contains source code"
return False, 0.0, None
class VersionDisclosureTester(BaseTester):
"""Tester for Version Disclosure mapping to known CVEs"""
def __init__(self):
super().__init__()
self.name = "version_disclosure"
# Software versions with known critical CVEs
self.vulnerable_versions = {
r"Apache/2\.4\.49\b": "CVE-2021-41773 (path traversal)",
r"Apache/2\.4\.50\b": "CVE-2021-42013 (path traversal bypass)",
r"nginx/1\.(?:[0-9]|1[0-7])\.\d+": "Potential nginx < 1.18 vulnerabilities",
r"PHP/(?:5\.\d|7\.[0-3])\.\d+": "Outdated PHP version with known CVEs",
r"OpenSSL/1\.0\.\d": "OpenSSL 1.0.x - multiple known CVEs",
r"jQuery/(?:1\.\d|2\.\d|3\.[0-4])\.\d+": "jQuery < 3.5 - XSS via htmlPrefilter",
r"WordPress/(?:[1-4]\.\d|5\.[0-7])": "Outdated WordPress version",
r"Drupal/(?:[1-7]\.\d|8\.[0-5])": "Outdated Drupal version",
r"Rails/(?:[1-4]\.\d|5\.[01])": "Outdated Rails version",
r"Spring Framework/(?:[1-4]\.\d|5\.[0-2])": "Outdated Spring version",
r"Express/(?:[1-3]\.\d|4\.(?:1[0-6]))": "Outdated Express.js version",
r"Django/(?:1\.\d|2\.[01]|3\.0)": "Outdated Django version",
r"Log4j.(?:2\.(?:0|1[0-4])\.\d)": "CVE-2021-44228 (Log4Shell)",
r"Tomcat/(?:[1-8]\.\d|9\.[0-3]\d\.\d)": "Potentially outdated Tomcat",
r"IIS/(?:[1-9]\.0|10\.0)": "IIS version disclosure",
}
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for version strings mapping to known CVEs"""
# Combine headers and body for scanning
headers_str = "\n".join(f"{k}: {v}" for k, v in response_headers.items())
full_text = headers_str + "\n" + response_body
findings = []
for pattern, cve_info in self.vulnerable_versions.items():
match = re.search(pattern, full_text, re.IGNORECASE)
if match:
version_str = match.group(0)
findings.append(f"{version_str} - {cve_info}")
if findings:
# Known CVEs are high confidence
has_cve = any("CVE-" in f for f in findings)
confidence = 0.9 if has_cve else 0.7
return True, confidence, f"Version disclosure: {'; '.join(findings[:3])}"
# Generic version disclosure in Server header
headers_lower = {k.lower(): v for k, v in response_headers.items()}
server = headers_lower.get("server", "")
if re.search(r"/\d+\.\d+", server):
return True, 0.5, f"Version disclosure in Server header: {server}"
return False, 0.0, None
@@ -0,0 +1,356 @@
"""
NeuroSploit v3 - File Access Vulnerability Testers
Testers for LFI, RFI, Path Traversal, XXE, File Upload
"""
import re
from typing import Tuple, Dict, Optional
from backend.core.vuln_engine.testers.base_tester import BaseTester
class LFITester(BaseTester):
"""Tester for Local File Inclusion"""
def __init__(self):
super().__init__()
self.name = "lfi"
self.file_signatures = {
# Linux files
r"root:.*:0:0:": "/etc/passwd",
r"\[boot loader\]": "Windows boot.ini",
r"\[operating systems\]": "Windows boot.ini",
r"# /etc/hosts": "/etc/hosts",
r"localhost": "/etc/hosts",
r"\[global\]": "Samba config",
r"include.*php": "PHP config",
# Windows files
r"\[extensions\]": "Windows win.ini",
r"for 16-bit app support": "Windows system.ini",
}
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for LFI indicators"""
for pattern, file_name in self.file_signatures.items():
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.95, f"LFI confirmed: {file_name} content detected"
# Check for path in error messages
path_patterns = [
r"failed to open stream.*No such file",
r"include\(.*\): failed to open stream",
r"Warning.*file_get_contents",
r"fopen\(.*\): failed"
]
for pattern in path_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.6, "LFI indicator: File operation error with path"
return False, 0.0, None
class RFITester(BaseTester):
"""Tester for Remote File Inclusion"""
def __init__(self):
super().__init__()
self.name = "rfi"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for RFI indicators"""
# Check if our remote content was included
if "neurosploit_rfi_test" in response_body:
return True, 0.95, "RFI confirmed: Remote content executed"
# Check for URL-related errors
rfi_errors = [
r"failed to open stream: HTTP request failed",
r"allow_url_include",
r"URL file-access is disabled"
]
for pattern in rfi_errors:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.5, f"RFI indicator: {pattern}"
return False, 0.0, None
class PathTraversalTester(BaseTester):
"""Tester for Path Traversal"""
def __init__(self):
super().__init__()
self.name = "path_traversal"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for path traversal indicators"""
# Same as LFI essentially
file_contents = [
r"root:.*:0:0:",
r"\[boot loader\]",
r"# /etc/",
r"127\.0\.0\.1.*localhost"
]
for pattern in file_contents:
if re.search(pattern, response_body):
return True, 0.9, f"Path traversal successful: File content detected"
return False, 0.0, None
class XXETester(BaseTester):
"""Tester for XML External Entity Injection"""
def __init__(self):
super().__init__()
self.name = "xxe"
def build_request(self, endpoint, payload: str) -> Tuple[str, Dict, Dict, Optional[str]]:
"""Build XXE request with XML body"""
headers = {
"User-Agent": "NeuroSploit/3.0",
"Content-Type": "application/xml"
}
return endpoint.url, {}, headers, payload
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for XXE indicators"""
# File content indicators
xxe_indicators = [
r"root:.*:0:0:",
r"\[boot loader\]",
r"# /etc/hosts",
r"<!ENTITY",
]
for pattern in xxe_indicators:
if re.search(pattern, response_body):
return True, 0.9, f"XXE confirmed: External entity processed"
# Error indicators
xxe_errors = [
r"XML parsing error",
r"External entity",
r"DOCTYPE.*ENTITY",
r"libxml"
]
for pattern in xxe_errors:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.6, f"XXE indicator: XML error with entity reference"
return False, 0.0, None
class FileUploadTester(BaseTester):
"""Tester for Arbitrary File Upload"""
def __init__(self):
super().__init__()
self.name = "file_upload"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for file upload vulnerability indicators"""
# Check for successful upload indicators
if response_status in [200, 201]:
success_indicators = [
"uploaded successfully",
"file saved",
"upload complete",
'"success"\\s*:\\s*true',
'"status"\\s*:\\s*"ok"'
]
for pattern in success_indicators:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.7, "File uploaded successfully - verify execution"
# Check for path disclosure in response
if re.search(r'["\']?(?:path|url|file)["\']?\s*:\s*["\'][^"\']+\.(php|asp|jsp)', response_body, re.IGNORECASE):
return True, 0.8, "Executable file path returned - possible RCE"
return False, 0.0, None
class ArbitraryFileReadTester(BaseTester):
"""Tester for Arbitrary File Read vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "arbitrary_file_read"
self.sensitive_file_patterns = {
# /etc/passwd format
r"root:.*:0:0:": "/etc/passwd",
r"daemon:.*:\d+:\d+:": "/etc/passwd",
r"nobody:.*:\d+:\d+:": "/etc/passwd",
# .env file patterns
r"(?:DB_PASSWORD|DATABASE_URL|SECRET_KEY|API_KEY|APP_SECRET)\s*=": ".env file",
r"(?:AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY)\s*=": ".env file (AWS credentials)",
# SSH key headers
r"-----BEGIN (?:RSA |DSA |EC |OPENSSH )?PRIVATE KEY-----": "SSH/TLS private key",
r"-----BEGIN CERTIFICATE-----": "TLS certificate",
# Shadow file
r"root:\$[0-9a-z]+\$": "/etc/shadow",
# Config files
r"<\?php.*\$db": "PHP config with DB credentials",
}
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for sensitive file contents in response"""
for pattern, file_desc in self.sensitive_file_patterns.items():
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.95, f"Arbitrary file read confirmed: {file_desc} content detected"
# Check for base64-encoded sensitive content
base64_pattern = re.findall(r'[A-Za-z0-9+/]{40,}={0,2}', response_body)
for b64_match in base64_pattern[:5]: # Check first 5 matches
try:
import base64
decoded = base64.b64decode(b64_match).decode('utf-8', errors='ignore')
if re.search(r"root:.*:0:0:", decoded) or re.search(r"-----BEGIN.*PRIVATE KEY-----", decoded):
return True, 0.9, "Arbitrary file read: Base64-encoded sensitive file content"
except Exception:
pass
return False, 0.0, None
class ArbitraryFileDeleteTester(BaseTester):
"""Tester for Arbitrary File Delete vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "arbitrary_file_delete"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for successful file deletion indicators"""
body_lower = response_body.lower()
# Check for explicit deletion success messages
delete_success_patterns = [
r"file\s+(?:has been\s+)?(?:deleted|removed)\s+successfully",
r"(?:deleted|removed)\s+successfully",
r'"success"\s*:\s*true.*(?:delet|remov)',
r'"status"\s*:\s*"(?:deleted|removed)"',
r'"message"\s*:\s*".*(?:deleted|removed).*"',
]
for pattern in delete_success_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.85, "Arbitrary file delete: Deletion success confirmed in response"
# Check for 200/204 on DELETE request with traversal path
traversal_indicators = ["../", "..\\", "%2e%2e", "..%2f", "..%5c"]
has_traversal = any(t in payload.lower() for t in traversal_indicators)
if has_traversal:
if response_status == 204:
return True, 0.8, "Arbitrary file delete: 204 No Content after path traversal delete"
if response_status == 200:
return True, 0.7, "Arbitrary file delete: 200 OK after path traversal delete request"
# Check for file-not-found on subsequent access (context-based)
if context.get("follow_up_status") == 404:
return True, 0.85, "Arbitrary file delete: File not found after deletion request"
return False, 0.0, None
class ZipSlipTester(BaseTester):
"""Tester for Zip Slip (path traversal in archive extraction)"""
def __init__(self):
super().__init__()
self.name = "zip_slip"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for path traversal in archive extraction"""
body_lower = response_body.lower()
# Check for traversal path acceptance in response
traversal_patterns = [
r"\.\./\.\./\.\./",
r"\.\.\\\.\.\\\.\.\\",
r"%2e%2e%2f",
r"%2e%2e/",
]
for pattern in traversal_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
# Traversal path echoed in response
if response_status in [200, 201]:
return True, 0.8, "Zip Slip: Path traversal sequence accepted in archive extraction"
# Check for successful extraction with traversal payload
if response_status in [200, 201]:
extraction_success = [
r"extract(?:ed|ion)\s+(?:successful|complete)",
r"(?:file|archive)\s+(?:uploaded|processed)\s+successfully",
r'"extracted"\s*:\s*true',
r'"files"\s*:\s*\[.*\.\./.*\]',
]
for pattern in extraction_success:
if re.search(pattern, response_body, re.IGNORECASE):
if any(t in payload for t in ["../", "..\\", "%2e%2e"]):
return True, 0.85, "Zip Slip: Archive with traversal paths extracted successfully"
# Check for file written outside expected directory
overwrite_indicators = [
r"(?:overwr(?:ote|itten)|replaced)\s+.*(?:/etc/|/var/|/tmp/|C:\\)",
r"(?:created|wrote)\s+.*\.\./",
]
for pattern in overwrite_indicators:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.9, "Zip Slip: File written outside extraction directory"
return False, 0.0, None
@@ -0,0 +1,509 @@
"""
NeuroSploit v3 - Infrastructure Vulnerability Testers
Testers for Security Headers, SSL/TLS, HTTP Methods
"""
import re
from typing import Tuple, Dict, Optional
from backend.core.vuln_engine.testers.base_tester import BaseTester
class SecurityHeadersTester(BaseTester):
"""Tester for Missing Security Headers"""
def __init__(self):
super().__init__()
self.name = "security_headers"
self.required_headers = {
"Strict-Transport-Security": "HSTS not configured",
"X-Content-Type-Options": "X-Content-Type-Options not set",
"X-Frame-Options": "X-Frame-Options not set",
"Content-Security-Policy": "CSP not configured",
"X-XSS-Protection": "X-XSS-Protection not set (legacy but still useful)",
"Referrer-Policy": "Referrer-Policy not configured"
}
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for missing security headers"""
missing = []
headers_lower = {k.lower(): v for k, v in response_headers.items()}
for header, message in self.required_headers.items():
if header.lower() not in headers_lower:
missing.append(message)
# Check for weak CSP
csp = headers_lower.get("content-security-policy", "")
if csp:
weak_csp = []
if "unsafe-inline" in csp:
weak_csp.append("unsafe-inline")
if "unsafe-eval" in csp:
weak_csp.append("unsafe-eval")
if "*" in csp:
weak_csp.append("wildcard sources")
if weak_csp:
missing.append(f"Weak CSP: {', '.join(weak_csp)}")
if missing:
confidence = min(0.3 + len(missing) * 0.1, 0.8)
return True, confidence, f"Missing/weak headers: {'; '.join(missing[:3])}"
return False, 0.0, None
class SSLTester(BaseTester):
"""Tester for SSL/TLS Issues"""
def __init__(self):
super().__init__()
self.name = "ssl_issues"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for SSL/TLS issues"""
issues = []
# Check HSTS
hsts = response_headers.get("Strict-Transport-Security", "")
if not hsts:
issues.append("HSTS not enabled")
else:
# Check HSTS max-age
max_age_match = re.search(r'max-age=(\d+)', hsts)
if max_age_match:
max_age = int(max_age_match.group(1))
if max_age < 31536000: # Less than 1 year
issues.append(f"HSTS max-age too short: {max_age}s")
if "includeSubDomains" not in hsts:
issues.append("HSTS missing includeSubDomains")
# Check for HTTP resources on HTTPS page
if "https://" in (context.get("url", "") or ""):
http_resources = re.findall(r'(?:src|href)=["\']http://[^"\']+', response_body)
if http_resources:
issues.append(f"Mixed content: {len(http_resources)} HTTP resources")
if issues:
return True, 0.6, f"SSL/TLS issues: {'; '.join(issues)}"
return False, 0.0, None
class HTTPMethodsTester(BaseTester):
"""Tester for Dangerous HTTP Methods"""
def __init__(self):
super().__init__()
self.name = "http_methods"
self.dangerous_methods = ["TRACE", "TRACK", "PUT", "DELETE", "CONNECT"]
def build_request(self, endpoint, payload: str) -> Tuple[str, Dict, Dict, Optional[str]]:
"""Build OPTIONS request to check allowed methods"""
headers = {
"User-Agent": "NeuroSploit/3.0"
}
# payload is the HTTP method to test
return endpoint.url, {}, headers, None
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for dangerous HTTP methods"""
# Check Allow header from OPTIONS response
allow = response_headers.get("Allow", "")
dangerous_found = []
for method in self.dangerous_methods:
if method in allow.upper():
dangerous_found.append(method)
# TRACE method enables XST attacks
if "TRACE" in dangerous_found or "TRACK" in dangerous_found:
return True, 0.7, f"Dangerous methods enabled: {', '.join(dangerous_found)} (XST risk)"
if dangerous_found:
return True, 0.5, f"Potentially dangerous methods: {', '.join(dangerous_found)}"
# Check if specific method test succeeded
if payload.upper() in self.dangerous_methods:
if response_status == 200:
return True, 0.6, f"{payload} method accepted"
return False, 0.0, None
class DirectoryListingTester(BaseTester):
"""Tester for Directory Listing exposure"""
def __init__(self):
super().__init__()
self.name = "directory_listing"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for directory listing patterns"""
if response_status == 200:
listing_patterns = [
r"<title>Index of\s*/",
r"Index of\s*/",
r"<h1>Index of",
r"Directory listing for\s*/",
r"<title>Directory listing",
r'<a\s+href="\.\./">\.\./</a>',
r"Parent Directory</a>",
r'\[DIR\]',
r'\[TXT\]',
r"<pre>.*<a href=",
]
for pattern in listing_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.9, "Directory listing: Server directory contents exposed"
# Check for Apache/Nginx-specific listing
if re.search(r'<address>Apache/[\d.]+ .* Server at', response_body, re.IGNORECASE):
if "Index of" in response_body:
return True, 0.95, "Directory listing: Apache directory listing enabled"
return False, 0.0, None
class DebugModeTester(BaseTester):
"""Tester for Debug Mode/Page exposure"""
def __init__(self):
super().__init__()
self.name = "debug_mode"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for debug pages, stack traces with source paths"""
# Werkzeug/Flask debugger
werkzeug_patterns = [
r"Werkzeug\s+Debugger",
r"werkzeug\.debug",
r"<div class=\"debugger\">",
r"The debugger caught an exception",
r"__debugger__",
]
for pattern in werkzeug_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.95, "Debug mode: Werkzeug interactive debugger exposed (RCE risk)"
# Laravel debug
laravel_patterns = [
r"Whoops!.*Laravel",
r"Ignition\s",
r"vendor/laravel",
r"Laravel.*Exception",
r"app/Http/Controllers",
]
for pattern in laravel_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.9, "Debug mode: Laravel debug page exposed"
# Django debug
django_patterns = [
r"You\'re seeing this error because you have <code>DEBUG = True</code>",
r"Django Version:",
r"Traceback.*django",
r"INSTALLED_APPS",
r"settings\.py",
]
for pattern in django_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.9, "Debug mode: Django debug page exposed"
# Generic stack traces with source paths
stack_trace_patterns = [
r"(?:File|at)\s+[\"']?(?:/[a-z]+/|C:\\)[^\s\"']+\.(?:py|php|rb|js|java|go)\b",
r"Traceback \(most recent call last\)",
r"Stack trace:.*(?:\.php|\.py|\.rb|\.java)",
r"(?:Error|Exception)\s+in\s+(?:/[a-z]+/|C:\\)[^\s]+:\d+",
]
for pattern in stack_trace_patterns:
if re.search(pattern, response_body, re.IGNORECASE | re.DOTALL):
return True, 0.8, "Debug mode: Stack trace with source file paths exposed"
# ASP.NET detailed errors
if re.search(r"Server Error in '/' Application", response_body):
return True, 0.85, "Debug mode: ASP.NET detailed error page exposed"
return False, 0.0, None
class ExposedAdminPanelTester(BaseTester):
"""Tester for Publicly Accessible Admin Panel"""
def __init__(self):
super().__init__()
self.name = "exposed_admin_panel"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for admin login pages accessible publicly"""
if response_status == 200:
admin_panel_patterns = [
(r"(?:admin|administrator)\s+(?:login|panel|dashboard|console)", "admin login page"),
(r"<title>[^<]*(?:admin|dashboard|control\s*panel|cms)[^<]*</title>", "admin title"),
(r"wp-login\.php", "WordPress login"),
(r"wp-admin", "WordPress admin"),
(r"/admin/login", "admin login endpoint"),
(r"phpmyadmin", "phpMyAdmin"),
(r"adminer\.php", "Adminer"),
(r"cPanel", "cPanel"),
(r"Webmin", "Webmin"),
(r"Plesk", "Plesk"),
(r"joomla.*administrator", "Joomla admin"),
(r"drupal.*user/login", "Drupal admin login"),
]
for pattern, panel_name in admin_panel_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.75, f"Exposed admin panel: {panel_name} accessible publicly"
return False, 0.0, None
class ExposedApiDocsTester(BaseTester):
"""Tester for Publicly Accessible API Documentation"""
def __init__(self):
super().__init__()
self.name = "exposed_api_docs"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for Swagger/OpenAPI documentation pages"""
if response_status == 200:
api_docs_patterns = [
(r"swagger-ui", "Swagger UI"),
(r'"swagger"\s*:\s*"[0-9.]+"', "Swagger spec"),
(r'"openapi"\s*:\s*"[0-9.]+"', "OpenAPI spec"),
(r"swagger-ui-bundle\.js", "Swagger UI bundle"),
(r"<title>Swagger UI</title>", "Swagger UI page"),
(r"redoc", "ReDoc API docs"),
(r"api-docs", "API documentation"),
(r"graphiql", "GraphiQL interface"),
(r"GraphQL Playground", "GraphQL Playground"),
(r'"paths"\s*:\s*\{', "OpenAPI paths object"),
(r'"info"\s*:\s*\{.*"title"\s*:', "OpenAPI info object"),
]
for pattern, doc_type in api_docs_patterns:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.8, f"Exposed API docs: {doc_type} publicly accessible"
# Check content type for JSON API specs
content_type = response_headers.get("Content-Type", "")
if "json" in content_type.lower():
if re.search(r'"paths"\s*:\s*\{.*"(?:get|post|put|delete)"', response_body, re.DOTALL):
return True, 0.85, "Exposed API docs: OpenAPI/Swagger JSON specification exposed"
return False, 0.0, None
class InsecureCookieFlagsTester(BaseTester):
"""Tester for Missing Secure/HttpOnly/SameSite Cookie Flags"""
def __init__(self):
super().__init__()
self.name = "insecure_cookie_flags"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check Set-Cookie headers for missing security flags"""
# Collect all Set-Cookie headers
set_cookie_values = []
for key, value in response_headers.items():
if key.lower() == "set-cookie":
if isinstance(value, list):
set_cookie_values.extend(value)
else:
set_cookie_values.append(value)
if not set_cookie_values:
return False, 0.0, None
issues = []
for cookie in set_cookie_values:
cookie_lower = cookie.lower()
cookie_name = cookie.split("=")[0].strip()
# Session cookies are more critical
is_session = any(
s in cookie_name.lower()
for s in ["session", "sess", "sid", "token", "auth", "jwt", "csrf"]
)
missing_flags = []
if "secure" not in cookie_lower:
missing_flags.append("Secure")
if "httponly" not in cookie_lower:
missing_flags.append("HttpOnly")
if "samesite" not in cookie_lower:
missing_flags.append("SameSite")
if missing_flags:
severity = "session cookie" if is_session else "cookie"
issues.append(f"{cookie_name} ({severity}): missing {', '.join(missing_flags)}")
if issues:
confidence = 0.8 if any("session cookie" in i for i in issues) else 0.6
return True, confidence, f"Insecure cookie flags: {'; '.join(issues[:3])}"
return False, 0.0, None
class HttpSmugglingTester(BaseTester):
"""Tester for HTTP Request Smuggling (CL/TE discrepancy)"""
def __init__(self):
super().__init__()
self.name = "http_smuggling"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for CL/TE discrepancy indicators"""
# Check for request smuggling indicators
smuggling_indicators = [
# Different response than expected
(r"400 Bad Request.*(?:Content-Length|Transfer-Encoding)", "CL/TE parsing error"),
(r"(?:invalid|malformed)\s+(?:chunk|transfer.encoding)", "chunked encoding error"),
]
for pattern, desc in smuggling_indicators:
if re.search(pattern, response_body, re.IGNORECASE | re.DOTALL):
return True, 0.7, f"HTTP smuggling indicator: {desc}"
# Check for dual Transfer-Encoding handling
te_header = response_headers.get("Transfer-Encoding", "")
cl_header = response_headers.get("Content-Length", "")
if te_header and cl_header:
return True, 0.75, "HTTP smuggling: Both Transfer-Encoding and Content-Length in response"
# Check for timeout-based detection (context)
if context.get("response_time_ms", 0) > 10000:
if "transfer-encoding" in payload.lower() or "content-length" in payload.lower():
return True, 0.6, "HTTP smuggling: Abnormal response delay with CL/TE payload"
# Check for response desync indicators
if response_status == 0 or context.get("connection_reset"):
return True, 0.65, "HTTP smuggling: Connection reset/timeout with smuggling payload"
return False, 0.0, None
class CachePoisoningTester(BaseTester):
"""Tester for Web Cache Poisoning"""
def __init__(self):
super().__init__()
self.name = "cache_poisoning"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for cached response with injected unkeyed input"""
if response_status == 200:
# Check for cache headers indicating response was cached
cache_indicators = {
"X-Cache": response_headers.get("X-Cache", ""),
"CF-Cache-Status": response_headers.get("CF-Cache-Status", ""),
"Age": response_headers.get("Age", ""),
"X-Varnish": response_headers.get("X-Varnish", ""),
}
is_cached = False
for header, value in cache_indicators.items():
if value:
if any(hit in value.upper() for hit in ["HIT", "STALE"]):
is_cached = True
break
if header == "Age" and int(value or 0) > 0:
is_cached = True
break
# Check if our unkeyed input is reflected in the cached response
if is_cached or response_headers.get("Cache-Control", ""):
# Common unkeyed headers that might be reflected
unkeyed_indicators = [
r"X-Forwarded-Host", r"X-Forwarded-Scheme",
r"X-Original-URL", r"X-Rewrite-URL",
]
if payload in response_body:
if is_cached:
return True, 0.9, "Cache poisoning: Injected unkeyed input reflected in cached response"
else:
return True, 0.7, "Cache poisoning: Unkeyed input reflected - verify caching"
# Check for Vary header missing expected values
vary = response_headers.get("Vary", "")
cache_control = response_headers.get("Cache-Control", "")
if "no-store" not in cache_control and "private" not in cache_control:
if payload in response_body:
return True, 0.6, "Cache poisoning potential: Input reflected in cacheable response"
return False, 0.0, None
@@ -0,0 +1,443 @@
"""
NeuroSploit v3 - Injection Vulnerability Testers
Testers for XSS, SQL Injection, Command Injection, SSTI, etc.
"""
import re
from typing import Tuple, Dict, Optional
from backend.core.vuln_engine.testers.base_tester import BaseTester
class XSSReflectedTester(BaseTester):
"""Tester for Reflected XSS vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "xss_reflected"
# Unique markers for detection
self.markers = [
"neurosploit",
"xsstest123",
"alert(1)"
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check if XSS payload is reflected in response"""
if response_status >= 400:
return False, 0.0, None
# Check if payload is reflected
if payload in response_body:
# Use context-aware analysis to determine execution position
from backend.core.xss_context_analyzer import analyze_xss_execution_context
ctx = analyze_xss_execution_context(response_body, payload)
if ctx["executable"]:
return True, 0.95, f"XSS payload in auto-executing context: {ctx['detail']}"
elif ctx["interactive"]:
return True, 0.85, f"XSS payload in interactive context: {ctx['detail']}"
# Reflected but not in executable position
return True, 0.5, f"XSS payload reflected but {ctx['context']}: {ctx['detail']}"
# Check for partial reflection (script tags, etc.)
for marker in self.markers:
if marker in payload and marker in response_body:
return True, 0.6, f"XSS marker '{marker}' found in response"
return False, 0.0, None
class XSSStoredTester(BaseTester):
"""Tester for Stored XSS vulnerabilities.
Supports two-phase verification:
Phase 1: analyze_response() - Check if submission succeeded (data stored)
Phase 2: analyze_display_response() - Check if payload executes on display page
"""
def __init__(self):
super().__init__()
self.name = "xss_stored"
self.storage_indicators = [
"success", "created", "saved", "posted", "submitted",
"thank", "comment", "added", "published", "updated",
"your comment", "your post", "your message",
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Phase 1: Check if payload was likely stored.
Returns confidence 0.3-0.5 for storage-only confirmation.
Full confirmation requires Phase 2 (analyze_display_response).
"""
body_lower = response_body.lower()
# Redirect after POST is a common form submission pattern
if response_status in [301, 302, 303]:
return True, 0.4, "Redirect after submission - payload likely stored"
if response_status in [200, 201]:
# Check for storage success indicators
for indicator in self.storage_indicators:
if indicator in body_lower:
return True, 0.4, f"Storage indicator found: '{indicator}'"
# Check if payload is reflected in the same response (immediate display)
if payload in response_body:
dangerous = [
"<script", "onerror=", "onload=", "onclick=", "onfocus=",
"onmouseover=", "<svg", "<img", "<iframe", "javascript:"
]
payload_lower = payload.lower()
for ctx in dangerous:
if ctx in payload_lower:
return True, 0.8, f"Stored XSS: payload reflected in dangerous context ({ctx})"
return True, 0.6, "Payload reflected in submission response"
# POST returning 200 often means submission accepted
if response_status == 200 and context.get("method") == "POST":
return True, 0.3, "POST returned 200 - submission possibly accepted"
return False, 0.0, None
def analyze_display_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Phase 2: Verify payload executes on the display page.
Called after navigating to the page where stored content is rendered.
"""
if response_status >= 400:
return False, 0.0, None
# Check if payload exists unescaped in display page
if payload in response_body:
# Use context-aware analysis to determine execution position
from backend.core.xss_context_analyzer import analyze_xss_execution_context
ctx = analyze_xss_execution_context(response_body, payload)
if ctx["executable"]:
return True, 0.95, f"Stored XSS confirmed: {ctx['detail']}"
elif ctx["interactive"]:
return True, 0.90, f"Stored XSS (interaction required): {ctx['detail']}"
# Payload present but not executable
return True, 0.5, f"Stored payload on display page but {ctx['context']}: {ctx['detail']}"
# Check for core execution markers even if full payload is modified
core_markers = [
"alert(1)", "alert(document.domain)", "onerror=alert",
"onload=alert", "onfocus=alert", "ontoggle=alert",
]
body_lower = response_body.lower()
for marker in core_markers:
if marker in payload.lower() and marker in body_lower:
return True, 0.85, f"Stored XSS: execution marker '{marker}' found on display page"
return False, 0.0, None
class XSSDomTester(BaseTester):
"""Tester for DOM-based XSS vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "xss_dom"
self.dom_sinks = [
"innerHTML", "outerHTML", "document.write", "document.writeln",
"eval(", "setTimeout(", "setInterval(", "location.href",
"location.assign", "location.replace"
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for DOM XSS indicators"""
# Look for dangerous DOM sinks in JavaScript
for sink in self.dom_sinks:
pattern = rf'{sink}[^;]*(?:location|document\.URL|document\.referrer|window\.name)'
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.7, f"Potential DOM XSS sink found: {sink}"
# Check if URL parameters are used in JavaScript
if re.search(r'(?:location\.search|location\.hash|document\.URL)', response_body):
if any(sink in response_body for sink in self.dom_sinks):
return True, 0.6, "URL input flows to DOM sink"
return False, 0.0, None
class SQLiErrorTester(BaseTester):
"""Tester for Error-based SQL Injection"""
def __init__(self):
super().__init__()
self.name = "sqli_error"
self.error_patterns = [
# MySQL
r"SQL syntax.*MySQL", r"Warning.*mysql_", r"MySQLSyntaxErrorException",
r"valid MySQL result", r"check the manual that corresponds to your MySQL",
# PostgreSQL
r"PostgreSQL.*ERROR", r"Warning.*pg_", r"valid PostgreSQL result",
r"Npgsql\.", r"PG::SyntaxError",
# SQL Server
r"Driver.*SQL[\-\_\ ]*Server", r"OLE DB.*SQL Server",
r"(\W|\A)SQL Server.*Driver", r"Warning.*mssql_",
r"(\W|\A)SQL Server.*[0-9a-fA-F]{8}", r"Microsoft SQL Native Client error",
# Oracle
r"\bORA-[0-9][0-9][0-9][0-9]", r"Oracle error", r"Oracle.*Driver",
r"Warning.*oci_", r"Warning.*ora_",
# SQLite
r"SQLite/JDBCDriver", r"SQLite\.Exception", r"System\.Data\.SQLite\.SQLiteException",
r"Warning.*sqlite_", r"Warning.*SQLite3::",
# Generic
r"SQL syntax.*", r"syntax error.*SQL", r"unclosed quotation mark",
r"quoted string not properly terminated"
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for SQL error messages in response"""
for pattern in self.error_patterns:
match = re.search(pattern, response_body, re.IGNORECASE)
if match:
return True, 0.9, f"SQL error detected: {match.group(0)[:100]}"
return False, 0.0, None
class SQLiUnionTester(BaseTester):
"""Tester for Union-based SQL Injection"""
def __init__(self):
super().__init__()
self.name = "sqli_union"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for union-based SQLi indicators"""
# Look for injected data markers
union_markers = ["neurosploit", "uniontest", "concat(", "version()"]
for marker in union_markers:
if marker in payload.lower() and marker in response_body.lower():
return True, 0.8, f"Union injection marker '{marker}' found in response"
# Check for database version strings
version_patterns = [
r"MySQL.*\d+\.\d+", r"PostgreSQL.*\d+\.\d+",
r"Microsoft SQL Server.*\d+", r"Oracle.*\d+",
r"\d+\.\d+\.\d+-MariaDB"
]
for pattern in version_patterns:
if re.search(pattern, response_body):
return True, 0.7, "Database version string found - possible union SQLi"
return False, 0.0, None
class SQLiBlindTester(BaseTester):
"""Tester for Boolean-based Blind SQL Injection"""
def __init__(self):
super().__init__()
self.name = "sqli_blind"
self.baseline_length = None
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for boolean-based blind SQLi"""
# This requires comparing responses - simplified check
response_length = len(response_body)
# Check for significant difference in response
if "baseline_length" in context:
diff = abs(response_length - context["baseline_length"])
if diff > 100: # Significant difference
return True, 0.6, f"Response length differs by {diff} bytes - possible blind SQLi"
# Check for conditional responses
if "1=1" in payload and response_status == 200:
return True, 0.5, "True condition returned 200 - possible blind SQLi"
return False, 0.0, None
class SQLiTimeTester(BaseTester):
"""Tester for Time-based Blind SQL Injection"""
def __init__(self):
super().__init__()
self.name = "sqli_time"
def check_timeout_vulnerability(self, vuln_type: str) -> bool:
"""Time-based SQLi is indicated by timeout"""
return True
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Time-based detection relies on timeout"""
# Response time analysis would be done in the engine
return False, 0.0, None
class CommandInjectionTester(BaseTester):
"""Tester for OS Command Injection"""
def __init__(self):
super().__init__()
self.name = "command_injection"
self.command_outputs = [
# Linux
r"root:.*:0:0:", r"bin:.*:1:1:", # /etc/passwd
r"uid=\d+.*gid=\d+", # id command
r"Linux.*\d+\.\d+\.\d+", # uname
r"total \d+.*drwx", # ls -la
# Windows
r"Volume Serial Number",
r"Directory of [A-Z]:\\",
r"Windows.*\[Version",
r"Microsoft Windows"
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for command execution evidence"""
for pattern in self.command_outputs:
match = re.search(pattern, response_body, re.IGNORECASE)
if match:
return True, 0.95, f"Command output detected: {match.group(0)[:100]}"
# Check for our marker
if "neurosploit" in payload and "neurosploit" in response_body:
return True, 0.8, "Command injection marker echoed"
return False, 0.0, None
class SSTITester(BaseTester):
"""Tester for Server-Side Template Injection"""
def __init__(self):
super().__init__()
self.name = "ssti"
# Mathematical expressions that prove code execution
self.math_results = {
"{{7*7}}": "49",
"${7*7}": "49",
"#{7*7}": "49",
"<%= 7*7 %>": "49",
"{{7*'7'}}": "7777777", # Jinja2
}
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for SSTI indicators"""
# Check mathematical results
for expr, result in self.math_results.items():
if expr in payload and result in response_body:
return True, 0.95, f"SSTI confirmed: {expr} = {result}"
# Check for template errors
template_errors = [
r"TemplateSyntaxError", r"Jinja2", r"Twig_Error",
r"freemarker\.core\.", r"velocity\.exception",
r"org\.apache\.velocity", r"Smarty"
]
for pattern in template_errors:
if re.search(pattern, response_body):
return True, 0.7, f"Template engine error: {pattern}"
return False, 0.0, None
class NoSQLInjectionTester(BaseTester):
"""Tester for NoSQL Injection"""
def __init__(self):
super().__init__()
self.name = "nosql_injection"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for NoSQL injection indicators"""
# MongoDB errors
nosql_errors = [
r"MongoError", r"MongoDB", r"bson",
r"\$where", r"\$gt", r"\$ne",
r"SyntaxError.*JSON"
]
for pattern in nosql_errors:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.7, f"NoSQL error indicator: {pattern}"
# Check for authentication bypass
if "$ne" in payload or "$gt" in payload:
if response_status == 200 and "success" in response_body.lower():
return True, 0.6, "Possible NoSQL authentication bypass"
return False, 0.0, None
+457
View File
@@ -0,0 +1,457 @@
"""
NeuroSploit v3 - Logic and Protocol Vulnerability Testers
Testers for race conditions, business logic, rate limiting, parameter pollution,
type juggling, timing attacks, host header injection, HTTP smuggling, cache poisoning.
"""
import re
from typing import Tuple, Dict, Optional
from backend.core.vuln_engine.testers.base_tester import BaseTester
class RaceConditionTester(BaseTester):
"""Tester for Race Condition vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "race_condition"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for duplicate operation success indicators"""
body_lower = response_body.lower()
# Multiple success responses from concurrent requests
if context.get("concurrent_successes", 0) > 1:
return True, 0.85, f"Race condition: {context['concurrent_successes']} concurrent requests succeeded"
# Double-spend / duplicate operation indicators
duplicate_indicators = [
"already processed", "duplicate", "already exists",
"already applied", "already redeemed",
]
# If we got a success despite expected duplicate check
if response_status in [200, 201]:
success_words = ["success", "created", "processed", "applied", "completed", "confirmed"]
if any(w in body_lower for w in success_words):
if context.get("request_count", 0) > 1:
return True, 0.7, "Race condition: operation succeeded multiple times"
# Check for resource count discrepancy
if "balance" in body_lower or "quantity" in body_lower or "count" in body_lower:
numbers = re.findall(r'"(?:balance|quantity|count|amount)"\s*:\s*(-?\d+\.?\d*)', response_body)
if numbers and context.get("expected_value") is not None:
try:
actual = float(numbers[0])
expected = float(context["expected_value"])
if actual != expected:
return True, 0.75, f"Race condition: value mismatch (expected {expected}, got {actual})"
except (ValueError, IndexError):
pass
return False, 0.0, None
class BusinessLogicTester(BaseTester):
"""Tester for Business Logic vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "business_logic"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for business logic bypass indicators"""
body_lower = response_body.lower()
# Negative value acceptance
if re.search(r"-\d+", payload):
if response_status == 200:
if any(w in body_lower for w in ["success", "accepted", "processed", "approved"]):
return True, 0.8, "Business logic: negative value accepted"
# Check for negative pricing
if re.search(r'"(?:total|price|amount)"\s*:\s*-\d+', response_body):
return True, 0.9, "Business logic: negative price/amount in response"
# Zero-value bypass
if payload.strip() in ["0", "0.00", "0.0"]:
if response_status == 200 and "success" in body_lower:
return True, 0.75, "Business logic: zero value accepted for transaction"
# Workflow step skip
if context.get("skipped_step"):
if response_status == 200:
return True, 0.7, f"Business logic: step '{context['skipped_step']}' was skippable"
# Discount/coupon abuse
if "coupon" in payload.lower() or "discount" in payload.lower():
if re.search(r'"discount"\s*:\s*(?:100|[1-9]\d{2,})', response_body):
return True, 0.8, "Business logic: excessive discount applied"
# Role/privilege escalation via parameter
if any(w in payload.lower() for w in ["admin", "role=admin", "is_admin=true", "privilege"]):
if response_status == 200 and "admin" in body_lower:
return True, 0.6, "Business logic: privilege escalation parameter accepted"
return False, 0.0, None
class RateLimitBypassTester(BaseTester):
"""Tester for Rate Limit Bypass vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "rate_limit_bypass"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for continued success after many requests (bypass)"""
headers_lower = {k.lower(): v for k, v in response_headers.items()}
# After many requests, still getting 200
request_count = context.get("request_count", 0)
if request_count > 50 and response_status == 200:
# Check rate limit headers
remaining = headers_lower.get("x-ratelimit-remaining",
headers_lower.get("x-rate-limit-remaining",
headers_lower.get("ratelimit-remaining")))
if remaining is not None:
try:
if int(remaining) > 0:
return True, 0.6, f"Rate limit not enforced after {request_count} requests (remaining: {remaining})"
except ValueError:
pass
else:
# No rate limit headers at all
return True, 0.7, f"No rate limiting detected after {request_count} requests"
# Rate limit bypass via header manipulation
bypass_headers = ["x-forwarded-for", "x-real-ip", "x-originating-ip", "x-client-ip"]
if any(h in payload.lower() for h in bypass_headers):
if response_status == 200 and context.get("was_rate_limited"):
return True, 0.85, "Rate limit bypassed via IP spoofing header"
# Check if 429 was expected but got 200
if context.get("expected_429") and response_status == 200:
return True, 0.8, "Expected 429 (rate limited) but received 200"
return False, 0.0, None
class ParameterPollutionTester(BaseTester):
"""Tester for HTTP Parameter Pollution vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "parameter_pollution"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for different behavior with duplicate parameters"""
# Compare response with baseline (single param)
if "baseline_body" in context and "baseline_status" in context:
baseline_len = len(context["baseline_body"])
current_len = len(response_body)
diff = abs(current_len - baseline_len)
# Significant response difference
if diff > 200:
return True, 0.7, f"Parameter pollution: response differs by {diff} bytes from baseline"
# Status code difference
if response_status != context["baseline_status"]:
return True, 0.75, f"Parameter pollution: status changed from {context['baseline_status']} to {response_status}"
# Check if attacker-controlled value was used
if "neurosploit" in payload and "neurosploit" in response_body:
if context.get("original_value") and context["original_value"] not in response_body:
return True, 0.8, "Parameter pollution: attacker value used instead of original"
# WAF bypass via duplicate params
if context.get("waf_blocked_original") and response_status == 200:
return True, 0.8, "Parameter pollution: WAF bypass - blocked payload succeeded with duplicate params"
return False, 0.0, None
class TypeJugglingTester(BaseTester):
"""Tester for Type Juggling / Type Coercion vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "type_juggling"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for auth bypass with type coercion"""
body_lower = response_body.lower()
# Type juggling payloads
juggling_values = ["0", "true", "false", "null", "[]", "{}", "0e123", "0e999"]
if payload.strip() in juggling_values or payload.strip().startswith("0e"):
# Auth bypass
if response_status == 200:
auth_success = [
"authenticated", "logged in", "welcome", "dashboard",
"token", "session", "success",
]
for indicator in auth_success:
if indicator in body_lower:
return True, 0.8, f"Type juggling: auth bypass with '{payload.strip()}' - '{indicator}' in response"
# JWT/token accepted
if "jwt" in body_lower or "bearer" in body_lower:
if response_status == 200:
return True, 0.7, f"Type juggling: token accepted with value '{payload.strip()}'"
# Magic hash comparison bypass (0e strings)
if re.match(r"0e\d+", payload.strip()):
if response_status == 200 and any(w in body_lower for w in ["match", "equal", "valid", "correct"]):
return True, 0.85, f"Type juggling: magic hash bypass with '{payload.strip()}'"
# Array vs string comparison
if payload.strip() in ["[]", "Array"]:
if response_status == 200 and "success" in body_lower:
return True, 0.7, "Type juggling: array comparison bypass"
return False, 0.0, None
class TimingAttackTester(BaseTester):
"""Tester for Timing Attack vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "timing_attack"
def check_timeout_vulnerability(self, vuln_type: str) -> bool:
"""Timing attacks are detected via response time differences"""
return True
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check response time differences for timing side channels"""
# Primary detection is via response timing (handled by engine)
response_time = context.get("response_time_ms", 0)
baseline_time = context.get("baseline_time_ms", 0)
if response_time > 0 and baseline_time > 0:
diff = response_time - baseline_time
# Significant timing difference (> 100ms)
if diff > 100:
return True, 0.7, f"Timing attack: {diff}ms difference (baseline: {baseline_time}ms, actual: {response_time}ms)"
# Very significant (> 500ms)
if diff > 500:
return True, 0.9, f"Timing attack: {diff}ms difference strongly indicates character-by-character comparison"
# Check for timing via multiple measurements
if "timing_samples" in context:
samples = context["timing_samples"]
if len(samples) >= 2:
max_diff = max(samples) - min(samples)
if max_diff > 200:
return True, 0.65, f"Timing attack: {max_diff}ms variance across samples"
return False, 0.0, None
class HostHeaderInjectionTester(BaseTester):
"""Tester for Host Header Injection vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "host_header_injection"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for Host value reflected in response links/URLs"""
body_lower = response_body.lower()
# Injected host appearing in response
evil_hosts = ["evil.com", "attacker.com", "neurosploit.test", "canary.host"]
for host in evil_hosts:
if host in payload.lower() and host in body_lower:
# High confidence if in URL context
url_contexts = [
rf'https?://{re.escape(host)}',
rf'href\s*=\s*["\'][^"\']*{re.escape(host)}',
rf'action\s*=\s*["\'][^"\']*{re.escape(host)}',
rf'redirect.*{re.escape(host)}',
]
for pattern in url_contexts:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.9, f"Host header injection: '{host}' reflected in URL context"
return True, 0.7, f"Host header injection: '{host}' reflected in response body"
# X-Forwarded-Host injection
if "x-forwarded-host" in payload.lower():
headers_lower = {k.lower(): v for k, v in response_headers.items()}
location = headers_lower.get("location", "")
if any(h in location.lower() for h in evil_hosts):
return True, 0.9, "Host header injection: X-Forwarded-Host reflected in redirect"
# Password reset link poisoning
if context.get("is_password_reset") and response_status == 200:
for host in evil_hosts:
if host in body_lower:
return True, 0.95, f"Host header injection in password reset: link points to '{host}'"
return False, 0.0, None
class HttpSmugglingTester(BaseTester):
"""Tester for HTTP Request Smuggling vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "http_smuggling"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for HTTP smuggling indicators"""
headers_lower = {k.lower(): v for k, v in response_headers.items()}
# Response splitting - two HTTP responses in one
if re.search(r"HTTP/\d\.\d\s+\d{3}", response_body):
return True, 0.85, "HTTP smuggling: embedded HTTP response in body (response splitting)"
# Conflicting Content-Length and Transfer-Encoding
has_cl = "content-length" in headers_lower
has_te = "transfer-encoding" in headers_lower
if has_cl and has_te:
return True, 0.7, "HTTP smuggling: both Content-Length and Transfer-Encoding present"
# CL.TE or TE.CL desync indicators
if context.get("desync_detected"):
return True, 0.9, "HTTP smuggling: request desync confirmed"
# Unexpected response to smuggled second request
if "smuggle_marker" in context:
marker = context["smuggle_marker"]
if marker in response_body:
return True, 0.85, f"HTTP smuggling: smuggled request marker '{marker}' in response"
# Different status than expected (frontend vs backend disagreement)
if context.get("expected_status") and response_status != context["expected_status"]:
if response_status in [400, 403] and context["expected_status"] == 200:
return True, 0.5, f"HTTP smuggling: status mismatch (expected {context['expected_status']}, got {response_status})"
# Timeout on second request (queued/poisoned)
if context.get("second_request_timeout"):
return True, 0.7, "HTTP smuggling: second request timed out (possible queue poisoning)"
return False, 0.0, None
def check_timeout_vulnerability(self, vuln_type: str) -> bool:
"""Smuggling can cause timeouts on subsequent requests"""
return True
class CachePoisoningTester(BaseTester):
"""Tester for Web Cache Poisoning vulnerabilities"""
def __init__(self):
super().__init__()
self.name = "cache_poisoning"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for cache poisoning - injected content served from cache"""
headers_lower = {k.lower(): v for k, v in response_headers.items()}
# Check for cache hit with injected content
cache_hit = False
cache_headers = ["x-cache", "cf-cache-status", "x-varnish", "x-drupal-cache",
"x-proxy-cache", "x-cdn-cache"]
for header in cache_headers:
value = headers_lower.get(header, "").lower()
if "hit" in value:
cache_hit = True
break
# Age header indicates cached response
age = headers_lower.get("age")
if age and age != "0":
cache_hit = True
if cache_hit:
# Check if our injected content is in the cached response
injection_markers = ["neurosploit", "xss", "evil.com", "attacker"]
for marker in injection_markers:
if marker in payload.lower() and marker in response_body.lower():
return True, 0.9, f"Cache poisoning: injected content '{marker}' served from cache"
# Unkeyed header reflected in response (potential cache poison vector)
unkeyed_headers = ["x-forwarded-host", "x-forwarded-scheme", "x-original-url",
"x-rewrite-url", "x-forwarded-prefix"]
for header in unkeyed_headers:
if header in payload.lower():
# Check if the value appears in response
for marker in ["evil.com", "neurosploit", "attacker"]:
if marker in payload.lower() and marker in response_body.lower():
cache_status = "cached" if cache_hit else "uncached"
confidence = 0.85 if cache_hit else 0.5
return True, confidence, f"Cache poisoning: unkeyed header '{header}' reflected ({cache_status})"
# Cache deception check
if context.get("is_cache_deception_test"):
if cache_hit and ("token" in response_body.lower() or "session" in response_body.lower()):
return True, 0.8, "Cache deception: sensitive data cached via path confusion"
return False, 0.0, None
@@ -0,0 +1,211 @@
"""
NeuroSploit v3 - Request Forgery Vulnerability Testers
Testers for SSRF and CSRF
"""
import re
from typing import Tuple, Dict, Optional
from backend.core.vuln_engine.testers.base_tester import BaseTester
class SSRFTester(BaseTester):
"""Tester for Server-Side Request Forgery"""
def __init__(self):
super().__init__()
self.name = "ssrf"
# Cloud metadata indicators
self.cloud_indicators = [
r"ami-[a-z0-9]+", # AWS AMI ID
r"instance-id",
r"iam/security-credentials",
r"compute/v1", # GCP
r"metadata/instance",
r"169\.254\.169\.254"
]
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for SSRF indicators"""
# Check for cloud metadata
for pattern in self.cloud_indicators:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.95, f"SSRF to cloud metadata: {pattern}"
# Check for internal service indicators
internal_indicators = [
r"localhost",
r"127\.0\.0\.1",
r"192\.168\.\d+\.\d+",
r"10\.\d+\.\d+\.\d+",
r"172\.(?:1[6-9]|2\d|3[01])\.\d+\.\d+"
]
for pattern in internal_indicators:
if pattern in payload and re.search(pattern, response_body):
return True, 0.8, f"SSRF accessing internal resource: {pattern}"
# Check for different response when internal URL requested
if response_status == 200 and len(response_body) > 100:
if "169.254" in payload or "localhost" in payload or "127.0.0.1" in payload:
return True, 0.6, "Response received from internal URL - possible SSRF"
return False, 0.0, None
class CSRFTester(BaseTester):
"""Tester for Cross-Site Request Forgery"""
def __init__(self):
super().__init__()
self.name = "csrf"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for CSRF vulnerability indicators"""
# Check for missing CSRF protections
csrf_protections = [
r'name=["\']?csrf',
r'name=["\']?_token',
r'name=["\']?authenticity_token',
r'X-CSRF-TOKEN',
r'X-XSRF-TOKEN'
]
has_protection = any(
re.search(pattern, response_body, re.IGNORECASE)
for pattern in csrf_protections
)
# Check SameSite cookie
has_samesite = "samesite" in str(response_headers).lower()
# State-changing request without protection
if not has_protection and not has_samesite:
if response_status in [200, 302]:
return True, 0.7, "No CSRF token found in form - possible CSRF"
return False, 0.0, None
class GraphqlIntrospectionTester(BaseTester):
"""Tester for GraphQL Introspection exposure"""
def __init__(self):
super().__init__()
self.name = "graphql_introspection"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for __schema data in response indicating introspection is enabled"""
if response_status == 200:
# Direct __schema indicators
schema_patterns = [
r'"__schema"\s*:\s*\{',
r'"__type"\s*:\s*\{',
r'"queryType"\s*:\s*\{',
r'"mutationType"\s*:\s*\{',
r'"subscriptionType"\s*:',
r'"types"\s*:\s*\[.*"name"\s*:\s*"__',
r'"directives"\s*:\s*\[.*"name"\s*:',
]
for pattern in schema_patterns:
if re.search(pattern, response_body, re.IGNORECASE | re.DOTALL):
return True, 0.9, "GraphQL introspection: Full schema exposed via __schema query"
# Check for type listings
type_listing_patterns = [
r'"kind"\s*:\s*"(?:OBJECT|SCALAR|ENUM|INPUT_OBJECT|INTERFACE|UNION)"',
r'"fields"\s*:\s*\[.*"name"\s*:.*"type"\s*:',
r'"inputFields"\s*:\s*\[',
r'"enumValues"\s*:\s*\[',
]
type_match_count = sum(
1 for p in type_listing_patterns
if re.search(p, response_body, re.IGNORECASE | re.DOTALL)
)
if type_match_count >= 2:
return True, 0.85, "GraphQL introspection: Type schema data exposed"
# Check for field suggestions (partial introspection)
if re.search(r'"(?:message|errors)".*"Did you mean.*"', response_body):
return True, 0.6, "GraphQL introspection: Field suggestions leak schema information"
return False, 0.0, None
class GraphqlDosTester(BaseTester):
"""Tester for GraphQL Denial of Service via deeply nested queries"""
def __init__(self):
super().__init__()
self.name = "graphql_dos"
def analyze_response(
self,
payload: str,
response_status: int,
response_headers: Dict,
response_body: str,
context: Dict
) -> Tuple[bool, float, Optional[str]]:
"""Check for slow response with deeply nested queries indicating DoS potential"""
# Check response time for nested query DoS
response_time_ms = context.get("response_time_ms", 0)
# Nested query indicators in payload
nesting_indicators = [
payload.count("{") > 5,
"__typename" in payload and payload.count("__typename") > 3,
"fragment" in payload.lower() and "..." in payload,
]
is_nested_payload = any(nesting_indicators)
if is_nested_payload:
# Very slow response indicates resource exhaustion
if response_time_ms > 10000: # > 10 seconds
return True, 0.85, f"GraphQL DoS: Deeply nested query caused {response_time_ms}ms response time"
if response_time_ms > 5000: # > 5 seconds
return True, 0.7, f"GraphQL DoS: Nested query caused slow response ({response_time_ms}ms)"
# Check for timeout/error responses
if response_status in [408, 504, 502]:
if is_nested_payload:
return True, 0.8, "GraphQL DoS: Server timeout on deeply nested query"
# Check for resource limit errors (server has some protection but confirms issue)
resource_errors = [
r"query.*(?:too complex|too deep|exceeds.*(?:depth|complexity))",
r"max.*(?:depth|complexity).*(?:exceeded|reached)",
r"(?:depth|complexity)\s+limit",
r"query.*(?:cost|weight).*exceeded",
]
for pattern in resource_errors:
if re.search(pattern, response_body, re.IGNORECASE):
return True, 0.5, "GraphQL DoS: Depth/complexity limits exist but confirm nested queries are processed"
# Server error on complex query
if response_status == 500 and is_nested_payload:
return True, 0.65, "GraphQL DoS: Server error on deeply nested query"
return False, 0.0, None