mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-02-12 14:02:45 +00:00
1106 lines
51 KiB
Python
1106 lines
51 KiB
Python
"""
|
|
NeuroSploit v3 - Scan Service
|
|
|
|
Orchestrates the entire scan process:
|
|
1. AI-powered prompt processing
|
|
2. REAL reconnaissance with actual tools
|
|
3. AUTONOMOUS endpoint discovery when recon finds little
|
|
4. AI-driven vulnerability testing
|
|
5. Dynamic analysis based on findings
|
|
|
|
GLOBAL AUTHORIZATION NOTICE:
|
|
This is a homologated penetration testing tool.
|
|
All tests are performed with explicit authorization from the target owner.
|
|
The AI agent has full permission to test for vulnerabilities.
|
|
"""
|
|
import asyncio
|
|
from datetime import datetime
|
|
from typing import Optional, List, Dict, Any
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
|
|
from backend.models import Scan, Target, Endpoint, Vulnerability, VulnerabilityTest, AgentTask
|
|
from backend.api.websocket import manager as ws_manager
|
|
from backend.api.v1.prompts import PRESET_PROMPTS
|
|
from backend.db.database import async_session_factory
|
|
from backend.core.recon_integration import ReconIntegration, check_tools_installed
|
|
from backend.core.ai_prompt_processor import AIPromptProcessor, AIVulnerabilityAnalyzer
|
|
from backend.core.vuln_engine.engine import DynamicVulnerabilityEngine
|
|
from backend.core.vuln_engine.payload_generator import PayloadGenerator
|
|
from backend.core.autonomous_scanner import AutonomousScanner
|
|
from backend.core.ai_pentest_agent import AIPentestAgent
|
|
from backend.core.ai_prompt_processor import TestingPlan
|
|
|
|
|
|
# Phase control: signaling between API and running background tasks
|
|
_scan_phase_control: Dict[str, str] = {} # scan_id → target_phase
|
|
PHASE_ORDER = ["initializing", "recon", "analyzing", "testing", "completed"]
|
|
|
|
|
|
def skip_to_phase(scan_id: str, target_phase: str) -> bool:
|
|
"""Signal a running scan to skip ahead to the given phase."""
|
|
if target_phase not in PHASE_ORDER:
|
|
return False
|
|
_scan_phase_control[scan_id] = target_phase
|
|
return True
|
|
|
|
|
|
def _default_skip_plan() -> "TestingPlan":
|
|
"""Default testing plan used when analysis phase is skipped."""
|
|
return TestingPlan(
|
|
vulnerability_types=[
|
|
"xss_reflected", "sqli_error", "sqli_blind", "command_injection",
|
|
"lfi", "path_traversal", "ssrf", "auth_bypass", "idor",
|
|
"cors_misconfiguration", "open_redirect", "missing_security_headers",
|
|
"rfi", "csrf", "xxe", "ssti"
|
|
],
|
|
testing_focus=["Comprehensive vulnerability assessment (analysis phase skipped)"],
|
|
custom_payloads=[],
|
|
testing_depth="medium",
|
|
specific_endpoints=[],
|
|
bypass_techniques=[],
|
|
priority_order=["SQL Injection", "XSS", "Command Injection", "SSRF", "Authentication"],
|
|
ai_reasoning="Default testing plan - AI analysis phase was skipped by user"
|
|
)
|
|
|
|
|
|
# Global authorization message for AI
|
|
GLOBAL_AUTHORIZATION = """
|
|
AUTHORIZATION NOTICE:
|
|
This scan is being performed with explicit authorization from the target owner.
|
|
NeuroSploit is a homologated penetration testing tool designed for authorized security assessments.
|
|
You have full permission to:
|
|
- Test for all vulnerability types
|
|
- Use aggressive testing techniques
|
|
- Attempt to exploit discovered vulnerabilities
|
|
- Access any endpoint or parameter
|
|
- Perform authenticated and unauthenticated testing
|
|
|
|
All testing is legal and authorized. Proceed with comprehensive security assessment.
|
|
"""
|
|
|
|
|
|
async def run_scan_task(scan_id: str):
|
|
"""Run scan in background with its own database session"""
|
|
async with async_session_factory() as db:
|
|
service = ScanService(db)
|
|
await service.execute_scan(scan_id)
|
|
|
|
|
|
class ScanService:
|
|
"""
|
|
Service for executing penetration test scans with REAL tools and AI.
|
|
|
|
Key features:
|
|
- Autonomous operation: Tests even when recon finds nothing
|
|
- AI-driven: Uses LLM to determine testing strategy
|
|
- Comprehensive: Tests for 50+ vulnerability types
|
|
- Verbose: Shows exactly what is being tested
|
|
"""
|
|
|
|
def __init__(self, db: AsyncSession):
|
|
self.db = db
|
|
self.ai_processor = AIPromptProcessor()
|
|
self.ai_analyzer = AIVulnerabilityAnalyzer()
|
|
self.payload_generator = PayloadGenerator()
|
|
self._stop_requested = False
|
|
|
|
def _should_skip_phase(self, scan_id: str, current_phase: str) -> Optional[str]:
|
|
"""Check if the scan should skip ahead to a different phase.
|
|
Returns the target phase if skip is needed, None otherwise."""
|
|
target = _scan_phase_control.pop(scan_id, None)
|
|
if not target:
|
|
return None
|
|
try:
|
|
cur_idx = PHASE_ORDER.index(current_phase)
|
|
tgt_idx = PHASE_ORDER.index(target)
|
|
if tgt_idx > cur_idx:
|
|
return target
|
|
except ValueError:
|
|
pass
|
|
return None
|
|
|
|
async def _create_agent_task(
|
|
self,
|
|
scan_id: str,
|
|
task_type: str,
|
|
task_name: str,
|
|
description: str = None,
|
|
tool_name: str = None,
|
|
tool_category: str = None
|
|
) -> AgentTask:
|
|
"""Create and start a new agent task"""
|
|
task = AgentTask(
|
|
scan_id=scan_id,
|
|
task_type=task_type,
|
|
task_name=task_name,
|
|
description=description,
|
|
tool_name=tool_name,
|
|
tool_category=tool_category
|
|
)
|
|
task.start()
|
|
self.db.add(task)
|
|
await self.db.flush()
|
|
|
|
# Broadcast task started
|
|
await ws_manager.broadcast_agent_task_started(scan_id, task.to_dict())
|
|
|
|
return task
|
|
|
|
async def _complete_agent_task(
|
|
self,
|
|
task: AgentTask,
|
|
items_processed: int = 0,
|
|
items_found: int = 0,
|
|
summary: str = None
|
|
):
|
|
"""Mark an agent task as completed"""
|
|
task.complete(items_processed, items_found, summary)
|
|
await self.db.commit()
|
|
|
|
# Broadcast task completed
|
|
await ws_manager.broadcast_agent_task_completed(task.scan_id, task.to_dict())
|
|
|
|
async def _fail_agent_task(self, task: AgentTask, error: str):
|
|
"""Mark an agent task as failed"""
|
|
task.fail(error)
|
|
await self.db.commit()
|
|
|
|
# Broadcast task update
|
|
await ws_manager.broadcast_agent_task(task.scan_id, task.to_dict())
|
|
|
|
async def execute_scan(self, scan_id: str):
|
|
"""Execute a complete scan with real recon, autonomous discovery, and AI analysis"""
|
|
try:
|
|
# Get scan from database
|
|
result = await self.db.execute(select(Scan).where(Scan.id == scan_id))
|
|
scan = result.scalar_one_or_none()
|
|
|
|
if not scan:
|
|
await ws_manager.broadcast_error(scan_id, "Scan not found")
|
|
return
|
|
|
|
# Update status
|
|
scan.status = "running"
|
|
scan.started_at = datetime.utcnow()
|
|
scan.current_phase = "initializing"
|
|
scan.progress = 2
|
|
await self.db.commit()
|
|
|
|
await ws_manager.broadcast_scan_started(scan_id)
|
|
await ws_manager.broadcast_log(scan_id, "info", "=" * 60)
|
|
await ws_manager.broadcast_log(scan_id, "info", "NEUROSPLOIT v3 - AI-Powered Penetration Testing")
|
|
await ws_manager.broadcast_log(scan_id, "info", "=" * 60)
|
|
await ws_manager.broadcast_log(scan_id, "info", "AUTHORIZED PENETRATION TEST - Full permission granted")
|
|
await ws_manager.broadcast_progress(scan_id, 2, "Initializing...")
|
|
|
|
# Get targets
|
|
targets_result = await self.db.execute(
|
|
select(Target).where(Target.scan_id == scan_id)
|
|
)
|
|
targets = targets_result.scalars().all()
|
|
|
|
if not targets:
|
|
await ws_manager.broadcast_error(scan_id, "No targets found")
|
|
scan.status = "failed"
|
|
scan.error_message = "No targets found"
|
|
await self.db.commit()
|
|
return
|
|
|
|
await ws_manager.broadcast_log(scan_id, "info", f"Targets: {', '.join([t.url for t in targets])}")
|
|
|
|
# Check available tools - Create task for initialization
|
|
init_task = await self._create_agent_task(
|
|
scan_id=scan_id,
|
|
task_type="recon",
|
|
task_name="Initialize Security Tools",
|
|
description="Checking available security tools and dependencies",
|
|
tool_name="system",
|
|
tool_category="setup"
|
|
)
|
|
|
|
await ws_manager.broadcast_log(scan_id, "info", "")
|
|
await ws_manager.broadcast_log(scan_id, "info", "Checking installed security tools...")
|
|
tools_status = await check_tools_installed()
|
|
installed_tools = [t for t, installed in tools_status.items() if installed]
|
|
await ws_manager.broadcast_log(scan_id, "info", f"Available: {', '.join(installed_tools[:15])}...")
|
|
|
|
await self._complete_agent_task(
|
|
init_task,
|
|
items_processed=len(tools_status),
|
|
items_found=len(installed_tools),
|
|
summary=f"Found {len(installed_tools)} available security tools"
|
|
)
|
|
|
|
# Get prompt content
|
|
prompt_content = await self._get_prompt_content(scan)
|
|
await ws_manager.broadcast_log(scan_id, "info", "")
|
|
await ws_manager.broadcast_log(scan_id, "info", "User Prompt:")
|
|
await ws_manager.broadcast_log(scan_id, "debug", f"{prompt_content[:300]}...")
|
|
|
|
# Phase 1: REAL Reconnaissance (if enabled)
|
|
recon_data = {}
|
|
skip_target = self._should_skip_phase(scan_id, "initializing")
|
|
recon_skipped = False
|
|
if skip_target:
|
|
# User requested skip from initializing
|
|
skip_idx = PHASE_ORDER.index(skip_target)
|
|
if skip_idx >= PHASE_ORDER.index("recon"):
|
|
recon_skipped = True
|
|
await ws_manager.broadcast_log(scan_id, "warning", "")
|
|
await ws_manager.broadcast_log(scan_id, "warning", ">> PHASE SKIPPED: Reconnaissance (user request)")
|
|
await ws_manager.broadcast_phase_change(scan_id, "recon_skipped")
|
|
scan.recon_enabled = False
|
|
|
|
if scan.recon_enabled and not recon_skipped:
|
|
scan.current_phase = "recon"
|
|
await self.db.commit()
|
|
await ws_manager.broadcast_phase_change(scan_id, "recon")
|
|
await ws_manager.broadcast_progress(scan_id, 5, "Starting reconnaissance...")
|
|
await ws_manager.broadcast_log(scan_id, "info", "")
|
|
await ws_manager.broadcast_log(scan_id, "info", "=" * 40)
|
|
await ws_manager.broadcast_log(scan_id, "info", "PHASE 1: RECONNAISSANCE")
|
|
await ws_manager.broadcast_log(scan_id, "info", "=" * 40)
|
|
|
|
recon_integration = ReconIntegration(scan_id)
|
|
depth = "medium" if scan.scan_type == "full" else "quick"
|
|
|
|
for target in targets:
|
|
# Create recon task for each target
|
|
recon_task = await self._create_agent_task(
|
|
scan_id=scan_id,
|
|
task_type="recon",
|
|
task_name=f"Reconnaissance: {target.hostname or target.url[:30]}",
|
|
description=f"Running {depth} reconnaissance on {target.url}",
|
|
tool_name="recon_integration",
|
|
tool_category="scanner"
|
|
)
|
|
|
|
try:
|
|
await ws_manager.broadcast_log(scan_id, "info", f"Target: {target.url}")
|
|
target_recon = await recon_integration.run_full_recon(target.url, depth=depth)
|
|
recon_data = self._merge_recon_data(recon_data, target_recon)
|
|
|
|
endpoints_found = 0
|
|
# Save discovered endpoints to database
|
|
for endpoint_data in target_recon.get("endpoints", []):
|
|
if isinstance(endpoint_data, dict):
|
|
endpoint = Endpoint(
|
|
scan_id=scan_id,
|
|
target_id=target.id,
|
|
url=endpoint_data.get("url", ""),
|
|
method="GET",
|
|
path=endpoint_data.get("path", "/"),
|
|
response_status=endpoint_data.get("status"),
|
|
content_type=endpoint_data.get("content_type", "")
|
|
)
|
|
self.db.add(endpoint)
|
|
scan.total_endpoints += 1
|
|
endpoints_found += 1
|
|
|
|
await self._complete_agent_task(
|
|
recon_task,
|
|
items_processed=1,
|
|
items_found=endpoints_found,
|
|
summary=f"Found {endpoints_found} endpoints, {len(target_recon.get('urls', []))} URLs"
|
|
)
|
|
except Exception as e:
|
|
await self._fail_agent_task(recon_task, str(e))
|
|
|
|
await self.db.commit()
|
|
recon_endpoints = scan.total_endpoints
|
|
recon_urls = len(recon_data.get("urls", []))
|
|
await ws_manager.broadcast_log(scan_id, "info", f"Recon found: {recon_endpoints} endpoints, {recon_urls} URLs")
|
|
|
|
# Phase 1.5: AUTONOMOUS DISCOVERY (if recon found little)
|
|
endpoints_count = scan.total_endpoints + len(recon_data.get("urls", []))
|
|
|
|
if endpoints_count < 10:
|
|
await ws_manager.broadcast_log(scan_id, "info", "")
|
|
await ws_manager.broadcast_log(scan_id, "info", "=" * 40)
|
|
await ws_manager.broadcast_log(scan_id, "info", "AUTONOMOUS DISCOVERY MODE")
|
|
await ws_manager.broadcast_log(scan_id, "info", "=" * 40)
|
|
await ws_manager.broadcast_log(scan_id, "warning", "Recon found limited data. Activating autonomous scanner...")
|
|
await ws_manager.broadcast_progress(scan_id, 20, "Autonomous endpoint discovery...")
|
|
|
|
# Create log callback for autonomous scanner
|
|
async def scanner_log(level: str, message: str):
|
|
await ws_manager.broadcast_log(scan_id, level, message)
|
|
|
|
for target in targets:
|
|
# Create autonomous discovery task
|
|
discovery_task = await self._create_agent_task(
|
|
scan_id=scan_id,
|
|
task_type="recon",
|
|
task_name=f"Autonomous Discovery: {target.hostname or target.url[:30]}",
|
|
description="AI-powered endpoint discovery and vulnerability scanning",
|
|
tool_name="autonomous_scanner",
|
|
tool_category="ai"
|
|
)
|
|
|
|
try:
|
|
endpoints_discovered = 0
|
|
vulns_discovered = 0
|
|
|
|
async with AutonomousScanner(
|
|
scan_id=scan_id,
|
|
log_callback=scanner_log,
|
|
timeout=15,
|
|
max_depth=3
|
|
) as scanner:
|
|
autonomous_results = await scanner.run_autonomous_scan(
|
|
target_url=target.url,
|
|
recon_data=recon_data
|
|
)
|
|
|
|
# Merge autonomous results
|
|
for ep in autonomous_results.get("endpoints", []):
|
|
if isinstance(ep, dict):
|
|
endpoint = Endpoint(
|
|
scan_id=scan_id,
|
|
target_id=target.id,
|
|
url=ep.get("url", ""),
|
|
method=ep.get("method", "GET"),
|
|
path=ep.get("url", "").split("?")[0].split("/")[-1] or "/"
|
|
)
|
|
self.db.add(endpoint)
|
|
scan.total_endpoints += 1
|
|
endpoints_discovered += 1
|
|
|
|
# Add URLs to recon data
|
|
recon_data["urls"] = recon_data.get("urls", []) + [
|
|
ep.get("url") for ep in autonomous_results.get("endpoints", [])
|
|
if isinstance(ep, dict)
|
|
]
|
|
recon_data["directories"] = autonomous_results.get("directories_found", [])
|
|
recon_data["parameters"] = autonomous_results.get("parameters_found", [])
|
|
|
|
# Save autonomous vulnerabilities directly
|
|
for vuln in autonomous_results.get("vulnerabilities", []):
|
|
vuln_severity = self._confidence_to_severity(vuln["confidence"])
|
|
db_vuln = Vulnerability(
|
|
scan_id=scan_id,
|
|
title=f"{vuln['type'].replace('_', ' ').title()} on {vuln['endpoint'][:50]}",
|
|
vulnerability_type=vuln["type"],
|
|
severity=vuln_severity,
|
|
description=vuln["evidence"],
|
|
affected_endpoint=vuln["endpoint"],
|
|
poc_payload=vuln["payload"],
|
|
poc_request=str(vuln.get("request", {}))[:5000],
|
|
poc_response=str(vuln.get("response", {}))[:5000]
|
|
)
|
|
self.db.add(db_vuln)
|
|
await self.db.flush() # Ensure ID is assigned
|
|
vulns_discovered += 1
|
|
|
|
# Increment vulnerability count
|
|
await self._increment_vulnerability_count(scan, vuln_severity)
|
|
|
|
await ws_manager.broadcast_vulnerability_found(scan_id, {
|
|
"id": db_vuln.id,
|
|
"title": db_vuln.title,
|
|
"severity": db_vuln.severity,
|
|
"type": vuln["type"],
|
|
"endpoint": vuln["endpoint"]
|
|
})
|
|
|
|
await self._complete_agent_task(
|
|
discovery_task,
|
|
items_processed=endpoints_discovered,
|
|
items_found=vulns_discovered,
|
|
summary=f"Discovered {endpoints_discovered} endpoints, {vulns_discovered} vulnerabilities"
|
|
)
|
|
except Exception as e:
|
|
await self._fail_agent_task(discovery_task, str(e))
|
|
|
|
await self.db.commit()
|
|
await ws_manager.broadcast_log(scan_id, "info", f"Autonomous discovery complete. Total endpoints: {scan.total_endpoints}")
|
|
|
|
# Check for phase skip before analysis
|
|
skip_target = self._should_skip_phase(scan_id, "recon") or (skip_target if skip_target and PHASE_ORDER.index(skip_target) >= PHASE_ORDER.index("analyzing") else None)
|
|
analysis_skipped = False
|
|
testing_plan = None
|
|
|
|
if skip_target and PHASE_ORDER.index(skip_target) >= PHASE_ORDER.index("analyzing"):
|
|
analysis_skipped = True
|
|
testing_plan = _default_skip_plan()
|
|
await ws_manager.broadcast_log(scan_id, "warning", "")
|
|
await ws_manager.broadcast_log(scan_id, "warning", ">> PHASE SKIPPED: AI Analysis (user request)")
|
|
await ws_manager.broadcast_log(scan_id, "info", f"Using default testing plan with {len(testing_plan.vulnerability_types)} vulnerability types")
|
|
await ws_manager.broadcast_phase_change(scan_id, "analyzing_skipped")
|
|
|
|
if skip_target == "completed":
|
|
# User wants to skip everything - finalize
|
|
self._stop_requested = True
|
|
|
|
if not analysis_skipped:
|
|
# Phase 2: AI Prompt Processing
|
|
scan.current_phase = "analyzing"
|
|
await self.db.commit()
|
|
await ws_manager.broadcast_phase_change(scan_id, "analyzing")
|
|
await ws_manager.broadcast_progress(scan_id, 40, "AI analyzing prompt and data...")
|
|
await ws_manager.broadcast_log(scan_id, "info", "")
|
|
await ws_manager.broadcast_log(scan_id, "info", "=" * 40)
|
|
await ws_manager.broadcast_log(scan_id, "info", "PHASE 2: AI ANALYSIS")
|
|
await ws_manager.broadcast_log(scan_id, "info", "=" * 40)
|
|
|
|
if not analysis_skipped:
|
|
# Create AI analysis task
|
|
analysis_task = await self._create_agent_task(
|
|
scan_id=scan_id,
|
|
task_type="analysis",
|
|
task_name="AI Strategy Analysis",
|
|
description="Analyzing prompt and recon data to determine testing strategy",
|
|
tool_name="ai_prompt_processor",
|
|
tool_category="ai"
|
|
)
|
|
|
|
try:
|
|
# Enhance prompt with authorization
|
|
enhanced_prompt = f"{GLOBAL_AUTHORIZATION}\n\nUSER REQUEST:\n{prompt_content}"
|
|
|
|
# Get AI-generated testing plan
|
|
await ws_manager.broadcast_log(scan_id, "info", "AI processing prompt and determining attack strategy...")
|
|
|
|
testing_plan = await self.ai_processor.process_prompt(
|
|
prompt=enhanced_prompt,
|
|
recon_data=recon_data,
|
|
target_info={"targets": [t.url for t in targets]}
|
|
)
|
|
|
|
await ws_manager.broadcast_log(scan_id, "info", "")
|
|
await ws_manager.broadcast_log(scan_id, "info", "AI TESTING PLAN:")
|
|
await ws_manager.broadcast_log(scan_id, "info", f" Vulnerability Types: {', '.join(testing_plan.vulnerability_types[:10])}")
|
|
if len(testing_plan.vulnerability_types) > 10:
|
|
await ws_manager.broadcast_log(scan_id, "info", f" ... and {len(testing_plan.vulnerability_types) - 10} more types")
|
|
await ws_manager.broadcast_log(scan_id, "info", f" Testing Focus: {', '.join(testing_plan.testing_focus[:5])}")
|
|
await ws_manager.broadcast_log(scan_id, "info", f" Depth: {testing_plan.testing_depth}")
|
|
await ws_manager.broadcast_log(scan_id, "info", "")
|
|
await ws_manager.broadcast_log(scan_id, "info", f"AI Reasoning: {testing_plan.ai_reasoning[:300]}...")
|
|
|
|
await self._complete_agent_task(
|
|
analysis_task,
|
|
items_processed=1,
|
|
items_found=len(testing_plan.vulnerability_types),
|
|
summary=f"Generated testing plan with {len(testing_plan.vulnerability_types)} vulnerability types"
|
|
)
|
|
except Exception as e:
|
|
await self._fail_agent_task(analysis_task, str(e))
|
|
raise
|
|
|
|
# Ensure testing_plan exists (either from AI or default skip plan)
|
|
if testing_plan is None:
|
|
testing_plan = _default_skip_plan()
|
|
|
|
await ws_manager.broadcast_progress(scan_id, 45, f"Testing {len(testing_plan.vulnerability_types)} vuln types")
|
|
|
|
# Check for phase skip before testing
|
|
skip_target = self._should_skip_phase(scan_id, "analyzing")
|
|
testing_skipped = False
|
|
if skip_target and PHASE_ORDER.index(skip_target) >= PHASE_ORDER.index("testing"):
|
|
if skip_target == "completed":
|
|
testing_skipped = True
|
|
self._stop_requested = True
|
|
await ws_manager.broadcast_log(scan_id, "warning", "")
|
|
await ws_manager.broadcast_log(scan_id, "warning", ">> PHASE SKIPPED: Testing (user request - jumping to completion)")
|
|
await ws_manager.broadcast_phase_change(scan_id, "testing_skipped")
|
|
|
|
if not testing_skipped:
|
|
# Phase 3: AI OFFENSIVE AGENT
|
|
scan.current_phase = "testing"
|
|
await self.db.commit()
|
|
await ws_manager.broadcast_phase_change(scan_id, "testing")
|
|
await ws_manager.broadcast_log(scan_id, "info", "")
|
|
await ws_manager.broadcast_log(scan_id, "info", "=" * 40)
|
|
await ws_manager.broadcast_log(scan_id, "info", "PHASE 3: AI OFFENSIVE AGENT")
|
|
await ws_manager.broadcast_log(scan_id, "info", "=" * 40)
|
|
|
|
# Run the AI Offensive Agent for each target
|
|
for target in targets:
|
|
await ws_manager.broadcast_log(scan_id, "info", f"Deploying AI Agent on: {target.url}")
|
|
|
|
# Create AI pentest agent task
|
|
agent_task = await self._create_agent_task(
|
|
scan_id=scan_id,
|
|
task_type="testing",
|
|
task_name=f"AI Pentest Agent: {target.hostname or target.url[:30]}",
|
|
description=f"AI-powered penetration testing on {target.url}",
|
|
tool_name="ai_pentest_agent",
|
|
tool_category="ai"
|
|
)
|
|
|
|
try:
|
|
# Create log callback for the agent
|
|
async def agent_log(level: str, message: str):
|
|
await ws_manager.broadcast_log(scan_id, level, message)
|
|
|
|
# Build auth headers
|
|
auth_headers = self._build_auth_headers(scan)
|
|
|
|
findings_count = 0
|
|
endpoints_tested = 0
|
|
|
|
async with AIPentestAgent(
|
|
target=target.url,
|
|
log_callback=agent_log,
|
|
auth_headers=auth_headers,
|
|
max_depth=5
|
|
) as agent:
|
|
agent_report = await agent.run()
|
|
|
|
# Save agent findings as vulnerabilities
|
|
for finding in agent_report.get("findings", []):
|
|
finding_severity = finding["severity"]
|
|
vuln = Vulnerability(
|
|
scan_id=scan_id,
|
|
title=f"{finding['type'].upper()} - {finding['endpoint'][:50]}",
|
|
vulnerability_type=finding["type"],
|
|
severity=finding_severity,
|
|
description=finding["evidence"],
|
|
affected_endpoint=finding["endpoint"],
|
|
poc_payload=finding["payload"],
|
|
poc_request=finding.get("raw_request", "")[:5000],
|
|
poc_response=finding.get("raw_response", "")[:5000],
|
|
remediation=finding.get("impact", ""),
|
|
ai_analysis="\n".join(finding.get("exploitation_steps", []))
|
|
)
|
|
self.db.add(vuln)
|
|
await self.db.flush() # Ensure ID is assigned
|
|
findings_count += 1
|
|
|
|
# Increment vulnerability count
|
|
await self._increment_vulnerability_count(scan, finding_severity)
|
|
|
|
await ws_manager.broadcast_vulnerability_found(scan_id, {
|
|
"id": vuln.id,
|
|
"title": vuln.title,
|
|
"severity": vuln.severity,
|
|
"type": finding["type"],
|
|
"endpoint": finding["endpoint"]
|
|
})
|
|
|
|
# Update endpoint count
|
|
endpoints_tested = agent_report.get("summary", {}).get("total_endpoints", 0)
|
|
scan.total_endpoints += endpoints_tested
|
|
|
|
await self._complete_agent_task(
|
|
agent_task,
|
|
items_processed=endpoints_tested,
|
|
items_found=findings_count,
|
|
summary=f"Tested {endpoints_tested} endpoints, found {findings_count} vulnerabilities"
|
|
)
|
|
except Exception as e:
|
|
await self._fail_agent_task(agent_task, str(e))
|
|
|
|
await self.db.commit()
|
|
|
|
# Continue with additional AI-driven testing
|
|
|
|
# Get all endpoints to test
|
|
endpoints_result = await self.db.execute(
|
|
select(Endpoint).where(Endpoint.scan_id == scan_id)
|
|
)
|
|
endpoints = list(endpoints_result.scalars().all())
|
|
|
|
# Add URLs from recon as endpoints
|
|
for url in recon_data.get("urls", [])[:100]: # Test up to 100 URLs
|
|
if "?" in url and url not in [e.url for e in endpoints]:
|
|
endpoint = Endpoint(
|
|
scan_id=scan_id,
|
|
url=url,
|
|
method="GET",
|
|
path=url.split("?")[0].split("/")[-1] if "/" in url else "/"
|
|
)
|
|
self.db.add(endpoint)
|
|
endpoints.append(endpoint)
|
|
await self.db.commit()
|
|
|
|
# If STILL no endpoints, create from targets with common paths
|
|
if not endpoints:
|
|
await ws_manager.broadcast_log(scan_id, "warning", "No endpoints found. Creating test endpoints from targets...")
|
|
common_paths = [
|
|
"/", "/login", "/admin", "/api", "/search", "/user",
|
|
"/?id=1", "/?page=1", "/?q=test", "/?search=test"
|
|
]
|
|
for target in targets:
|
|
for path in common_paths:
|
|
url = target.url.rstrip("/") + path
|
|
endpoint = Endpoint(
|
|
scan_id=scan_id,
|
|
target_id=target.id,
|
|
url=url,
|
|
method="GET",
|
|
path=path
|
|
)
|
|
self.db.add(endpoint)
|
|
endpoints.append(endpoint)
|
|
scan.total_endpoints += 1
|
|
await self.db.commit()
|
|
|
|
await ws_manager.broadcast_log(scan_id, "info", f"Testing {len(endpoints)} endpoints for {len(testing_plan.vulnerability_types)} vuln types")
|
|
await ws_manager.broadcast_log(scan_id, "info", "")
|
|
|
|
# Create vulnerability testing task
|
|
vuln_testing_task = await self._create_agent_task(
|
|
scan_id=scan_id,
|
|
task_type="testing",
|
|
task_name="Vulnerability Testing",
|
|
description=f"Testing {len(endpoints)} endpoints for {len(testing_plan.vulnerability_types)} vulnerability types",
|
|
tool_name="dynamic_vuln_engine",
|
|
tool_category="scanner"
|
|
)
|
|
|
|
try:
|
|
# Test endpoints with AI-determined vulnerabilities
|
|
total_endpoints = len(endpoints)
|
|
endpoints_tested = 0
|
|
vulns_before = scan.total_vulnerabilities
|
|
|
|
# Check for mid-phase skip signal
|
|
skip_now = self._should_skip_phase(scan_id, "testing")
|
|
if skip_now:
|
|
self._stop_requested = True
|
|
|
|
async with DynamicVulnerabilityEngine() as engine:
|
|
for i, endpoint in enumerate(endpoints):
|
|
if self._stop_requested:
|
|
break
|
|
|
|
# Check for skip signal during testing loop
|
|
skip_now = self._should_skip_phase(scan_id, "testing")
|
|
if skip_now:
|
|
await ws_manager.broadcast_log(scan_id, "warning", ">> Phase skip requested - finishing testing early")
|
|
break
|
|
|
|
progress = 45 + int((i / total_endpoints) * 45)
|
|
await ws_manager.broadcast_progress(
|
|
scan_id, progress,
|
|
f"Testing {i+1}/{total_endpoints}: {endpoint.path or endpoint.url[:50]}"
|
|
)
|
|
|
|
# Log what we're testing
|
|
await ws_manager.broadcast_log(scan_id, "debug", f"[{i+1}/{total_endpoints}] Testing: {endpoint.url[:80]}")
|
|
|
|
await self._test_endpoint_with_ai(
|
|
scan=scan,
|
|
endpoint=endpoint,
|
|
testing_plan=testing_plan,
|
|
engine=engine,
|
|
recon_data=recon_data
|
|
)
|
|
endpoints_tested += 1
|
|
|
|
# Update final counts
|
|
await self._update_vulnerability_counts(scan)
|
|
|
|
vulns_found = scan.total_vulnerabilities - vulns_before
|
|
await self._complete_agent_task(
|
|
vuln_testing_task,
|
|
items_processed=endpoints_tested,
|
|
items_found=vulns_found,
|
|
summary=f"Tested {endpoints_tested} endpoints, found {vulns_found} vulnerabilities"
|
|
)
|
|
except Exception as e:
|
|
await self._fail_agent_task(vuln_testing_task, str(e))
|
|
raise
|
|
|
|
# Phase 4: Complete
|
|
scan.status = "completed"
|
|
scan.completed_at = datetime.utcnow()
|
|
scan.progress = 100
|
|
scan.current_phase = "completed"
|
|
|
|
# Calculate duration
|
|
if scan.started_at:
|
|
duration = (scan.completed_at - scan.started_at).total_seconds()
|
|
scan.duration = int(duration)
|
|
|
|
await self.db.commit()
|
|
|
|
await ws_manager.broadcast_log(scan_id, "info", "")
|
|
await ws_manager.broadcast_log(scan_id, "info", "=" * 60)
|
|
await ws_manager.broadcast_log(scan_id, "info", "SCAN COMPLETE")
|
|
await ws_manager.broadcast_log(scan_id, "info", "=" * 60)
|
|
await ws_manager.broadcast_progress(scan_id, 100, "Scan complete!")
|
|
await ws_manager.broadcast_log(scan_id, "info", f"Endpoints Tested: {scan.total_endpoints}")
|
|
await ws_manager.broadcast_log(scan_id, "info", f"Vulnerabilities Found: {scan.total_vulnerabilities}")
|
|
await ws_manager.broadcast_log(scan_id, "info", f" Critical: {scan.critical_count}")
|
|
await ws_manager.broadcast_log(scan_id, "info", f" High: {scan.high_count}")
|
|
await ws_manager.broadcast_log(scan_id, "info", f" Medium: {scan.medium_count}")
|
|
await ws_manager.broadcast_log(scan_id, "info", f" Low: {scan.low_count}")
|
|
|
|
await ws_manager.broadcast_scan_completed(scan_id, {
|
|
"total_endpoints": scan.total_endpoints,
|
|
"total_vulnerabilities": scan.total_vulnerabilities,
|
|
"critical": scan.critical_count,
|
|
"high": scan.high_count,
|
|
"medium": scan.medium_count,
|
|
"low": scan.low_count
|
|
})
|
|
|
|
# Auto-generate report on completion
|
|
try:
|
|
from backend.services.report_service import auto_generate_report
|
|
await ws_manager.broadcast_log(scan_id, "info", "")
|
|
await ws_manager.broadcast_log(scan_id, "info", "Generating security assessment report...")
|
|
report = await auto_generate_report(self.db, scan_id, is_partial=False)
|
|
await ws_manager.broadcast_log(scan_id, "info", f"Report generated: {report.title}")
|
|
except Exception as report_error:
|
|
await ws_manager.broadcast_log(scan_id, "warning", f"Failed to auto-generate report: {str(report_error)}")
|
|
|
|
except Exception as e:
|
|
import traceback
|
|
error_msg = f"Scan error: {str(e)}"
|
|
print(f"Scan error: {traceback.format_exc()}")
|
|
|
|
try:
|
|
result = await self.db.execute(select(Scan).where(Scan.id == scan_id))
|
|
scan = result.scalar_one_or_none()
|
|
if scan:
|
|
scan.status = "failed"
|
|
scan.error_message = str(e)
|
|
scan.completed_at = datetime.utcnow()
|
|
await self.db.commit()
|
|
except:
|
|
pass
|
|
|
|
await ws_manager.broadcast_error(scan_id, error_msg)
|
|
await ws_manager.broadcast_log(scan_id, "error", f"ERROR: {error_msg}")
|
|
|
|
def _confidence_to_severity(self, confidence: float) -> str:
|
|
"""Convert confidence score to severity level"""
|
|
if confidence >= 0.9:
|
|
return "critical"
|
|
elif confidence >= 0.7:
|
|
return "high"
|
|
elif confidence >= 0.5:
|
|
return "medium"
|
|
else:
|
|
return "low"
|
|
|
|
async def _get_prompt_content(self, scan: Scan) -> str:
|
|
"""Get the prompt content for the scan"""
|
|
if scan.custom_prompt:
|
|
return scan.custom_prompt
|
|
|
|
if scan.prompt_id:
|
|
for preset in PRESET_PROMPTS:
|
|
if preset["id"] == scan.prompt_id:
|
|
return preset["content"]
|
|
|
|
from backend.models import Prompt
|
|
result = await self.db.execute(
|
|
select(Prompt).where(Prompt.id == scan.prompt_id)
|
|
)
|
|
prompt = result.scalar_one_or_none()
|
|
if prompt:
|
|
return prompt.content
|
|
|
|
return """Perform a comprehensive security assessment.
|
|
Test for all common vulnerabilities including:
|
|
- XSS (reflected, stored, DOM)
|
|
- SQL Injection (error, blind, time-based)
|
|
- Command Injection and RCE
|
|
- LFI/RFI and Path Traversal
|
|
- SSRF
|
|
- Authentication and Session issues
|
|
- Authorization flaws (IDOR, BOLA)
|
|
- Security misconfigurations
|
|
- API vulnerabilities
|
|
- Business logic flaws
|
|
|
|
Be thorough and test all discovered endpoints aggressively.
|
|
"""
|
|
|
|
def _merge_recon_data(self, base: Dict, new: Dict) -> Dict:
|
|
"""Merge recon data dictionaries"""
|
|
for key, value in new.items():
|
|
if key in base:
|
|
if isinstance(value, list):
|
|
base[key] = list(set(base[key] + value))
|
|
elif isinstance(value, dict):
|
|
base[key].update(value)
|
|
else:
|
|
base[key] = value
|
|
return base
|
|
|
|
async def _test_endpoint_with_ai(
|
|
self,
|
|
scan: Scan,
|
|
endpoint: Endpoint,
|
|
testing_plan,
|
|
engine: DynamicVulnerabilityEngine,
|
|
recon_data: Dict
|
|
):
|
|
"""Test an endpoint using AI-determined vulnerability types"""
|
|
import aiohttp
|
|
|
|
async def progress_callback(message: str):
|
|
await ws_manager.broadcast_log(scan.id, "debug", f" {message}")
|
|
|
|
for vuln_type in testing_plan.vulnerability_types:
|
|
if self._stop_requested:
|
|
break
|
|
|
|
try:
|
|
# Get payloads for this vulnerability type
|
|
payloads = await self.payload_generator.get_payloads(
|
|
vuln_type=vuln_type,
|
|
endpoint=endpoint,
|
|
context={"testing_plan": testing_plan.__dict__, "recon": recon_data}
|
|
)
|
|
|
|
if not payloads:
|
|
continue
|
|
|
|
# Test payloads
|
|
for payload in payloads[:5]: # Limit payloads per type
|
|
result = await self._execute_payload_test(
|
|
endpoint=endpoint,
|
|
vuln_type=vuln_type,
|
|
payload=payload,
|
|
scan=scan # Pass scan for authentication
|
|
)
|
|
|
|
if result and result.get("is_vulnerable"):
|
|
# Use AI to analyze and confirm
|
|
ai_analysis = await self.ai_analyzer.analyze_finding(
|
|
vuln_type=vuln_type,
|
|
request=result.get("request", {}),
|
|
response=result.get("response", {}),
|
|
payload=payload
|
|
)
|
|
|
|
confidence = ai_analysis.get("confidence", result.get("confidence", 0.5))
|
|
|
|
if confidence >= 0.5: # Lower threshold to catch more
|
|
# Create vulnerability record
|
|
vuln_severity = ai_analysis.get("severity", self._confidence_to_severity(confidence))
|
|
vuln = Vulnerability(
|
|
scan_id=scan.id,
|
|
title=f"{vuln_type.replace('_', ' ').title()} on {endpoint.path or endpoint.url}",
|
|
vulnerability_type=vuln_type,
|
|
severity=vuln_severity,
|
|
description=ai_analysis.get("evidence", result.get("evidence", "")),
|
|
affected_endpoint=endpoint.url,
|
|
poc_payload=payload,
|
|
poc_request=str(result.get("request", {}))[:5000],
|
|
poc_response=str(result.get("response", {}).get("body_preview", ""))[:5000],
|
|
remediation=ai_analysis.get("remediation", ""),
|
|
ai_analysis=ai_analysis.get("exploitation_path", "")
|
|
)
|
|
self.db.add(vuln)
|
|
await self.db.flush() # Ensure ID is assigned
|
|
|
|
# Increment vulnerability count
|
|
await self._increment_vulnerability_count(scan, vuln_severity)
|
|
|
|
await ws_manager.broadcast_vulnerability_found(scan.id, {
|
|
"id": vuln.id,
|
|
"title": vuln.title,
|
|
"severity": vuln.severity,
|
|
"type": vuln_type,
|
|
"endpoint": endpoint.url
|
|
})
|
|
await ws_manager.broadcast_log(
|
|
scan.id, "warning",
|
|
f" FOUND: {vuln.title} [{vuln.severity.upper()}]"
|
|
)
|
|
break # Found vulnerability, move to next type
|
|
|
|
except Exception as e:
|
|
await ws_manager.broadcast_log(scan.id, "debug", f" Error testing {vuln_type}: {str(e)}")
|
|
|
|
await self.db.commit()
|
|
|
|
def _build_auth_headers(self, scan: Scan) -> Dict[str, str]:
|
|
"""Build authentication headers from scan configuration"""
|
|
headers = {"User-Agent": "NeuroSploit/3.0"}
|
|
|
|
# Add custom headers
|
|
if scan.custom_headers:
|
|
headers.update(scan.custom_headers)
|
|
|
|
# Add authentication
|
|
if scan.auth_type and scan.auth_credentials:
|
|
creds = scan.auth_credentials
|
|
|
|
if scan.auth_type == "cookie" and "cookie" in creds:
|
|
headers["Cookie"] = creds["cookie"]
|
|
|
|
elif scan.auth_type == "bearer" and "bearer_token" in creds:
|
|
headers["Authorization"] = f"Bearer {creds['bearer_token']}"
|
|
|
|
elif scan.auth_type == "basic" and "username" in creds and "password" in creds:
|
|
import base64
|
|
credentials = f"{creds['username']}:{creds['password']}"
|
|
encoded = base64.b64encode(credentials.encode()).decode()
|
|
headers["Authorization"] = f"Basic {encoded}"
|
|
|
|
elif scan.auth_type == "header" and "header_name" in creds and "header_value" in creds:
|
|
headers[creds["header_name"]] = creds["header_value"]
|
|
|
|
return headers
|
|
|
|
async def _execute_payload_test(
|
|
self,
|
|
endpoint: Endpoint,
|
|
vuln_type: str,
|
|
payload: str,
|
|
scan: Optional[Scan] = None
|
|
) -> Optional[Dict]:
|
|
"""Execute a single payload test with optional authentication"""
|
|
import aiohttp
|
|
|
|
try:
|
|
# Determine where to inject payload
|
|
url = endpoint.url
|
|
params = {}
|
|
|
|
# Build headers with authentication if available
|
|
if scan:
|
|
headers = self._build_auth_headers(scan)
|
|
else:
|
|
headers = {"User-Agent": "NeuroSploit/3.0"}
|
|
|
|
if "?" in url:
|
|
base_url, query = url.split("?", 1)
|
|
for param in query.split("&"):
|
|
if "=" in param:
|
|
key, value = param.split("=", 1)
|
|
params[key] = payload # Inject into all params
|
|
url = base_url
|
|
else:
|
|
# Add payload as common parameter
|
|
params = {"q": payload, "search": payload, "id": payload, "page": payload}
|
|
|
|
timeout = aiohttp.ClientTimeout(total=15)
|
|
connector = aiohttp.TCPConnector(ssl=False)
|
|
|
|
async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
|
|
async with session.get(url, params=params, headers=headers, allow_redirects=False) as response:
|
|
body = await response.text()
|
|
|
|
# Basic vulnerability detection
|
|
is_vulnerable = False
|
|
confidence = 0.0
|
|
evidence = ""
|
|
|
|
if vuln_type in ["xss_reflected", "xss_stored"]:
|
|
if payload in body:
|
|
is_vulnerable = True
|
|
confidence = 0.7
|
|
evidence = "Payload reflected in response"
|
|
|
|
elif vuln_type in ["sqli_error", "sqli_blind"]:
|
|
error_patterns = ["sql", "mysql", "syntax error", "query", "oracle", "postgresql", "sqlite", "database", "odbc", "jdbc"]
|
|
body_lower = body.lower()
|
|
for pattern in error_patterns:
|
|
if pattern in body_lower:
|
|
is_vulnerable = True
|
|
confidence = 0.8
|
|
evidence = f"SQL error pattern found: {pattern}"
|
|
break
|
|
|
|
elif vuln_type == "lfi":
|
|
if "root:" in body or "[extensions]" in body or "boot.ini" in body.lower():
|
|
is_vulnerable = True
|
|
confidence = 0.9
|
|
evidence = "File content detected"
|
|
|
|
elif vuln_type == "command_injection":
|
|
if "uid=" in body or "bin/" in body or "Volume Serial" in body:
|
|
is_vulnerable = True
|
|
confidence = 0.9
|
|
evidence = "Command execution detected"
|
|
|
|
elif vuln_type == "open_redirect":
|
|
if response.status in [301, 302, 303, 307, 308]:
|
|
location = response.headers.get("Location", "")
|
|
if payload in location or "evil" in location.lower():
|
|
is_vulnerable = True
|
|
confidence = 0.7
|
|
evidence = f"Redirect to: {location}"
|
|
|
|
elif vuln_type == "ssti":
|
|
# Check for template injection markers
|
|
if "49" in body or "7777777" in body: # Common test: 7*7 or 7*7*7*7*7*7*7
|
|
is_vulnerable = True
|
|
confidence = 0.8
|
|
evidence = "Template execution detected"
|
|
|
|
return {
|
|
"is_vulnerable": is_vulnerable,
|
|
"confidence": confidence,
|
|
"evidence": evidence,
|
|
"request": {"url": url, "params": params, "payload": payload},
|
|
"response": {
|
|
"status": response.status,
|
|
"headers": dict(response.headers),
|
|
"body_preview": body[:2000]
|
|
}
|
|
}
|
|
|
|
except asyncio.TimeoutError:
|
|
# Timeout might indicate time-based injection
|
|
if vuln_type in ["sqli_blind", "sqli_time"]:
|
|
return {
|
|
"is_vulnerable": True,
|
|
"confidence": 0.6,
|
|
"evidence": "Request timed out - possible time-based injection",
|
|
"request": {"url": endpoint.url, "payload": payload},
|
|
"response": {"status": 0, "body_preview": "TIMEOUT"}
|
|
}
|
|
return None
|
|
|
|
except Exception as e:
|
|
return None
|
|
|
|
async def _update_vulnerability_counts(self, scan: Scan):
|
|
"""Update vulnerability counts in scan"""
|
|
from sqlalchemy import func
|
|
|
|
for severity in ["critical", "high", "medium", "low", "info"]:
|
|
result = await self.db.execute(
|
|
select(func.count()).select_from(Vulnerability)
|
|
.where(Vulnerability.scan_id == scan.id)
|
|
.where(Vulnerability.severity == severity)
|
|
)
|
|
count = result.scalar() or 0
|
|
setattr(scan, f"{severity}_count", count)
|
|
|
|
result = await self.db.execute(
|
|
select(func.count()).select_from(Vulnerability)
|
|
.where(Vulnerability.scan_id == scan.id)
|
|
)
|
|
scan.total_vulnerabilities = result.scalar() or 0
|
|
|
|
result = await self.db.execute(
|
|
select(func.count()).select_from(Endpoint)
|
|
.where(Endpoint.scan_id == scan.id)
|
|
)
|
|
scan.total_endpoints = result.scalar() or 0
|
|
|
|
await self.db.commit()
|
|
|
|
async def _increment_vulnerability_count(self, scan: Scan, severity: str):
|
|
"""Increment vulnerability count for a severity level and broadcast update"""
|
|
# Increment the appropriate counter
|
|
severity_lower = severity.lower()
|
|
if severity_lower in ["critical", "high", "medium", "low", "info"]:
|
|
current = getattr(scan, f"{severity_lower}_count", 0)
|
|
setattr(scan, f"{severity_lower}_count", current + 1)
|
|
scan.total_vulnerabilities = (scan.total_vulnerabilities or 0) + 1
|
|
await self.db.commit()
|
|
|
|
# Broadcast stats update
|
|
await ws_manager.broadcast_stats_update(scan.id, {
|
|
"total_vulnerabilities": scan.total_vulnerabilities,
|
|
"critical": scan.critical_count,
|
|
"high": scan.high_count,
|
|
"medium": scan.medium_count,
|
|
"low": scan.low_count,
|
|
"info": scan.info_count,
|
|
"total_endpoints": scan.total_endpoints
|
|
})
|