mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-08-28 04:10:25 +02:00
Add files via upload
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from backend.core.vuln_engine.testers.base_tester import BaseTester
|
||||
|
||||
__all__ = ["BaseTester"]
|
||||
@@ -0,0 +1,124 @@
|
||||
"""
|
||||
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
|
||||
@@ -0,0 +1,130 @@
|
||||
"""
|
||||
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
|
||||
@@ -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,150 @@
|
||||
"""
|
||||
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
|
||||
@@ -0,0 +1,203 @@
|
||||
"""
|
||||
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
|
||||
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
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
|
||||
@@ -0,0 +1,372 @@
|
||||
"""
|
||||
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:
|
||||
# Check if it's in a dangerous context
|
||||
dangerous_patterns = [
|
||||
rf'<script[^>]*>{re.escape(payload)}',
|
||||
rf'on\w+\s*=\s*["\']?{re.escape(payload)}',
|
||||
rf'javascript:\s*{re.escape(payload)}',
|
||||
rf'<[^>]+{re.escape(payload)}[^>]*>',
|
||||
]
|
||||
|
||||
for pattern in dangerous_patterns:
|
||||
if re.search(pattern, response_body, re.IGNORECASE):
|
||||
return True, 0.9, f"XSS payload reflected in dangerous context: {pattern}"
|
||||
|
||||
# Payload reflected but possibly encoded
|
||||
return True, 0.7, "XSS payload reflected in response"
|
||||
|
||||
# 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"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.name = "xss_stored"
|
||||
|
||||
def analyze_response(
|
||||
self,
|
||||
payload: str,
|
||||
response_status: int,
|
||||
response_headers: Dict,
|
||||
response_body: str,
|
||||
context: Dict
|
||||
) -> Tuple[bool, float, Optional[str]]:
|
||||
"""Check for stored XSS - requires subsequent request verification"""
|
||||
# For stored XSS, we need to check if data was stored
|
||||
# This is a simplified check - full implementation would verify on retrieval
|
||||
if response_status in [200, 201, 302]:
|
||||
if "success" in response_body.lower() or "created" in response_body.lower():
|
||||
return True, 0.5, "Data possibly stored - verify retrieval for stored XSS"
|
||||
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
|
||||
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user