Add files via upload

This commit is contained in:
Joas A Santos
2026-01-19 19:21:57 -03:00
committed by GitHub
parent b966ba658a
commit 5a8a1fc0d7
57 changed files with 17928 additions and 0 deletions
+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()