diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1f76ad8 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# Environment Configuration for StrikePackageGPT +# Copy this file to .env and fill in your values + +# LLM API Keys (optional - Ollama works without API keys) +OPENAI_API_KEY= +ANTHROPIC_API_KEY= + +# Ollama Configuration +OLLAMA_BASE_URL=http://ollama:11434 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c11cff2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Environment files +.env + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +.venv/ +ENV/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Docker +*.log + +# Data directories (may contain sensitive info) +data/ +*.db + +# Temporary files +*.tmp +*.temp diff --git a/Claude.md b/Claude.md new file mode 100644 index 0000000..116c0a7 --- /dev/null +++ b/Claude.md @@ -0,0 +1,220 @@ +# StrikePackageGPT - Development Guidelines + +## Project Overview + +StrikePackageGPT is an AI-powered security analysis platform combining LLM capabilities with professional penetration testing tools. It provides a web interface for security researchers and penetration testers to interact with AI assistants specialized in cybersecurity. + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Dashboard (8080) │ +│ FastAPI + Jinja2 Templates │ +│ Tabbed UI: Chat | Terminal | Scans │ +└──────────────────────────┬──────────────────────────────────────┘ + │ +┌──────────────────────────▼──────────────────────────────────────┐ +│ HackGPT API (8001) │ +│ Security-focused API endpoints │ +│ Chat, Scans, Execute, Analyze, AI-Scan │ +└───────────────┬─────────────────────────┬───────────────────────┘ + │ │ +┌───────────────▼──────────┐ ┌───────────▼───────────────────────┐ +│ LLM Router (8000) │ │ Kali Executor (8002) │ +│ OpenAI/Anthropic/Ollama │ │ Docker SDK command execution │ +└───────────────┬──────────┘ └───────────┬───────────────────────┘ + │ │ +┌───────────────▼──────────┐ ┌───────────▼───────────────────────┐ +│ Ollama (11434) │ │ Kali Container │ +│ Local LLM inference │ │ nmap, nikto, sqlmap, etc. │ +└──────────────────────────┘ └───────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ Shared Library │ +│ models.py | parsers.py | tools.py │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Data Flow + +1. **Chat Flow**: User → Dashboard → HackGPT API → LLM Router → Ollama/OpenAI/Anthropic +2. **Scan Flow**: User → Dashboard → HackGPT API → Kali Executor → Kali Container +3. **AI-Scan Flow**: Combines both - AI plans the scan, Executor runs it, AI analyzes results + +## Services + +### Dashboard (`services/dashboard/`) +- **Port**: 8080 +- **Purpose**: Web UI for interacting with the security AI +- **Tech**: FastAPI, Jinja2, TailwindCSS, Alpine.js +- **Key files**: + - `app/main.py` - FastAPI application + - `templates/index.html` - Main dashboard UI + +### HackGPT API (`services/hackgpt-api/`) +- **Port**: 8001 +- **Purpose**: Security-focused API with specialized prompts +- **Tech**: FastAPI +- **Key endpoints**: + - `POST /chat` - Security chat interface with session support + - `POST /analyze` - Start async security analysis tasks + - `POST /execute` - Execute commands in Kali (proxied) + - `POST /scan` - Run security scans (nmap, nikto, etc.) + - `POST /ai-scan` - AI-driven intelligent scanning + - `GET /scans` - List all scans with status + - `GET /tools` - List available security tools + - `POST /suggest-command` - AI-suggested security commands + +### LLM Router (`services/llm-router/`) +- **Port**: 8000 +- **Purpose**: Route requests to different LLM providers +- **Tech**: FastAPI, httpx +- **Supported providers**: + - OpenAI (gpt-4o, gpt-4o-mini) + - Anthropic (Claude Sonnet 4, Claude 3.5 Haiku) + - Ollama (local models - llama3.2, codellama, mistral) + +### Kali (`services/kali/`) +- **Purpose**: Container with security tools +- **Base**: kalilinux/kali-rolling +- **Tools included**: + - Reconnaissance: nmap, masscan, amass, theHarvester + - Web: nikto, gobuster, sqlmap + - Exploitation: metasploit, hydra, searchsploit + +### Kali Executor (`services/kali-executor/`) +- **Port**: 8002 +- **Purpose**: Execute commands in the Kali container via Docker SDK +- **Tech**: FastAPI, Docker SDK, WebSockets +- **Key endpoints**: + - `POST /execute` - Execute a command (with whitelist validation) + - `WS /stream` - WebSocket for real-time command output + - `GET /jobs` - List running/completed jobs + - `GET /tools` - List available security tools +- **Security**: Command whitelist restricts executable binaries + +### Shared Library (`services/shared/`) +- **Purpose**: Common models and utilities shared across services +- **Files**: + - `models.py` - Pydantic models (ScanResult, CommandResult, etc.) + - `parsers.py` - Output parsers for nmap, nikto, etc. + - `tools.py` - Security tool definitions and templates + +## Development Commands + +```bash +# Start all services +docker-compose up -d + +# Start with build +docker-compose up -d --build + +# View logs +docker-compose logs -f + +# View specific service logs +docker-compose logs -f dashboard + +# Stop all services +docker-compose down + +# Rebuild specific service +docker-compose build dashboard + +# Access Kali container +docker exec -it strikepackage-kali bash + +# Pull Ollama model (run after first start) +docker exec -it strikepackage-ollama ollama pull llama3.2 +``` + +## Environment Variables + +Copy `.env.example` to `.env` and configure: + +```bash +# Optional - Ollama works without these +OPENAI_API_KEY=sk-... +ANTHROPIC_API_KEY=sk-ant-... + +# Ollama (default works for Docker network) +OLLAMA_BASE_URL=http://ollama:11434 +``` + +## Code Style Guidelines + +### Python +- Use type hints for all function parameters and returns +- Use Pydantic models for request/response validation +- Async functions for I/O operations +- Follow PEP 8 naming conventions +- Use docstrings for public functions + +### API Design +- RESTful endpoints with clear naming +- Health check endpoint at `/health` for each service +- Consistent error responses with HTTPException +- CORS enabled for all services + +### Frontend +- Alpine.js for reactivity +- TailwindCSS for styling +- Marked.js for Markdown rendering +- Responsive design + +## Adding New Features + +### New LLM Provider +1. Add configuration to `LLM_ROUTER_URL` in `llm-router/app/main.py` +2. Implement `_call_` async function +3. Add provider to `/providers` endpoint +4. Update `ChatRequest` model if needed + +### New Security Tool +1. Install in `kali/Dockerfile` +2. Add to tool list in `hackgpt-api/app/main.py` `/tools` endpoint +3. Add relevant prompts to `SECURITY_PROMPTS` dict + +### New Analysis Type +1. Add prompt to `SECURITY_PROMPTS` in `hackgpt-api/app/main.py` +2. Add button in `dashboard/templates/index.html` +3. Update `SecurityAnalysisRequest` model if needed + +## Security Considerations + +- This platform is for **authorized security testing only** +- Always obtain proper authorization before testing +- The Kali container has elevated network capabilities +- API keys are passed via environment variables +- No authentication is implemented by default (add for production) + +## Troubleshooting + +### Ollama not responding +```bash +# Check if Ollama is running +docker-compose logs ollama + +# Pull a model if none exist +docker exec -it strikepackage-ollama ollama pull llama3.2 +``` + +### Service connection issues +```bash +# Check network +docker network ls +docker network inspect strikepackagegpt_strikepackage-net + +# Check service health +curl http://localhost:8000/health +curl http://localhost:8001/health +curl http://localhost:8080/health +``` + +### Build issues +```bash +# Clean rebuild +docker-compose down +docker-compose build --no-cache +docker-compose up -d +``` \ No newline at end of file diff --git a/README.md b/README.md index a18e997..fab26c1 100644 --- a/README.md +++ b/README.md @@ -1 +1,150 @@ -# StrikePackageGPT \ No newline at end of file +# ⚡ StrikePackageGPT + +AI-powered security analysis platform combining LLM capabilities with professional penetration testing tools. + +![License](https://img.shields.io/badge/license-MIT-blue.svg) +![Python](https://img.shields.io/badge/python-3.12-blue.svg) +![Docker](https://img.shields.io/badge/docker-ready-blue.svg) + +## 🎯 Overview + +StrikePackageGPT provides security researchers and penetration testers with an AI assistant specialized in: + +- **Reconnaissance** - OSINT, subdomain enumeration, port scanning strategies +- **Vulnerability Analysis** - CVE research, misconfiguration detection +- **Exploit Research** - Safe research and documentation of exploits +- **Report Generation** - Professional security assessment reports + +## 🚀 Quick Start + +### Prerequisites + +- Docker & Docker Compose +- 8GB+ RAM recommended (for local LLM) +- (Optional) OpenAI or Anthropic API key + +### Installation + +1. **Clone the repository** + ```bash + git clone https://github.com/mblanke/StrikePackageGPT.git + cd StrikePackageGPT + ``` + +2. **Configure environment** (optional) + ```bash + cp .env.example .env + # Edit .env to add API keys if using cloud LLMs + ``` + +3. **Start the services** + ```bash + docker-compose up -d + ``` + +4. **Pull a local model** (first time only) + ```bash + docker exec -it strikepackage-ollama ollama pull llama3.2 + ``` + +5. **Access the dashboard** + + Open http://localhost:8080 in your browser + +## 📦 Services + +| Service | Port | Description | +|---------|------|-------------| +| Dashboard | 8080 | Web UI with Chat, Terminal, and Scans tabs | +| HackGPT API | 8001 | Security-focused API with scan management | +| Kali Executor | 8002 | Docker SDK command execution | +| LLM Router | 8000 | Multi-provider LLM gateway | +| Ollama | 11434 | Local LLM inference | +| Kali | - | Security tools container | + +## 🛠️ Security Tools + +The Kali container includes: + +- **Reconnaissance**: nmap, masscan, amass, theHarvester, whatweb +- **Web Testing**: nikto, gobuster, dirb, sqlmap +- **Exploitation**: metasploit-framework, hydra, searchsploit +- **Network**: tcpdump, netcat, wireshark + +Access the Kali container: +```bash +docker exec -it strikepackage-kali bash +``` + +## 🤖 LLM Providers + +StrikePackageGPT supports multiple LLM providers: + +| Provider | Models | API Key Required | +|----------|--------|------------------| +| Ollama | llama3.2, codellama, mistral | No (local) | +| OpenAI | gpt-4o, gpt-4o-mini | Yes | +| Anthropic | claude-sonnet-4-20250514, claude-3-5-haiku | Yes | + +## 📖 Usage Examples + +### Chat with the AI +Ask security-related questions in natural language: +- "Explain how to use nmap for service detection" +- "What are common web application vulnerabilities?" +- "How do I enumerate subdomains for a target?" + +### Terminal Access +Execute commands directly in the Kali container from the Terminal tab: +- Real-time command output +- Command history with up/down arrows +- Whitelisted tools for security + +### Security Scans +Launch and monitor scans from the Scans tab: +- **nmap** - Port scanning and service detection +- **nikto** - Web server vulnerability scanning +- **gobuster** - Directory and DNS enumeration +- **sqlmap** - SQL injection testing +- **whatweb** - Web technology fingerprinting + +### Quick Analysis +Use the sidebar buttons to start guided analysis: +- 🔍 **Reconnaissance** - Plan your information gathering +- 🛡️ **Vulnerability Scan** - Assess potential weaknesses +- 💉 **Exploit Research** - Research known vulnerabilities +- 📄 **Generate Report** - Create professional documentation + +## ⚠️ Legal Disclaimer + +This tool is intended for **authorized security testing only**. Always: + +- Obtain written permission before testing any systems +- Follow responsible disclosure practices +- Comply with all applicable laws and regulations +- Use in isolated lab environments when learning + +The developers are not responsible for misuse of this software. + +## 🔧 Development + +See [Claude.md](./Claude.md) for development guidelines. + +```bash +# Rebuild after changes +docker-compose up -d --build + +# View logs +docker-compose logs -f + +# Stop all services +docker-compose down +``` + +## 📄 License + +MIT License - See [LICENSE](./LICENSE) for details. + +## 🤝 Contributing + +Contributions welcome! Please read the development guidelines in Claude.md before submitting PRs. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9b00aec --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,118 @@ +services: + # Web Dashboard - Main user interface + dashboard: + build: + context: ./services/dashboard + dockerfile: Dockerfile + container_name: strikepackage-dashboard + ports: + - "8080:8080" + environment: + - HACKGPT_API_URL=http://strikepackage-hackgpt-api:8001 + - LLM_ROUTER_URL=http://strikepackage-llm-router:8000 + - KALI_EXECUTOR_URL=http://strikepackage-kali-executor:8002 + depends_on: + - hackgpt-api + - llm-router + networks: + - strikepackage-net + restart: unless-stopped + + # HackGPT API - Security-focused API service + hackgpt-api: + build: + context: ./services/hackgpt-api + dockerfile: Dockerfile + container_name: strikepackage-hackgpt-api + ports: + - "8001:8001" + environment: + - LLM_ROUTER_URL=http://strikepackage-llm-router:8000 + - KALI_EXECUTOR_URL=http://strikepackage-kali-executor:8002 + depends_on: + - llm-router + - kali-executor + networks: + - strikepackage-net + restart: unless-stopped + + # Kali Executor - Command execution service + kali-executor: + build: + context: ./services/kali-executor + dockerfile: Dockerfile + container_name: strikepackage-kali-executor + ports: + - "8002:8002" + environment: + - KALI_CONTAINER_NAME=strikepackage-kali + volumes: + - /var/run/docker.sock:/var/run/docker.sock + depends_on: + - kali + networks: + - strikepackage-net + restart: unless-stopped + + # LLM Router - Routes to different LLM providers + llm-router: + build: + context: ./services/llm-router + dockerfile: Dockerfile + container_name: strikepackage-llm-router + ports: + - "8000:8000" + environment: + - OPENAI_API_KEY=${OPENAI_API_KEY:-} + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} + - OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://192.168.1.50:11434} + networks: + - strikepackage-net + restart: unless-stopped + + # Kali Linux - Security tools container + kali: + build: + context: ./services/kali + dockerfile: Dockerfile + container_name: strikepackage-kali + stdin_open: true + tty: true + volumes: + - kali-workspace:/workspace + - ./data:/data + networks: + - strikepackage-net + cap_add: + - NET_ADMIN + - NET_RAW + restart: unless-stopped + + # Ollama - Local LLM (disabled - using Dell LLM box at 192.168.1.50) + # Uncomment to use local Ollama instead + # ollama: + # image: ollama/ollama:latest + # container_name: strikepackage-ollama + # ports: + # - "11434:11434" + # volumes: + # - ollama-models:/root/.ollama + # networks: + # - strikepackage-net + # restart: unless-stopped + # # Uncomment for GPU support: + # # deploy: + # # resources: + # # reservations: + # # devices: + # # - driver: nvidia + # # count: all + # # capabilities: [gpu] + +networks: + strikepackage-net: + driver: bridge + +volumes: + kali-workspace: + ollama-models: \ No newline at end of file diff --git a/scripts/init.sh b/scripts/init.sh new file mode 100644 index 0000000..7e1d6a1 --- /dev/null +++ b/scripts/init.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +echo "==================================================" +echo " StrikePackageGPT - Initialization Script" +echo "==================================================" +echo "" + +# Check for Docker +if ! command -v docker &> /dev/null; then + echo "❌ Docker is not installed. Please install Docker first." + exit 1 +fi + +if ! command -v docker-compose &> /dev/null; then + echo "❌ Docker Compose is not installed. Please install Docker Compose first." + exit 1 +fi + +echo "✅ Docker and Docker Compose are installed" + +# Create .env if not exists +if [ ! -f .env ]; then + echo "📄 Creating .env file from template..." + cp .env.example .env + echo "✅ Created .env file. Edit it to add API keys if needed." +else + echo "✅ .env file already exists" +fi + +# Create data directory +mkdir -p data +echo "✅ Created data directory" + +# Start services +echo "" +echo "🚀 Starting services..." +docker-compose up -d --build + +# Wait for services to be ready +echo "" +echo "⏳ Waiting for services to start..." +sleep 10 + +# Check service health +echo "" +echo "🔍 Checking service health..." + +check_service() { + local url=$1 + local name=$2 + if curl -s "$url" > /dev/null 2>&1; then + echo " ✅ $name is healthy" + return 0 + else + echo " ⏳ $name is starting..." + return 1 + fi +} + +check_service "http://localhost:8000/health" "LLM Router" +check_service "http://localhost:8001/health" "HackGPT API" +check_service "http://localhost:8080/health" "Dashboard" + +# Pull default Ollama model +echo "" +echo "📥 Pulling default LLM model (llama3.2)..." +echo " This may take a few minutes on first run..." +docker exec strikepackage-ollama ollama pull llama3.2 + +echo "" +echo "==================================================" +echo " ✅ StrikePackageGPT is ready!" +echo "==================================================" +echo "" +echo " Dashboard: http://localhost:8080" +echo " API Docs: http://localhost:8001/docs" +echo " LLM Router: http://localhost:8000/docs" +echo "" +echo " To access Kali container:" +echo " docker exec -it strikepackage-kali bash" +echo "" +echo " To view logs:" +echo " docker-compose logs -f" +echo "" \ No newline at end of file diff --git a/scripts/preflight.ps1 b/scripts/preflight.ps1 new file mode 100644 index 0000000..3d70eed --- /dev/null +++ b/scripts/preflight.ps1 @@ -0,0 +1,88 @@ +# StrikePackageGPT Pre-flight Check + +$AllPassed = $true + +function Show-Check { + param([string]$Name, [bool]$Passed, [string]$Message) + if ($Passed) { + Write-Host " [OK] $Name" -ForegroundColor Green + if ($Message) { Write-Host " $Message" -ForegroundColor DarkGray } + } else { + Write-Host " [X] $Name" -ForegroundColor Red + if ($Message) { Write-Host " $Message" -ForegroundColor Yellow } + $script:AllPassed = $false + } +} + +Write-Host "" +Write-Host "========================================================" -ForegroundColor Cyan +Write-Host " StrikePackageGPT Pre-flight Check " -ForegroundColor Cyan +Write-Host "========================================================" -ForegroundColor Cyan +Write-Host "" + +# Hardware +Write-Host " HARDWARE" -ForegroundColor White +Write-Host " --------" -ForegroundColor DarkGray +$os = Get-CimInstance Win32_OperatingSystem +$ram = [math]::Round($os.TotalVisibleMemorySize / 1MB, 1) +Show-Check "RAM: ${ram}GB" ($ram -ge 8) "" +$disk = [math]::Round((Get-PSDrive (Get-Location).Drive.Name).Free / 1GB, 1) +Show-Check "Disk: ${disk}GB free" ($disk -ge 20) "" + +Write-Host "" + +# Docker +Write-Host " DOCKER" -ForegroundColor White +Write-Host " ------" -ForegroundColor DarkGray +$dockerOk = $null -ne (Get-Command docker -ErrorAction SilentlyContinue) +Show-Check "Docker installed" $dockerOk "" +$dockerRun = $false +if ($dockerOk) { try { docker info 2>$null | Out-Null; $dockerRun = $true } catch {} } +Show-Check "Docker running" $dockerRun "" + +Write-Host "" + +# Containers +Write-Host " CONTAINERS" -ForegroundColor White +Write-Host " ----------" -ForegroundColor DarkGray +if ($dockerRun) { + $containers = @("dashboard","hackgpt-api","llm-router","kali","kali-executor") + foreach ($c in $containers) { + $name = "strikepackage-$c" + $status = docker ps --filter "name=$name" --format "{{.Status}}" 2>$null + if ($status) { + Show-Check $name $true $status + } else { + Write-Host " [ ] $name - Not running" -ForegroundColor DarkGray + } + } +} + +Write-Host "" + +# Ollama +Write-Host " OLLAMA" -ForegroundColor White +Write-Host " ------" -ForegroundColor DarkGray +try { + $r = Invoke-RestMethod -Uri "http://localhost:11434/api/tags" -TimeoutSec 3 -ErrorAction Stop + $m = ($r.models | ForEach-Object { $_.name }) -join ", " + Show-Check "Local Ollama" $true $m +} catch { + Write-Host " [ ] Local Ollama - Not running" -ForegroundColor DarkGray +} +try { + $r = Invoke-RestMethod -Uri "http://192.168.1.50:11434/api/tags" -TimeoutSec 3 -ErrorAction Stop + $m = ($r.models | ForEach-Object { $_.name }) -join ", " + Show-Check "Dell LLM Box" $true $m +} catch { + Write-Host " [ ] Dell LLM Box - Not reachable" -ForegroundColor DarkGray +} + +Write-Host "" +Write-Host "========================================================" -ForegroundColor DarkGray +if ($AllPassed) { + Write-Host " ALL CHECKS PASSED!" -ForegroundColor Green +} else { + Write-Host " Some checks failed" -ForegroundColor Yellow +} +Write-Host "" diff --git a/services/dashboard/Dockerfile b/services/dashboard/Dockerfile new file mode 100644 index 0000000..7bc4d1e --- /dev/null +++ b/services/dashboard/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.12-slim + +WORKDIR /app + +# Install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY app/ ./app/ +COPY templates/ ./templates/ +COPY static/ ./static/ + +# Expose port +EXPOSE 8080 + +# Run the application +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"] \ No newline at end of file diff --git a/services/dashboard/app/__init__.py b/services/dashboard/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/dashboard/app/main.py b/services/dashboard/app/main.py new file mode 100644 index 0000000..10a88d5 --- /dev/null +++ b/services/dashboard/app/main.py @@ -0,0 +1,368 @@ +""" +StrikePackageGPT Dashboard +Web interface for security analysis and LLM-powered penetration testing assistant. +""" +from fastapi import FastAPI, Request, HTTPException, WebSocket, WebSocketDisconnect +from fastapi.responses import HTMLResponse +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel, Field +from typing import Optional, Dict, Any, List +import httpx +import os +import json + +app = FastAPI( + title="StrikePackageGPT Dashboard", + description="Web interface for AI-powered security analysis", + version="0.2.0" +) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Configuration +HACKGPT_API_URL = os.getenv("HACKGPT_API_URL", "http://strikepackage-hackgpt-api:8001") +LLM_ROUTER_URL = os.getenv("LLM_ROUTER_URL", "http://strikepackage-llm-router:8000") +KALI_EXECUTOR_URL = os.getenv("KALI_EXECUTOR_URL", "http://strikepackage-kali-executor:8002") + +# Static files +app.mount("/static", StaticFiles(directory="static"), name="static") + +# Templates +templates = Jinja2Templates(directory="templates") + + +class ChatMessage(BaseModel): + message: str + session_id: Optional[str] = None + provider: str = "ollama" + model: str = "llama3.2" + context: Optional[str] = None + + +class PhaseChatMessage(BaseModel): + message: str + phase: str + provider: str = "ollama" + model: str = "llama3.2" + findings: List[Dict[str, Any]] = Field(default_factory=list) + + +class AttackChainRequest(BaseModel): + findings: List[Dict[str, Any]] + provider: str = "ollama" + model: str = "llama3.2" + + +class CommandRequest(BaseModel): + command: str + timeout: int = Field(default=300, ge=1, le=3600) + working_dir: str = "/workspace" + + +class ScanRequest(BaseModel): + tool: str + target: str + scan_type: Optional[str] = None + options: Dict[str, Any] = Field(default_factory=dict) + + +@app.get("/health") +async def health_check(): + """Health check endpoint""" + return {"status": "healthy", "service": "dashboard"} + + +@app.get("/", response_class=HTMLResponse) +async def index(request: Request): + """Main dashboard page""" + return templates.TemplateResponse("index.html", {"request": request}) + + +@app.get("/terminal", response_class=HTMLResponse) +async def terminal_page(request: Request): + """Terminal page""" + return templates.TemplateResponse("terminal.html", {"request": request}) + + +@app.get("/api/status") +async def get_services_status(): + """Get status of all backend services""" + services = {} + + service_checks = [ + ("llm-router", f"{LLM_ROUTER_URL}/health"), + ("hackgpt-api", f"{HACKGPT_API_URL}/health"), + ("kali-executor", f"{KALI_EXECUTOR_URL}/health"), + ] + + async with httpx.AsyncClient() as client: + for name, url in service_checks: + try: + response = await client.get(url, timeout=5.0) + services[name] = response.status_code == 200 + except: + services[name] = False + + return {"services": services} + + +@app.get("/api/processes") +async def get_running_processes(): + """Get running security processes in Kali container""" + try: + async with httpx.AsyncClient() as client: + response = await client.get(f"{KALI_EXECUTOR_URL}/processes", timeout=10.0) + if response.status_code == 200: + return response.json() + return {"running_processes": [], "count": 0} + except: + return {"running_processes": [], "count": 0} + + +@app.get("/api/providers") +async def get_providers(): + """Get available LLM providers""" + try: + async with httpx.AsyncClient() as client: + response = await client.get(f"{LLM_ROUTER_URL}/providers", timeout=10.0) + if response.status_code == 200: + return response.json() + raise HTTPException(status_code=response.status_code, detail="Failed to get providers") + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="LLM Router not available") + + +@app.get("/api/tools") +async def get_tools(): + """Get available security tools""" + try: + async with httpx.AsyncClient() as client: + response = await client.get(f"{HACKGPT_API_URL}/tools", timeout=10.0) + if response.status_code == 200: + return response.json() + raise HTTPException(status_code=response.status_code, detail="Failed to get tools") + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="HackGPT API not available") + + +@app.post("/api/chat") +async def chat(message: ChatMessage): + """Send chat message to HackGPT API""" + try: + async with httpx.AsyncClient() as client: + response = await client.post( + f"{HACKGPT_API_URL}/chat", + json=message.model_dump(), + timeout=120.0 + ) + if response.status_code == 200: + return response.json() + raise HTTPException(status_code=response.status_code, detail=response.text) + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="HackGPT API not available") + + +@app.post("/api/chat/phase") +async def phase_chat(message: PhaseChatMessage): + """Send phase-aware chat message to HackGPT API""" + try: + async with httpx.AsyncClient() as client: + response = await client.post( + f"{HACKGPT_API_URL}/chat/phase", + json=message.model_dump(), + timeout=120.0 + ) + if response.status_code == 200: + return response.json() + raise HTTPException(status_code=response.status_code, detail=response.text) + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="HackGPT API not available") + + +@app.post("/api/attack-chains") +async def analyze_attack_chains(request: AttackChainRequest): + """Analyze findings to identify attack chains""" + try: + async with httpx.AsyncClient() as client: + response = await client.post( + f"{HACKGPT_API_URL}/attack-chains", + json=request.model_dump(), + timeout=120.0 + ) + if response.status_code == 200: + return response.json() + raise HTTPException(status_code=response.status_code, detail=response.text) + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="HackGPT API not available") + + +@app.post("/api/analyze") +async def analyze(request: Request): + """Start security analysis""" + data = await request.json() + try: + async with httpx.AsyncClient() as client: + response = await client.post( + f"{HACKGPT_API_URL}/analyze", + json=data, + timeout=30.0 + ) + if response.status_code == 200: + return response.json() + raise HTTPException(status_code=response.status_code, detail=response.text) + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="HackGPT API not available") + + +@app.get("/api/task/{task_id}") +async def get_task(task_id: str): + """Get task status""" + try: + async with httpx.AsyncClient() as client: + response = await client.get(f"{HACKGPT_API_URL}/task/{task_id}", timeout=10.0) + if response.status_code == 200: + return response.json() + raise HTTPException(status_code=response.status_code, detail=response.text) + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="HackGPT API not available") + + +@app.post("/api/suggest-command") +async def suggest_command(message: ChatMessage): + """Get AI-suggested security commands""" + try: + async with httpx.AsyncClient() as client: + response = await client.post( + f"{HACKGPT_API_URL}/suggest-command", + json=message.model_dump(), + timeout=60.0 + ) + if response.status_code == 200: + return response.json() + raise HTTPException(status_code=response.status_code, detail=response.text) + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="HackGPT API not available") + + +# ============== Command Execution ============== + +@app.post("/api/execute") +async def execute_command(request: CommandRequest): + """Execute a command in the Kali container""" + try: + async with httpx.AsyncClient() as client: + response = await client.post( + f"{HACKGPT_API_URL}/execute", + json=request.model_dump(), + timeout=float(request.timeout + 30) + ) + if response.status_code == 200: + return response.json() + raise HTTPException(status_code=response.status_code, detail=response.text) + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="HackGPT API not available") + except httpx.TimeoutException: + raise HTTPException(status_code=504, detail="Command execution timed out") + + +# ============== Scan Management ============== + +@app.post("/api/scan") +async def start_scan(request: ScanRequest): + """Start a security scan""" + try: + async with httpx.AsyncClient() as client: + response = await client.post( + f"{HACKGPT_API_URL}/scan", + json=request.model_dump(), + timeout=30.0 + ) + if response.status_code == 200: + return response.json() + raise HTTPException(status_code=response.status_code, detail=response.text) + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="HackGPT API not available") + + +@app.get("/api/scan/{scan_id}") +async def get_scan_result(scan_id: str): + """Get scan results""" + try: + async with httpx.AsyncClient() as client: + response = await client.get(f"{HACKGPT_API_URL}/scan/{scan_id}", timeout=10.0) + if response.status_code == 200: + return response.json() + raise HTTPException(status_code=response.status_code, detail=response.text) + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="HackGPT API not available") + + +@app.get("/api/scans") +async def list_scans(): + """List all scans""" + try: + async with httpx.AsyncClient() as client: + response = await client.get(f"{HACKGPT_API_URL}/scans", timeout=10.0) + if response.status_code == 200: + return response.json() + raise HTTPException(status_code=response.status_code, detail=response.text) + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="HackGPT API not available") + + +@app.post("/api/ai-scan") +async def ai_scan(message: ChatMessage): + """AI-assisted scanning""" + try: + async with httpx.AsyncClient() as client: + response = await client.post( + f"{HACKGPT_API_URL}/ai-scan", + json=message.model_dump(), + timeout=120.0 + ) + if response.status_code == 200: + return response.json() + raise HTTPException(status_code=response.status_code, detail=response.text) + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="HackGPT API not available") + + +# ============== Kali Container Info ============== + +@app.get("/api/kali/info") +async def get_kali_info(): + """Get Kali container information""" + try: + async with httpx.AsyncClient() as client: + response = await client.get(f"{KALI_EXECUTOR_URL}/container/info", timeout=10.0) + if response.status_code == 200: + return response.json() + raise HTTPException(status_code=response.status_code, detail=response.text) + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="Kali executor not available") + + +@app.get("/api/kali/tools") +async def get_kali_tools(): + """Get installed tools in Kali container""" + try: + async with httpx.AsyncClient() as client: + response = await client.get(f"{KALI_EXECUTOR_URL}/tools", timeout=30.0) + if response.status_code == 200: + return response.json() + raise HTTPException(status_code=response.status_code, detail=response.text) + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="Kali executor not available") + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8080) \ No newline at end of file diff --git a/services/dashboard/requirements.txt b/services/dashboard/requirements.txt new file mode 100644 index 0000000..2edfc5f --- /dev/null +++ b/services/dashboard/requirements.txt @@ -0,0 +1,5 @@ +fastapi==0.115.5 +uvicorn[standard]==0.32.1 +httpx==0.28.1 +pydantic==2.10.2 +jinja2==3.1.4 diff --git a/services/dashboard/static/README.md b/services/dashboard/static/README.md new file mode 100644 index 0000000..d2e9a5d --- /dev/null +++ b/services/dashboard/static/README.md @@ -0,0 +1,8 @@ +# Static Assets + +Place your custom assets here: + +- `icon.png` - Your StrikePackageGPT logo/icon (recommended: 64x64 or 128x128) +- `flag.png` - Canadian flag image (recommended: ~32px height) + +These will appear in the dashboard header. diff --git a/services/dashboard/templates/index.html b/services/dashboard/templates/index.html new file mode 100644 index 0000000..bab32a3 --- /dev/null +++ b/services/dashboard/templates/index.html @@ -0,0 +1,946 @@ + + + + + + StrikePackageGPT - Security Analysis Dashboard + + + + + + + + +
+ +
+
+
+
+ StrikePackageGPT +
+

StrikePackageGPT

+ AI-Powered Penetration Testing Platform +
+
+ Canada +
+
+
+ + +
+
+ +
+ + +
+
+ +
+

🔄 Running Processes

+
+ +
+
+ + +
+ +
+ + +
+
+ + +
+ + + + +
+ + +
+
+ + +
+
+
+ + + +
+
+
+
+ +
+
+ Scans: + +
+
+ +
+
+ + +
+
+
+ + +
+
+
+ +
+ Executing command... +
+
+
+ +
+
+
+ kali@strikepackage:~$ +
+ + +
+
+
+ + +
+
+
+

⛓️ Attack Chain Analysis

+

Correlated vulnerabilities and potential attack paths

+
+ +
+ +
+

⛓️

+

No attack chains detected yet.

+

Complete reconnaissance and vulnerability scanning to identify potential attack paths.

+
+ +
+ +
+
+
+
+ + +
+
+

Start Scan

+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+
+
+ + +
+
+
+

Scan Details

+ +
+ + +
+
+
+ + + + diff --git a/services/hackgpt-api/Dockerfile b/services/hackgpt-api/Dockerfile new file mode 100644 index 0000000..db041ba --- /dev/null +++ b/services/hackgpt-api/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.12-slim + +WORKDIR /app + +# Install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY app/ ./app/ + +# Expose port +EXPOSE 8001 + +# Run the application +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8001"] \ No newline at end of file diff --git a/services/hackgpt-api/app/__init__.py b/services/hackgpt-api/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/hackgpt-api/app/main.py b/services/hackgpt-api/app/main.py new file mode 100644 index 0000000..35bbff8 --- /dev/null +++ b/services/hackgpt-api/app/main.py @@ -0,0 +1,1018 @@ +""" +HackGPT API Service +Security-focused API that interfaces with LLM router and Kali container +for penetration testing and security analysis tasks. +""" +from fastapi import FastAPI, HTTPException, BackgroundTasks, WebSocket, WebSocketDisconnect +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel, Field +from typing import Optional, Literal, List, Dict, Any +import httpx +import asyncio +import os +import uuid +import json +from datetime import datetime + +app = FastAPI( + title="HackGPT API", + description="AI-powered security analysis and penetration testing assistant", + version="0.2.0" +) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Configuration +LLM_ROUTER_URL = os.getenv("LLM_ROUTER_URL", "http://strikepackage-llm-router:8000") +KALI_EXECUTOR_URL = os.getenv("KALI_EXECUTOR_URL", "http://strikepackage-kali-executor:8002") + +# In-memory storage (use Redis in production) +tasks: Dict[str, Any] = {} +sessions: Dict[str, Dict] = {} +scan_results: Dict[str, Any] = {} + + +# ============== Models ============== + +class ChatMessage(BaseModel): + role: Literal["system", "user", "assistant"] + content: str + timestamp: Optional[datetime] = None + + +class ChatRequest(BaseModel): + message: str + session_id: Optional[str] = None + context: Optional[str] = None + provider: str = "ollama" + model: str = "llama3.2" + + +class PhaseChatRequest(BaseModel): + message: str + phase: str + provider: str = "ollama" + model: str = "llama3.2" + findings: List[Dict[str, Any]] = [] + + +class AttackChainRequest(BaseModel): + findings: List[Dict[str, Any]] + provider: str = "ollama" + model: str = "llama3.2" + + +class CommandRequest(BaseModel): + command: str + timeout: int = Field(default=300, ge=1, le=3600) + working_dir: str = "/workspace" + parse_output: bool = True + + +class ScanRequest(BaseModel): + tool: str + target: str + scan_type: Optional[str] = None + options: Dict[str, Any] = Field(default_factory=dict) + + +class SecurityAnalysisRequest(BaseModel): + target: str + analysis_type: Literal["recon", "vulnerability", "exploit_research", "report"] + options: Optional[dict] = None + + +class TaskStatus(BaseModel): + task_id: str + status: Literal["pending", "running", "completed", "failed"] + result: Optional[Any] = None + error: Optional[str] = None + progress: int = 0 + + +# ============== Security Tool Definitions ============== + +SECURITY_TOOLS = { + "nmap": { + "name": "nmap", + "description": "Network scanner and security auditing tool", + "category": "reconnaissance", + "templates": { + "quick": "nmap -T4 -F {target}", + "full": "nmap -sV -sC -O -p- {target}", + "stealth": "nmap -sS -T2 -f {target}", + "vuln": "nmap --script vuln {target}", + "version": "nmap -sV -p {ports} {target}", + } + }, + "nikto": { + "name": "nikto", + "description": "Web server vulnerability scanner", + "category": "vulnerability_scanning", + "templates": { + "default": "nikto -h {target}", + "ssl": "nikto -h {target} -ssl", + "full": "nikto -h {target} -C all", + } + }, + "gobuster": { + "name": "gobuster", + "description": "Directory/file brute-forcing", + "category": "web_testing", + "templates": { + "dir": "gobuster dir -u {target} -w /usr/share/wordlists/dirb/common.txt -q", + "dns": "gobuster dns -d {target} -w /usr/share/wordlists/dns/subdomains-top1million-5000.txt", + } + }, + "sqlmap": { + "name": "sqlmap", + "description": "SQL injection detection and exploitation", + "category": "vulnerability_scanning", + "templates": { + "test": "sqlmap -u '{target}' --batch --level=1", + "dbs": "sqlmap -u '{target}' --batch --dbs", + } + }, + "whatweb": { + "name": "whatweb", + "description": "Web technology fingerprinting", + "category": "reconnaissance", + "templates": { + "default": "whatweb {target}", + "aggressive": "whatweb -a 3 {target}", + } + }, + "searchsploit": { + "name": "searchsploit", + "description": "Exploit database search", + "category": "exploitation", + "templates": { + "search": "searchsploit {query}", + "json": "searchsploit -j {query}", + } + } +} + +# System prompts for different security tasks +SECURITY_PROMPTS = { + "recon": """You are a penetration testing assistant specializing in reconnaissance. +Analyze the target and suggest reconnaissance techniques. Focus on: +- OSINT gathering +- DNS enumeration +- Subdomain discovery +- Port scanning strategies +- Technology fingerprinting +Always emphasize legal and ethical considerations.""", + + "vulnerability": """You are a vulnerability assessment specialist. +Analyze the provided information and identify potential vulnerabilities: +- Common CVEs that may apply +- Misconfigurations +- Weak authentication mechanisms +- Input validation issues +Provide severity ratings and remediation suggestions.""", + + "exploit_research": """You are a security researcher focused on exploit analysis. +Given the vulnerability information: +- Explain the technical details of the vulnerability +- Describe potential exploitation techniques +- Suggest proof-of-concept approaches +- Recommend detection and prevention methods +Always include responsible disclosure considerations.""", + + "report": """You are a security report writer. +Create a professional security assessment report including: +- Executive summary +- Technical findings +- Risk ratings +- Remediation recommendations +- Timeline for fixes +Format the report in clear, professional language.""", + + "command_assist": """You are a security command expert. Analyze the user's request and: +1. Suggest the most appropriate security tool and command +2. Explain what the command does +3. Describe expected output +4. Note any safety considerations + +If the user wants to run a scan, extract: +- tool: The security tool to use (nmap, nikto, gobuster, etc.) +- target: The target IP, hostname, or URL +- scan_type: The type of scan (quick, full, etc.) + +Respond in JSON format when suggesting a command: +{ + "tool": "tool_name", + "target": "target_value", + "scan_type": "scan_type", + "explanation": "What this will do", + "command": "The full command" +}""" +} + +# Phase-specific context-aware prompts (HackGpt-style) +PHASE_PROMPTS = { + "recon": """You are HackGPT operating in **Phase 1: Reconnaissance**. + +Your role is to assist with passive and active information gathering: +- OSINT techniques (theHarvester, Maltego, Shodan) +- DNS enumeration (amass, subfinder, dnsenum) +- Port scanning strategies (nmap, masscan) +- Technology fingerprinting (whatweb, wappalyzer) +- Google dorking and search operators + +For each response: +1. Provide actionable reconnaissance steps +2. Suggest specific tools and commands +3. Explain what information each technique reveals +4. Assign a risk relevance score (1-10) based on potential attack surface + +Keep responses focused on information gathering. Do not suggest exploitation yet.""", + + "scanning": """You are HackGPT operating in **Phase 2: Scanning & Enumeration**. + +Your role is to assist with in-depth service enumeration: +- Service version detection (nmap -sV) +- Banner grabbing and fingerprinting +- Directory brute-forcing (gobuster, ffuf, dirb) +- SMB/NetBIOS enumeration (enum4linux, smbclient) +- SNMP enumeration + +For each response: +1. Build on reconnaissance findings +2. Suggest detailed enumeration commands +3. Identify potential attack vectors from enumeration +4. Assign a risk score based on exposed services (1-10) + +Focus on gathering detailed service information.""", + + "vuln": """You are HackGPT operating in **Phase 3: Vulnerability Assessment**. + +Your role is to identify and assess vulnerabilities: +- CVE identification and CVSS scoring +- Automated vulnerability scanning (nikto, nuclei, nessus) +- Web application testing (OWASP Top 10) +- SQL injection detection (sqlmap) +- Configuration analysis + +For each finding, provide: +1. Vulnerability title and description +2. CVSS score estimate (0.0-10.0) with severity label +3. Affected components +4. Potential impact +5. Remediation recommendations + +Format findings as structured data when possible.""", + + "exploit": """You are HackGPT operating in **Phase 4: Exploitation**. + +Your role is to safely demonstrate vulnerability impact: +- Exploit research (searchsploit, exploit-db) +- Proof-of-concept development +- Credential attacks (hydra, medusa) +- Post-exploitation enumeration + +CRITICAL SAFETY RULES: +1. Only suggest exploitation with explicit authorization +2. Prefer non-destructive PoC approaches +3. Always have a rollback plan +4. Document every exploitation attempt + +Provide exploitation strategies with: +- Success probability estimate +- Required prerequisites +- Detection likelihood +- Impact demonstration goals""", + + "report": """You are HackGPT operating in **Phase 5: Reporting**. + +Your role is to create professional security documentation: +- Executive summaries for stakeholders +- Technical reports for remediation teams +- Risk matrices and prioritization +- Compliance mapping (OWASP, NIST, PCI-DSS) + +Structure reports with: +1. Executive Summary (business impact, key findings) +2. Scope and Methodology +3. Findings (sorted by severity: Critical > High > Medium > Low) +4. Risk Assessment Matrix +5. Remediation Recommendations with timelines +6. Appendices (raw data, tool outputs) + +Use professional language appropriate for security assessments.""", + + "retest": """You are HackGPT operating in **Phase 6: Retesting & Verification**. + +Your role is to verify remediation effectiveness: +- Re-run previous vulnerability scans +- Validate patches and fixes +- Regression testing +- Delta analysis (before/after) + +For each retest: +1. Reference the original finding +2. Describe the verification approach +3. Confirm fix status (Resolved/Partial/Unresolved) +4. Note any new issues introduced +5. Update risk scores accordingly + +Focus on validation and verification procedures.""" +} + + +@app.get("/health") +async def health_check(): + """Health check endpoint""" + return {"status": "healthy", "service": "hackgpt-api"} + + +@app.post("/chat") +async def security_chat(request: ChatRequest): + """Chat with security-focused AI assistant""" + messages = [ + { + "role": "system", + "content": """You are HackGPT, an AI assistant specialized in cybersecurity, +penetration testing, and security research. You provide educational information +about security concepts, tools, and techniques. Always emphasize ethical hacking +principles and legal considerations. You help security professionals understand +vulnerabilities and defenses.""" + } + ] + + if request.context: + messages.append({"role": "system", "content": f"Context: {request.context}"}) + + messages.append({"role": "user", "content": request.message}) + + async with httpx.AsyncClient() as client: + try: + response = await client.post( + f"{LLM_ROUTER_URL}/chat", + json={ + "provider": request.provider, + "model": request.model, + "messages": messages, + "temperature": 0.7, + "max_tokens": 2048 + }, + timeout=120.0 + ) + + if response.status_code != 200: + raise HTTPException(status_code=response.status_code, detail=response.text) + + return response.json() + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="LLM Router service not available") + + +@app.post("/chat/phase") +async def phase_aware_chat(request: PhaseChatRequest): + """Phase-aware chat with context from current pentest phase""" + phase_prompt = PHASE_PROMPTS.get(request.phase, PHASE_PROMPTS["recon"]) + + # Build context from findings if available + findings_context = "" + if request.findings: + findings_summary = [] + for f in request.findings[-10:]: # Last 10 findings + severity = f.get("severity", "info") + title = f.get("title") or f.get("raw", "Unknown finding") + findings_summary.append(f"- [{severity.upper()}] {title}") + if findings_summary: + findings_context = f"\n\nCurrent Findings:\n" + "\n".join(findings_summary) + + messages = [ + {"role": "system", "content": phase_prompt + findings_context}, + {"role": "user", "content": request.message} + ] + + async with httpx.AsyncClient() as client: + try: + response = await client.post( + f"{LLM_ROUTER_URL}/chat", + json={ + "provider": request.provider, + "model": request.model, + "messages": messages, + "temperature": 0.7, + "max_tokens": 2048 + }, + timeout=120.0 + ) + + if response.status_code != 200: + raise HTTPException(status_code=response.status_code, detail=response.text) + + result = response.json() + + # Try to extract findings and risk score from response + content = result.get("content", "") + risk_score = None + extracted_findings = [] + + # Simple extraction of risk scores mentioned in response + import re + risk_match = re.search(r'risk\s*(?:score|rating)?[:\s]*(\d+(?:\.\d+)?)\s*(?:/\s*10)?', content, re.IGNORECASE) + if risk_match: + try: + risk_score = float(risk_match.group(1)) + if risk_score > 10: + risk_score = risk_score / 10 + except: + pass + + # Extract any severity-labeled findings + severity_patterns = [ + (r'\[CRITICAL\]\s*(.+?)(?:\n|$)', 'critical'), + (r'\[HIGH\]\s*(.+?)(?:\n|$)', 'high'), + (r'\[MEDIUM\]\s*(.+?)(?:\n|$)', 'medium'), + (r'\[LOW\]\s*(.+?)(?:\n|$)', 'low'), + ] + + for pattern, severity in severity_patterns: + matches = re.findall(pattern, content, re.IGNORECASE) + for match in matches: + extracted_findings.append({ + "id": f"ai-{len(extracted_findings)}", + "title": match.strip()[:100], + "severity": severity + }) + + result["risk_score"] = risk_score + result["findings"] = extracted_findings[:5] # Limit to 5 + + return result + + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="LLM Router service not available") + + +@app.post("/attack-chains") +async def analyze_attack_chains(request: AttackChainRequest): + """Analyze findings to identify attack chains using AI""" + + if not request.findings: + return {"attack_chains": []} + + # Build findings summary for AI analysis + findings_text = [] + for f in request.findings: + severity = f.get("severity", "info") + title = f.get("title") or f.get("raw", "Unknown") + tool = f.get("tool", "unknown") + target = f.get("target", "unknown") + findings_text.append(f"- [{severity.upper()}] {title} (found by {tool} on {target})") + + prompt = f"""Analyze these security findings and identify potential attack chains. +An attack chain is a sequence of vulnerabilities that can be combined for greater impact. + +Findings: +{chr(10).join(findings_text)} + +For each attack chain identified, provide: +1. Chain name +2. Step-by-step attack path +3. Combined risk score (1-10) +4. Likelihood of success (0.0-1.0) +5. Overall impact +6. Recommendation priority + +Respond in this JSON format: +{{ + "attack_chains": [ + {{ + "name": "Chain name", + "risk_score": 8.5, + "likelihood": 0.7, + "impact": "Description of impact", + "recommendation": "Priority recommendation", + "steps": [ + {{"step": 1, "action": "First step", "method": "technique used"}}, + {{"step": 2, "action": "Second step", "method": "technique used"}} + ] + }} + ] +}} + +Only return valid JSON.""" + + messages = [ + {"role": "system", "content": "You are a security analyst specializing in attack chain analysis. Respond only with valid JSON."}, + {"role": "user", "content": prompt} + ] + + async with httpx.AsyncClient() as client: + try: + response = await client.post( + f"{LLM_ROUTER_URL}/chat", + json={ + "provider": request.provider, + "model": request.model, + "messages": messages, + "temperature": 0.3, + "max_tokens": 2048 + }, + timeout=120.0 + ) + + if response.status_code != 200: + raise HTTPException(status_code=response.status_code, detail=response.text) + + result = response.json() + content = result.get("content", "") + + # Try to parse JSON from response + try: + import re + json_match = re.search(r'\{[\s\S]*\}', content) + if json_match: + chains_data = json.loads(json_match.group()) + return chains_data + except json.JSONDecodeError: + pass + + # Fallback: generate basic chains from high-severity findings + high_severity = [f for f in request.findings if f.get("severity") in ["critical", "high"]] + if high_severity: + return { + "attack_chains": [{ + "name": f"Attack via {high_severity[0].get('title', 'vulnerability')[:50]}", + "risk_score": 7.5, + "likelihood": 0.6, + "impact": "Potential system compromise", + "recommendation": "Address high-severity findings first", + "steps": [ + {"step": 1, "action": "Exploit initial vulnerability", "method": high_severity[0].get("title", "unknown")[:50]}, + {"step": 2, "action": "Establish persistence", "method": "post-exploitation"}, + {"step": 3, "action": "Lateral movement", "method": "credential harvesting"} + ] + }] + } + + return {"attack_chains": []} + + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="LLM Router service not available") + + +# ============== Session Management ============== + +def get_or_create_session(session_id: Optional[str] = None) -> str: + """Get existing session or create a new one.""" + if session_id and session_id in sessions: + sessions[session_id]["last_activity"] = datetime.utcnow() + return session_id + + new_id = str(uuid.uuid4()) + sessions[new_id] = { + "id": new_id, + "created_at": datetime.utcnow(), + "last_activity": datetime.utcnow(), + "messages": [], + "context": {} + } + return new_id + + +@app.get("/sessions/{session_id}") +async def get_session(session_id: str): + """Get session details.""" + if session_id not in sessions: + raise HTTPException(status_code=404, detail="Session not found") + return sessions[session_id] + + +@app.post("/sessions/{session_id}/context") +async def update_session_context(session_id: str, context: Dict[str, Any]): + """Update session context with scan results or other data.""" + if session_id not in sessions: + raise HTTPException(status_code=404, detail="Session not found") + sessions[session_id]["context"].update(context) + return {"status": "updated"} + + +# ============== Command Execution ============== + +@app.post("/execute") +async def execute_command(request: CommandRequest): + """Execute a command in the Kali container.""" + async with httpx.AsyncClient() as client: + try: + response = await client.post( + f"{KALI_EXECUTOR_URL}/execute", + json={ + "command": request.command, + "timeout": request.timeout, + "working_dir": request.working_dir + }, + timeout=float(request.timeout + 30) + ) + + if response.status_code != 200: + raise HTTPException(status_code=response.status_code, detail=response.text) + + result = response.json() + + # Parse output if requested and tool is recognized + if request.parse_output: + tool = request.command.split()[0] + parsed = parse_tool_output(tool, result.get("stdout", "")) + result["parsed"] = parsed + + return result + + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="Kali executor service not available") + except httpx.TimeoutException: + raise HTTPException(status_code=504, detail="Command execution timed out") + + +# ============== Scan Management ============== + +@app.post("/scan") +async def start_scan(request: ScanRequest, background_tasks: BackgroundTasks): + """Start a security scan.""" + tool_config = SECURITY_TOOLS.get(request.tool) + if not tool_config: + raise HTTPException(status_code=400, detail=f"Unknown tool: {request.tool}") + + # Build command from template + scan_type = request.scan_type or list(tool_config["templates"].keys())[0] + template = tool_config["templates"].get(scan_type) + + if not template: + raise HTTPException(status_code=400, detail=f"Unknown scan type: {scan_type}") + + # Format command with target and options + try: + command = template.format(target=request.target, **request.options) + except KeyError as e: + raise HTTPException(status_code=400, detail=f"Missing required option: {e}") + + # Create scan task + scan_id = str(uuid.uuid4()) + scan_results[scan_id] = { + "scan_id": scan_id, + "tool": request.tool, + "target": request.target, + "scan_type": scan_type, + "command": command, + "status": "pending", + "started_at": datetime.utcnow().isoformat(), + "result": None, + "parsed": None + } + + background_tasks.add_task(run_scan, scan_id, command, request.tool) + + return {"scan_id": scan_id, "status": "pending", "command": command} + + +async def run_scan(scan_id: str, command: str, tool: str): + """Run scan in background.""" + scan_results[scan_id]["status"] = "running" + + try: + async with httpx.AsyncClient() as client: + response = await client.post( + f"{KALI_EXECUTOR_URL}/execute", + json={"command": command, "timeout": 600, "working_dir": "/workspace"}, + timeout=660.0 + ) + + if response.status_code == 200: + result = response.json() + scan_results[scan_id]["status"] = "completed" + scan_results[scan_id]["result"] = result + scan_results[scan_id]["completed_at"] = datetime.utcnow().isoformat() + + # Parse output + parsed = parse_tool_output(tool, result.get("stdout", "")) + scan_results[scan_id]["parsed"] = parsed + else: + scan_results[scan_id]["status"] = "failed" + scan_results[scan_id]["error"] = response.text + + except Exception as e: + scan_results[scan_id]["status"] = "failed" + scan_results[scan_id]["error"] = str(e) + + +@app.get("/scan/{scan_id}") +async def get_scan_result(scan_id: str): + """Get scan results.""" + if scan_id not in scan_results: + raise HTTPException(status_code=404, detail="Scan not found") + return scan_results[scan_id] + + +@app.get("/scans") +async def list_scans(): + """List all scans.""" + return list(scan_results.values()) + + +# ============== Output Parsing ============== + +def parse_tool_output(tool: str, output: str) -> Dict[str, Any]: + """Parse output from security tools.""" + tool = tool.lower() + + if tool == "nmap": + return parse_nmap_output(output) + elif tool == "nikto": + return parse_nikto_output(output) + elif tool == "gobuster": + return parse_gobuster_output(output) + + return {"raw": output} + + +def parse_nmap_output(output: str) -> Dict[str, Any]: + """Parse nmap output.""" + import re + + results = {"hosts": [], "raw": output} + current_host = None + + for line in output.split('\n'): + line = line.strip() + + if 'Nmap scan report for' in line: + if current_host: + results["hosts"].append(current_host) + + match = re.search(r'for (\S+)(?: \((\d+\.\d+\.\d+\.\d+)\))?', line) + if match: + current_host = { + "hostname": match.group(1), + "ip": match.group(2) or match.group(1), + "ports": [], + "os": None + } + + elif current_host and re.match(r'^\d+/(tcp|udp)', line): + parts = line.split() + if len(parts) >= 3: + port_proto = parts[0].split('/') + current_host["ports"].append({ + "port": int(port_proto[0]), + "protocol": port_proto[1], + "state": parts[1], + "service": parts[2] if len(parts) > 2 else "unknown", + "version": ' '.join(parts[3:]) if len(parts) > 3 else None + }) + + if current_host: + results["hosts"].append(current_host) + + return results + + +def parse_nikto_output(output: str) -> Dict[str, Any]: + """Parse nikto output.""" + results = {"findings": [], "server_info": {}, "raw": output} + + for line in output.split('\n'): + line = line.strip() + + if '+ Target IP:' in line: + results["server_info"]["ip"] = line.split(':')[-1].strip() + elif '+ Server:' in line: + results["server_info"]["server"] = line.split(':', 1)[-1].strip() + elif line.startswith('+') and ':' in line: + if not any(skip in line for skip in ['Target IP', 'Server:', 'Start Time']): + severity = "info" + if any(w in line.lower() for w in ['vulnerable', 'exploit']): + severity = "high" + elif any(w in line.lower() for w in ['outdated', 'insecure']): + severity = "medium" + + results["findings"].append({ + "raw": line[1:].strip(), + "severity": severity + }) + + return results + + +def parse_gobuster_output(output: str) -> Dict[str, Any]: + """Parse gobuster output.""" + import re + + results = {"findings": [], "directories": [], "files": [], "raw": output} + + for line in output.split('\n'): + match = re.search(r'^(/\S*)\s+\(Status:\s*(\d+)\)', line.strip()) + if match: + finding = { + "path": match.group(1), + "status": int(match.group(2)) + } + results["findings"].append(finding) + + if finding["path"].endswith('/'): + results["directories"].append(finding["path"]) + else: + results["files"].append(finding["path"]) + + return results + + +# ============== AI-Assisted Scanning ============== + +@app.post("/ai-scan") +async def ai_assisted_scan(request: ChatRequest, background_tasks: BackgroundTasks): + """Use AI to determine and run appropriate scan.""" + # Get AI suggestion + messages = [ + {"role": "system", "content": SECURITY_PROMPTS["command_assist"]}, + {"role": "user", "content": request.message} + ] + + async with httpx.AsyncClient() as client: + try: + response = await client.post( + f"{LLM_ROUTER_URL}/chat", + json={ + "provider": request.provider, + "model": request.model, + "messages": messages, + "temperature": 0.3, + "max_tokens": 1024 + }, + timeout=60.0 + ) + + if response.status_code != 200: + raise HTTPException(status_code=response.status_code, detail=response.text) + + ai_response = response.json() + content = ai_response.get("content", "") + + # Try to parse JSON from response + try: + import re + json_match = re.search(r'\{[^{}]*\}', content, re.DOTALL) + if json_match: + suggestion = json.loads(json_match.group()) + + # If we have a valid tool and target, start the scan + if suggestion.get("tool") and suggestion.get("target"): + scan_request = ScanRequest( + tool=suggestion["tool"], + target=suggestion["target"], + scan_type=suggestion.get("scan_type") + ) + + return await start_scan(scan_request, background_tasks) + + return {"suggestion": suggestion, "ai_response": content} + except json.JSONDecodeError: + pass + + return {"ai_response": content} + + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="LLM Router service not available") + + +@app.post("/analyze") +async def analyze_security(request: SecurityAnalysisRequest, background_tasks: BackgroundTasks): + """Start a security analysis task""" + task_id = str(uuid.uuid4()) + tasks[task_id] = TaskStatus(task_id=task_id, status="pending") + + background_tasks.add_task(run_analysis, task_id, request) + + return {"task_id": task_id, "status": "pending"} + + +async def run_analysis(task_id: str, request: SecurityAnalysisRequest): + """Run security analysis in background""" + tasks[task_id].status = "running" + + try: + prompt = SECURITY_PROMPTS.get(request.analysis_type, SECURITY_PROMPTS["recon"]) + + async with httpx.AsyncClient() as client: + response = await client.post( + f"{LLM_ROUTER_URL}/chat", + json={ + "provider": "ollama", + "model": "llama3.2", + "messages": [ + {"role": "system", "content": prompt}, + {"role": "user", "content": f"Analyze target: {request.target}\nOptions: {request.options}"} + ], + "temperature": 0.5, + "max_tokens": 4096 + }, + timeout=300.0 + ) + + if response.status_code == 200: + data = response.json() + tasks[task_id].status = "completed" + tasks[task_id].result = data.get("content", "") + else: + tasks[task_id].status = "failed" + tasks[task_id].error = response.text + + except Exception as e: + tasks[task_id].status = "failed" + tasks[task_id].error = str(e) + + +@app.get("/task/{task_id}") +async def get_task_status(task_id: str): + """Get status of a running task""" + if task_id not in tasks: + raise HTTPException(status_code=404, detail="Task not found") + return tasks[task_id] + + +@app.get("/tools") +async def list_tools(): + """List available security tools and their descriptions""" + return { + "reconnaissance": [ + {"name": "nmap", "description": "Network scanner and security auditing tool"}, + {"name": "masscan", "description": "Fast TCP port scanner"}, + {"name": "amass", "description": "Subdomain enumeration tool"}, + {"name": "theHarvester", "description": "OSINT tool for gathering emails, names, subdomains"}, + {"name": "whatweb", "description": "Web technology fingerprinting"}, + ], + "vulnerability_scanning": [ + {"name": "nikto", "description": "Web server vulnerability scanner"}, + {"name": "nuclei", "description": "Template-based vulnerability scanner"}, + {"name": "sqlmap", "description": "SQL injection detection and exploitation"}, + {"name": "wpscan", "description": "WordPress vulnerability scanner"}, + ], + "exploitation": [ + {"name": "metasploit", "description": "Penetration testing framework"}, + {"name": "searchsploit", "description": "Exploit database search tool"}, + {"name": "hydra", "description": "Network login cracker"}, + ], + "web_testing": [ + {"name": "burpsuite", "description": "Web application security testing"}, + {"name": "gobuster", "description": "Directory/file brute-forcing"}, + {"name": "ffuf", "description": "Fast web fuzzer"}, + ] + } + + +@app.post("/suggest-command") +async def suggest_command(request: ChatRequest): + """Get AI-suggested security commands based on context""" + messages = [ + { + "role": "system", + "content": """You are a security command expert. Given the user's request, +suggest appropriate security tool commands. Provide: +1. The exact command to run +2. Explanation of what it does +3. Expected output +4. Safety considerations +Only suggest commands for legitimate security testing purposes.""" + }, + {"role": "user", "content": request.message} + ] + + if request.context: + messages.insert(1, {"role": "system", "content": f"Context: {request.context}"}) + + async with httpx.AsyncClient() as client: + try: + response = await client.post( + f"{LLM_ROUTER_URL}/chat", + json={ + "provider": request.provider, + "model": request.model, + "messages": messages, + "temperature": 0.3, + "max_tokens": 1024 + }, + timeout=60.0 + ) + + if response.status_code != 200: + raise HTTPException(status_code=response.status_code, detail=response.text) + + return response.json() + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="LLM Router service not available") + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8001) \ No newline at end of file diff --git a/services/hackgpt-api/requirements.txt b/services/hackgpt-api/requirements.txt new file mode 100644 index 0000000..2919c00 --- /dev/null +++ b/services/hackgpt-api/requirements.txt @@ -0,0 +1,4 @@ +fastapi==0.115.5 +uvicorn[standard]==0.32.1 +httpx==0.28.1 +pydantic==2.10.2 diff --git a/services/kali-executor/Dockerfile b/services/kali-executor/Dockerfile new file mode 100644 index 0000000..81bf86d --- /dev/null +++ b/services/kali-executor/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.12-slim + +WORKDIR /app + +# Install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY app/ ./app/ + +# Expose port +EXPOSE 8002 + +# Run the application +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8002"] diff --git a/services/kali-executor/app/__init__.py b/services/kali-executor/app/__init__.py new file mode 100644 index 0000000..ea0674d --- /dev/null +++ b/services/kali-executor/app/__init__.py @@ -0,0 +1 @@ +"""Kali Executor Service""" diff --git a/services/kali-executor/app/main.py b/services/kali-executor/app/main.py new file mode 100644 index 0000000..deae3da --- /dev/null +++ b/services/kali-executor/app/main.py @@ -0,0 +1,480 @@ +""" +Kali Executor Service +Executes commands in the Kali container via Docker SDK. +""" +from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel, Field +from typing import Optional, Dict, Any, List +import docker +import asyncio +from concurrent.futures import ThreadPoolExecutor +import os +import uuid +import json +import re +from datetime import datetime +from contextlib import asynccontextmanager + +# Allowed command prefixes (security whitelist) +ALLOWED_COMMANDS = { + # Reconnaissance + "nmap", "masscan", "amass", "theharvester", "whatweb", "dnsrecon", "fierce", + "dig", "nslookup", "host", "whois", + # Web testing + "nikto", "gobuster", "dirb", "sqlmap", "wpscan", "curl", "wget", + # Network utilities + "ping", "traceroute", "netcat", "nc", "tcpdump", + # Exploitation research + "searchsploit", "msfconsole", "msfvenom", + # Brute force + "hydra", "medusa", + # System info + "ls", "cat", "head", "tail", "grep", "find", "pwd", "whoami", "id", + "uname", "hostname", "ip", "ifconfig", "netstat", "ss", + # Python scripts + "python", "python3", +} + +# Blocked patterns (dangerous commands) +BLOCKED_PATTERNS = [ + r"rm\s+-rf\s+/", # Prevent recursive deletion of root + r"mkfs", # Prevent formatting + r"dd\s+if=", # Prevent disk operations + r">\s*/dev/", # Prevent writing to devices + r"chmod\s+777\s+/", # Prevent dangerous permission changes + r"shutdown", r"reboot", r"halt", # Prevent system control + r"kill\s+-9\s+-1", # Prevent killing all processes +] + + +def validate_command(command: str) -> tuple[bool, str]: + """Validate command against whitelist and blocked patterns.""" + # Get the base command (first word) + parts = command.strip().split() + if not parts: + return False, "Empty command" + + base_cmd = parts[0].split("/")[-1] # Handle full paths + + # Check blocked patterns first + for pattern in BLOCKED_PATTERNS: + if re.search(pattern, command, re.IGNORECASE): + return False, f"Blocked pattern detected: {pattern}" + + # Check if command is in whitelist + if base_cmd not in ALLOWED_COMMANDS: + return False, f"Command '{base_cmd}' not in allowed list" + + return True, "OK" + + +# Docker client +docker_client = None +kali_container = None + + +@asynccontextmanager +async def lifespan(app: FastAPI): + """Lifecycle manager for the FastAPI app.""" + global docker_client, kali_container + + try: + docker_client = docker.from_env() + kali_container = docker_client.containers.get( + os.getenv("KALI_CONTAINER_NAME", "strikepackage-kali") + ) + print(f"Connected to Kali container: {kali_container.name}") + except docker.errors.NotFound: + print("Warning: Kali container not found. Command execution will fail.") + except docker.errors.DockerException as e: + print(f"Warning: Docker not available: {e}") + + yield + + if docker_client: + docker_client.close() + + +app = FastAPI( + title="Kali Executor", + description="Execute commands in the Kali container", + version="0.1.0", + lifespan=lifespan +) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Store running commands +running_commands: Dict[str, Dict[str, Any]] = {} + + +class CommandRequest(BaseModel): + command: str + timeout: int = Field(default=300, ge=1, le=3600) + working_dir: str = "/workspace" + stream: bool = False + + +class CommandResult(BaseModel): + command_id: str + command: str + status: str + exit_code: Optional[int] = None + stdout: str = "" + stderr: str = "" + started_at: datetime + completed_at: Optional[datetime] = None + duration_seconds: Optional[float] = None + + +@app.get("/health") +async def health_check(): + """Health check endpoint.""" + kali_status = "disconnected" + + if kali_container: + try: + kali_container.reload() + kali_status = kali_container.status + except: + kali_status = "error" + + return { + "status": "healthy", + "service": "kali-executor", + "kali_container": kali_status + } + + +@app.get("/processes") +async def get_running_processes(): + """Get list of running security tool processes in Kali container.""" + global kali_container + + if not kali_container: + raise HTTPException(status_code=503, detail="Kali container not available") + + try: + # Get process list + loop = asyncio.get_event_loop() + exit_code, output = await loop.run_in_executor( + executor, + lambda: kali_container.exec_run( + cmd=["ps", "aux", "--sort=-start_time"], + demux=True + ) + ) + + stdout = output[0].decode('utf-8', errors='replace') if output[0] else "" + + # Parse processes and filter for security tools + security_tools = ["nmap", "nikto", "gobuster", "sqlmap", "hydra", "masscan", + "amass", "theharvester", "dirb", "wpscan", "searchsploit", "msfconsole"] + + processes = [] + for line in stdout.split('\n')[1:]: # Skip header + parts = line.split(None, 10) + if len(parts) >= 11: + cmd = parts[10] + pid = parts[1] + cpu = parts[2] + mem = parts[3] + time_running = parts[9] + + # Check if it's a security tool + is_security_tool = any(tool in cmd.lower() for tool in security_tools) + + if is_security_tool: + processes.append({ + "pid": pid, + "cpu": cpu, + "mem": mem, + "time": time_running, + "command": cmd[:200] # Truncate long commands + }) + + return { + "running_processes": processes, + "count": len(processes) + } + + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +# Thread pool for blocking Docker operations +executor = ThreadPoolExecutor(max_workers=10) + +def _run_command_sync(container, command, working_dir): + """Synchronous command execution for thread pool.""" + full_command = f"cd {working_dir} && {command}" + return container.exec_run( + cmd=["bash", "-c", full_command], + demux=True, + workdir=working_dir + ) + +@app.post("/execute", response_model=CommandResult) +async def execute_command(request: CommandRequest): + """Execute a command in the Kali container.""" + global kali_container + + if not kali_container: + raise HTTPException(status_code=503, detail="Kali container not available") + + # Validate command against whitelist + is_valid, message = validate_command(request.command) + if not is_valid: + raise HTTPException(status_code=403, detail=f"Command blocked: {message}") + + command_id = str(uuid.uuid4()) + started_at = datetime.utcnow() + + try: + # Refresh container state + loop = asyncio.get_event_loop() + await loop.run_in_executor(executor, kali_container.reload) + + if kali_container.status != "running": + raise HTTPException(status_code=503, detail="Kali container is not running") + + # Execute command in thread pool to avoid blocking + exit_code, output = await loop.run_in_executor( + executor, + _run_command_sync, + kali_container, + request.command, + request.working_dir + ) + + completed_at = datetime.utcnow() + duration = (completed_at - started_at).total_seconds() + + stdout = output[0].decode('utf-8', errors='replace') if output[0] else "" + stderr = output[1].decode('utf-8', errors='replace') if output[1] else "" + + return CommandResult( + command_id=command_id, + command=request.command, + status="completed", + exit_code=exit_code, + stdout=stdout, + stderr=stderr, + started_at=started_at, + completed_at=completed_at, + duration_seconds=duration + ) + + except docker.errors.APIError as e: + raise HTTPException(status_code=500, detail=f"Docker error: {str(e)}") + except Exception as e: + raise HTTPException(status_code=500, detail=f"Execution error: {str(e)}") + + +@app.post("/execute/async") +async def execute_command_async(request: CommandRequest): + """Execute a command asynchronously and return immediately.""" + global kali_container + + if not kali_container: + raise HTTPException(status_code=503, detail="Kali container not available") + + # Validate command against whitelist + is_valid, message = validate_command(request.command) + if not is_valid: + raise HTTPException(status_code=403, detail=f"Command blocked: {message}") + + command_id = str(uuid.uuid4()) + started_at = datetime.utcnow() + + running_commands[command_id] = { + "command": request.command, + "status": "running", + "started_at": started_at, + "stdout": "", + "stderr": "" + } + + # Start background execution + asyncio.create_task(_run_command_background( + command_id, request.command, request.working_dir, request.timeout + )) + + return {"command_id": command_id, "status": "running"} + + +async def _run_command_background(command_id: str, command: str, working_dir: str, timeout: int): + """Run command in background.""" + global kali_container + + try: + kali_container.reload() + + full_command = f"cd {working_dir} && timeout {timeout} {command}" + + exit_code, output = kali_container.exec_run( + cmd=["bash", "-c", full_command], + demux=True, + workdir=working_dir + ) + + running_commands[command_id].update({ + "status": "completed", + "exit_code": exit_code, + "stdout": output[0].decode('utf-8', errors='replace') if output[0] else "", + "stderr": output[1].decode('utf-8', errors='replace') if output[1] else "", + "completed_at": datetime.utcnow() + }) + + except Exception as e: + running_commands[command_id].update({ + "status": "failed", + "error": str(e), + "completed_at": datetime.utcnow() + }) + + +@app.get("/execute/{command_id}") +async def get_command_status(command_id: str): + """Get status of an async command.""" + if command_id not in running_commands: + raise HTTPException(status_code=404, detail="Command not found") + + return running_commands[command_id] + + +@app.websocket("/ws/execute") +async def websocket_execute(websocket: WebSocket): + """WebSocket endpoint for streaming command output.""" + global kali_container + + await websocket.accept() + + try: + while True: + data = await websocket.receive_json() + command = data.get("command") + working_dir = data.get("working_dir", "/workspace") + + if not command: + await websocket.send_json({"error": "No command provided"}) + continue + + # Validate command against whitelist + is_valid, message = validate_command(command) + if not is_valid: + await websocket.send_json({"error": f"Command blocked: {message}"}) + continue + + if not kali_container: + await websocket.send_json({"error": "Kali container not available"}) + continue + + try: + kali_container.reload() + + # Use exec_run with stream=True for real-time output + exec_result = kali_container.exec_run( + cmd=["bash", "-c", f"cd {working_dir} && {command}"], + stream=True, + demux=True, + workdir=working_dir + ) + + # Stream output + for stdout, stderr in exec_result.output: + if stdout: + await websocket.send_json({ + "type": "stdout", + "data": stdout.decode('utf-8', errors='replace') + }) + if stderr: + await websocket.send_json({ + "type": "stderr", + "data": stderr.decode('utf-8', errors='replace') + }) + + await websocket.send_json({ + "type": "complete", + "exit_code": exec_result.exit_code if hasattr(exec_result, 'exit_code') else 0 + }) + + except Exception as e: + await websocket.send_json({"type": "error", "message": str(e)}) + + except WebSocketDisconnect: + pass + + +@app.get("/container/info") +async def get_container_info(): + """Get Kali container information.""" + global kali_container + + if not kali_container: + raise HTTPException(status_code=503, detail="Kali container not available") + + try: + kali_container.reload() + + return { + "id": kali_container.short_id, + "name": kali_container.name, + "status": kali_container.status, + "image": kali_container.image.tags[0] if kali_container.image.tags else "unknown", + "created": kali_container.attrs.get("Created"), + "network": list(kali_container.attrs.get("NetworkSettings", {}).get("Networks", {}).keys()) + } + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +@app.get("/tools") +async def list_installed_tools(): + """List security tools installed in Kali container.""" + global kali_container + + if not kali_container: + raise HTTPException(status_code=503, detail="Kali container not available") + + tools_to_check = [ + "nmap", "masscan", "nikto", "sqlmap", "gobuster", "dirb", + "hydra", "amass", "theharvester", "whatweb", "wpscan", + "searchsploit", "msfconsole", "netcat", "curl", "wget" + ] + + installed = [] + + for tool in tools_to_check: + try: + exit_code, _ = kali_container.exec_run( + cmd=["which", tool], + demux=True + ) + if exit_code == 0: + installed.append(tool) + except: + pass + + return {"installed_tools": installed} + + +@app.get("/allowed-commands") +async def get_allowed_commands(): + """Get list of allowed commands for security validation.""" + return { + "allowed_commands": sorted(list(ALLOWED_COMMANDS)), + "blocked_patterns": BLOCKED_PATTERNS + } + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8002) diff --git a/services/kali-executor/requirements.txt b/services/kali-executor/requirements.txt new file mode 100644 index 0000000..0f6e5fa --- /dev/null +++ b/services/kali-executor/requirements.txt @@ -0,0 +1,5 @@ +fastapi==0.115.5 +uvicorn[standard]==0.32.1 +docker==7.1.0 +pydantic==2.10.2 +websockets==14.1 diff --git a/services/kali/Dockerfile b/services/kali/Dockerfile new file mode 100644 index 0000000..ff965f5 --- /dev/null +++ b/services/kali/Dockerfile @@ -0,0 +1,58 @@ +FROM kalilinux/kali-rolling + +# Avoid prompts during package installation +ENV DEBIAN_FRONTEND=noninteractive + +# Update and install essential security tools +RUN apt-get update && apt-get install -y --no-install-recommends \ + # Core utilities + curl \ + wget \ + git \ + vim \ + net-tools \ + iputils-ping \ + dnsutils \ + # Reconnaissance tools + nmap \ + masscan \ + amass \ + theharvester \ + whatweb \ + dnsrecon \ + fierce \ + # Web testing tools + nikto \ + gobuster \ + dirb \ + sqlmap \ + # Network tools + netcat-openbsd \ + tcpdump \ + wireshark-common \ + hydra \ + # Exploitation + metasploit-framework \ + exploitdb \ + # Scripting + python3 \ + python3-pip \ + python3-venv \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install additional Python tools +RUN pip3 install --break-system-packages \ + requests \ + beautifulsoup4 \ + shodan \ + censys + +# Create workspace directory +WORKDIR /workspace + +# Copy entrypoint script +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/services/kali/entrypoint.sh b/services/kali/entrypoint.sh new file mode 100644 index 0000000..6395e3a --- /dev/null +++ b/services/kali/entrypoint.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +echo "==================================================" +echo " StrikePackageGPT - Kali Container" +echo " Security Tools Ready" +echo "==================================================" +echo "" +echo "Available tools:" +echo " - nmap, masscan (port scanning)" +echo " - amass, theharvester (reconnaissance)" +echo " - nikto, gobuster (web testing)" +echo " - sqlmap (SQL injection)" +echo " - hydra (brute force)" +echo " - metasploit (exploitation)" +echo " - searchsploit (exploit database)" +echo "" +echo "Container is ready for security testing." +echo "" + +# Keep container running +exec sleep infinity \ No newline at end of file diff --git a/services/llm-router/Dockerfile b/services/llm-router/Dockerfile new file mode 100644 index 0000000..741b9c7 --- /dev/null +++ b/services/llm-router/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.12-slim + +WORKDIR /app + +# Install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY app/ ./app/ + +# Expose port +EXPOSE 8000 + +# Run the application +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/services/llm-router/app/__init__.py b/services/llm-router/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/llm-router/app/main.py b/services/llm-router/app/main.py new file mode 100644 index 0000000..08bc882 --- /dev/null +++ b/services/llm-router/app/main.py @@ -0,0 +1,213 @@ +""" +LLM Router Service +Routes requests to different LLM providers (OpenAI, Anthropic, Ollama) +""" +from fastapi import FastAPI, HTTPException +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel +from typing import Optional, Literal +import httpx +import os + +app = FastAPI( + title="LLM Router", + description="Routes requests to multiple LLM providers", + version="0.1.0" +) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Configuration from environment +OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "") +ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY", "") +OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://192.168.1.50:11434") + + +class ChatMessage(BaseModel): + role: Literal["system", "user", "assistant"] + content: str + + +class ChatRequest(BaseModel): + provider: Literal["openai", "anthropic", "ollama"] = "ollama" + model: str = "llama3.2" + messages: list[ChatMessage] + temperature: float = 0.7 + max_tokens: int = 2048 + + +class ChatResponse(BaseModel): + provider: str + model: str + content: str + usage: Optional[dict] = None + + +@app.get("/health") +async def health_check(): + """Health check endpoint""" + return {"status": "healthy", "service": "llm-router"} + + +@app.get("/providers") +async def list_providers(): + """List available LLM providers and their status""" + # Dynamically fetch Ollama models + ollama_models = [] + ollama_available = False + try: + async with httpx.AsyncClient() as client: + response = await client.get(f"{OLLAMA_BASE_URL}/api/tags", timeout=5.0) + if response.status_code == 200: + data = response.json() + ollama_models = [m["name"] for m in data.get("models", [])] + ollama_available = True + except Exception: + ollama_models = ["llama3", "mistral", "codellama"] # fallback + + providers = { + "openai": {"available": bool(OPENAI_API_KEY), "models": ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo"]}, + "anthropic": {"available": bool(ANTHROPIC_API_KEY), "models": ["claude-sonnet-4-20250514", "claude-3-5-haiku-20241022"]}, + "ollama": {"available": ollama_available, "base_url": OLLAMA_BASE_URL, "models": ollama_models} + } + return providers + + +@app.post("/chat", response_model=ChatResponse) +async def chat(request: ChatRequest): + """Route chat request to specified LLM provider""" + + if request.provider == "openai": + return await _call_openai(request) + elif request.provider == "anthropic": + return await _call_anthropic(request) + elif request.provider == "ollama": + return await _call_ollama(request) + else: + raise HTTPException(status_code=400, detail=f"Unknown provider: {request.provider}") + + +async def _call_openai(request: ChatRequest) -> ChatResponse: + """Call OpenAI API""" + if not OPENAI_API_KEY: + raise HTTPException(status_code=503, detail="OpenAI API key not configured") + + async with httpx.AsyncClient() as client: + response = await client.post( + "https://api.openai.com/v1/chat/completions", + headers={ + "Authorization": f"Bearer {OPENAI_API_KEY}", + "Content-Type": "application/json" + }, + json={ + "model": request.model, + "messages": [m.model_dump() for m in request.messages], + "temperature": request.temperature, + "max_tokens": request.max_tokens + }, + timeout=60.0 + ) + + if response.status_code != 200: + raise HTTPException(status_code=response.status_code, detail=response.text) + + data = response.json() + return ChatResponse( + provider="openai", + model=request.model, + content=data["choices"][0]["message"]["content"], + usage=data.get("usage") + ) + + +async def _call_anthropic(request: ChatRequest) -> ChatResponse: + """Call Anthropic API""" + if not ANTHROPIC_API_KEY: + raise HTTPException(status_code=503, detail="Anthropic API key not configured") + + # Extract system message if present + system_msg = "" + messages = [] + for msg in request.messages: + if msg.role == "system": + system_msg = msg.content + else: + messages.append({"role": msg.role, "content": msg.content}) + + async with httpx.AsyncClient() as client: + payload = { + "model": request.model, + "messages": messages, + "max_tokens": request.max_tokens, + "temperature": request.temperature + } + if system_msg: + payload["system"] = system_msg + + response = await client.post( + "https://api.anthropic.com/v1/messages", + headers={ + "x-api-key": ANTHROPIC_API_KEY, + "Content-Type": "application/json", + "anthropic-version": "2023-06-01" + }, + json=payload, + timeout=60.0 + ) + + if response.status_code != 200: + raise HTTPException(status_code=response.status_code, detail=response.text) + + data = response.json() + return ChatResponse( + provider="anthropic", + model=request.model, + content=data["content"][0]["text"], + usage=data.get("usage") + ) + + +async def _call_ollama(request: ChatRequest) -> ChatResponse: + """Call Ollama API (local models)""" + async with httpx.AsyncClient() as client: + try: + response = await client.post( + f"{OLLAMA_BASE_URL}/api/chat", + json={ + "model": request.model, + "messages": [m.model_dump() for m in request.messages], + "stream": False, + "options": { + "temperature": request.temperature, + "num_predict": request.max_tokens + } + }, + timeout=120.0 + ) + + if response.status_code != 200: + raise HTTPException(status_code=response.status_code, detail=response.text) + + data = response.json() + return ChatResponse( + provider="ollama", + model=request.model, + content=data["message"]["content"], + usage={ + "prompt_tokens": data.get("prompt_eval_count", 0), + "completion_tokens": data.get("eval_count", 0) + } + ) + except httpx.ConnectError: + raise HTTPException(status_code=503, detail="Ollama service not available") + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/services/llm-router/requirements.txt b/services/llm-router/requirements.txt new file mode 100644 index 0000000..2919c00 --- /dev/null +++ b/services/llm-router/requirements.txt @@ -0,0 +1,4 @@ +fastapi==0.115.5 +uvicorn[standard]==0.32.1 +httpx==0.28.1 +pydantic==2.10.2 diff --git a/services/shared/__init__.py b/services/shared/__init__.py new file mode 100644 index 0000000..c300e9f --- /dev/null +++ b/services/shared/__init__.py @@ -0,0 +1,4 @@ +""" +StrikePackageGPT Shared Library +Common models, utilities, and constants used across services. +""" diff --git a/services/shared/models.py b/services/shared/models.py new file mode 100644 index 0000000..d8dc2b1 --- /dev/null +++ b/services/shared/models.py @@ -0,0 +1,158 @@ +""" +Shared Pydantic models for StrikePackageGPT services. +""" +from pydantic import BaseModel, Field +from typing import Optional, Literal, List, Dict, Any +from datetime import datetime +from enum import Enum + + +class TaskState(str, Enum): + PENDING = "pending" + RUNNING = "running" + COMPLETED = "completed" + FAILED = "failed" + CANCELLED = "cancelled" + + +class ToolCategory(str, Enum): + RECON = "reconnaissance" + VULN_SCAN = "vulnerability_scanning" + EXPLOITATION = "exploitation" + WEB_TESTING = "web_testing" + PASSWORD = "password_attacks" + WIRELESS = "wireless" + FORENSICS = "forensics" + + +# ============== Chat Models ============== + +class ChatMessage(BaseModel): + role: Literal["system", "user", "assistant"] + content: str + timestamp: Optional[datetime] = None + + +class ChatRequest(BaseModel): + message: str + session_id: Optional[str] = None + context: Optional[str] = None + provider: str = "ollama" + model: str = "llama3.2" + temperature: float = 0.7 + max_tokens: int = 2048 + + +class ChatResponse(BaseModel): + provider: str + model: str + content: str + usage: Optional[Dict[str, int]] = None + session_id: Optional[str] = None + + +# ============== Command Execution Models ============== + +class CommandRequest(BaseModel): + command: str + timeout: int = Field(default=300, ge=1, le=3600) + working_dir: Optional[str] = "/workspace" + env: Optional[Dict[str, str]] = None + + +class CommandResult(BaseModel): + command: str + exit_code: int + stdout: str + stderr: str + duration_seconds: float + timed_out: bool = False + + +# ============== Task Models ============== + +class Task(BaseModel): + task_id: str + task_type: str + status: TaskState = TaskState.PENDING + created_at: datetime = Field(default_factory=datetime.utcnow) + started_at: Optional[datetime] = None + completed_at: Optional[datetime] = None + result: Optional[Any] = None + error: Optional[str] = None + progress: int = Field(default=0, ge=0, le=100) + metadata: Dict[str, Any] = Field(default_factory=dict) + + +# ============== Security Tool Models ============== + +class SecurityTool(BaseModel): + name: str + description: str + category: ToolCategory + command_template: str + required_args: List[str] = [] + optional_args: List[str] = [] + output_parser: Optional[str] = None + + +class ScanTarget(BaseModel): + target: str # IP, hostname, URL, or CIDR + target_type: Literal["ip", "hostname", "url", "cidr", "auto"] = "auto" + ports: Optional[str] = None # e.g., "22,80,443" or "1-1000" + options: Dict[str, Any] = Field(default_factory=dict) + + +class ScanRequest(BaseModel): + target: ScanTarget + tool: str + scan_type: Optional[str] = None + options: Dict[str, Any] = Field(default_factory=dict) + + +class ScanResult(BaseModel): + scan_id: str + tool: str + target: str + status: TaskState + started_at: datetime + completed_at: Optional[datetime] = None + raw_output: Optional[str] = None + parsed_results: Optional[Dict[str, Any]] = None + findings: List[Dict[str, Any]] = [] + + +# ============== Session Models ============== + +class Session(BaseModel): + session_id: str + created_at: datetime = Field(default_factory=datetime.utcnow) + last_activity: datetime = Field(default_factory=datetime.utcnow) + messages: List[ChatMessage] = [] + context: Dict[str, Any] = Field(default_factory=dict) + active_scans: List[str] = [] + + +# ============== Finding Models ============== + +class Severity(str, Enum): + CRITICAL = "critical" + HIGH = "high" + MEDIUM = "medium" + LOW = "low" + INFO = "info" + + +class Finding(BaseModel): + finding_id: str + title: str + description: str + severity: Severity + category: str + target: str + evidence: Optional[str] = None + remediation: Optional[str] = None + references: List[str] = [] + cve_ids: List[str] = [] + detected_at: datetime = Field(default_factory=datetime.utcnow) + tool: Optional[str] = None diff --git a/services/shared/parsers.py b/services/shared/parsers.py new file mode 100644 index 0000000..04b7f37 --- /dev/null +++ b/services/shared/parsers.py @@ -0,0 +1,315 @@ +""" +Output parsers for security tool results. +Converts raw tool output into structured data. +""" +import re +import json +import xml.etree.ElementTree as ET +from typing import Dict, Any, List, Optional +from datetime import datetime + + +class BaseParser: + """Base class for tool output parsers.""" + + def parse(self, output: str) -> Dict[str, Any]: + raise NotImplementedError + + +class NmapParser(BaseParser): + """Parser for nmap output.""" + + def parse(self, output: str) -> Dict[str, Any]: + """Parse nmap text output.""" + results = { + "hosts": [], + "scan_info": {}, + "raw": output + } + + current_host = None + + for line in output.split('\n'): + line = line.strip() + + # Parse scan info + if line.startswith('Nmap scan report for'): + if current_host: + results["hosts"].append(current_host) + + # Extract hostname and IP + match = re.search(r'for (\S+)(?: \((\d+\.\d+\.\d+\.\d+)\))?', line) + if match: + current_host = { + "hostname": match.group(1), + "ip": match.group(2) or match.group(1), + "ports": [], + "os": None, + "status": "up" + } + + # Parse port info + elif current_host and re.match(r'^\d+/(tcp|udp)', line): + parts = line.split() + if len(parts) >= 3: + port_proto = parts[0].split('/') + current_host["ports"].append({ + "port": int(port_proto[0]), + "protocol": port_proto[1], + "state": parts[1], + "service": parts[2] if len(parts) > 2 else "unknown", + "version": ' '.join(parts[3:]) if len(parts) > 3 else None + }) + + # Parse OS detection + elif current_host and 'OS details:' in line: + current_host["os"] = line.replace('OS details:', '').strip() + + # Parse timing info + elif 'scanned in' in line.lower(): + match = re.search(r'scanned in ([\d.]+) seconds', line) + if match: + results["scan_info"]["duration_seconds"] = float(match.group(1)) + + if current_host: + results["hosts"].append(current_host) + + return results + + def parse_xml(self, xml_output: str) -> Dict[str, Any]: + """Parse nmap XML output for more detailed results.""" + try: + root = ET.fromstring(xml_output) + results = { + "hosts": [], + "scan_info": { + "scanner": root.get("scanner"), + "args": root.get("args"), + "start_time": root.get("start"), + } + } + + for host in root.findall('.//host'): + host_info = { + "ip": None, + "hostname": None, + "status": host.find('status').get('state') if host.find('status') is not None else "unknown", + "ports": [], + "os": [] + } + + # Get addresses + for addr in host.findall('.//address'): + if addr.get('addrtype') == 'ipv4': + host_info["ip"] = addr.get('addr') + + # Get hostnames + hostname_elem = host.find('.//hostname') + if hostname_elem is not None: + host_info["hostname"] = hostname_elem.get('name') + + # Get ports + for port in host.findall('.//port'): + port_info = { + "port": int(port.get('portid')), + "protocol": port.get('protocol'), + "state": port.find('state').get('state') if port.find('state') is not None else "unknown", + } + + service = port.find('service') + if service is not None: + port_info["service"] = service.get('name') + port_info["product"] = service.get('product') + port_info["version"] = service.get('version') + + host_info["ports"].append(port_info) + + results["hosts"].append(host_info) + + return results + except ET.ParseError: + return {"error": "Failed to parse XML", "raw": xml_output} + + +class NiktoParser(BaseParser): + """Parser for nikto output.""" + + def parse(self, output: str) -> Dict[str, Any]: + results = { + "target": None, + "findings": [], + "server_info": {}, + "raw": output + } + + for line in output.split('\n'): + line = line.strip() + + # Target info + if '+ Target IP:' in line: + results["target"] = line.split(':')[-1].strip() + elif '+ Target Hostname:' in line: + results["server_info"]["hostname"] = line.split(':')[-1].strip() + elif '+ Target Port:' in line: + results["server_info"]["port"] = line.split(':')[-1].strip() + elif '+ Server:' in line: + results["server_info"]["server"] = line.split(':', 1)[-1].strip() + + # Findings (lines starting with +) + elif line.startswith('+') and ':' in line: + # Skip info lines + if any(skip in line for skip in ['Target IP', 'Target Hostname', 'Target Port', 'Server:', 'Start Time', 'End Time']): + continue + + finding = { + "raw": line[1:].strip(), + "severity": "info" + } + + # Determine severity based on content + if any(word in line.lower() for word in ['vulnerable', 'vulnerability', 'exploit']): + finding["severity"] = "high" + elif any(word in line.lower() for word in ['outdated', 'deprecated', 'insecure']): + finding["severity"] = "medium" + elif any(word in line.lower() for word in ['disclosed', 'information', 'header']): + finding["severity"] = "low" + + # Extract OSVDB if present + osvdb_match = re.search(r'OSVDB-(\d+)', line) + if osvdb_match: + finding["osvdb"] = osvdb_match.group(1) + + results["findings"].append(finding) + + return results + + +class SQLMapParser(BaseParser): + """Parser for sqlmap output.""" + + def parse(self, output: str) -> Dict[str, Any]: + results = { + "target": None, + "parameters": [], + "injections": [], + "databases": [], + "raw": output + } + + in_parameter_section = False + + for line in output.split('\n'): + line = line.strip() + + # Target URL + if 'target URL' in line.lower(): + match = re.search(r"'([^']+)'", line) + if match: + results["target"] = match.group(1) + + # Injectable parameters + if 'Parameter:' in line: + param_match = re.search(r"Parameter: (\S+)", line) + if param_match: + results["parameters"].append({ + "name": param_match.group(1), + "injectable": True + }) + + # Injection type + if 'Type:' in line and 'injection' in line.lower(): + results["injections"].append(line.replace('Type:', '').strip()) + + # Databases found + if line.startswith('[*]') and 'available databases' not in line.lower(): + db_name = line[3:].strip() + if db_name: + results["databases"].append(db_name) + + return results + + +class GobusterParser(BaseParser): + """Parser for gobuster output.""" + + def parse(self, output: str) -> Dict[str, Any]: + results = { + "findings": [], + "directories": [], + "files": [], + "raw": output + } + + for line in output.split('\n'): + line = line.strip() + + # Parse found paths + # Format: /path (Status: 200) [Size: 1234] + match = re.search(r'^(/\S*)\s+\(Status:\s*(\d+)\)(?:\s+\[Size:\s*(\d+)\])?', line) + if match: + finding = { + "path": match.group(1), + "status": int(match.group(2)), + "size": int(match.group(3)) if match.group(3) else None + } + + results["findings"].append(finding) + + if finding["path"].endswith('/'): + results["directories"].append(finding["path"]) + else: + results["files"].append(finding["path"]) + + return results + + +class HydraParser(BaseParser): + """Parser for hydra output.""" + + def parse(self, output: str) -> Dict[str, Any]: + results = { + "credentials": [], + "target": None, + "service": None, + "raw": output + } + + for line in output.split('\n'): + line = line.strip() + + # Parse found credentials + # Format: [port][service] host: x login: y password: z + cred_match = re.search(r'\[(\d+)\]\[(\w+)\]\s+host:\s+(\S+)\s+login:\s+(\S+)\s+password:\s+(\S+)', line) + if cred_match: + results["credentials"].append({ + "port": int(cred_match.group(1)), + "service": cred_match.group(2), + "host": cred_match.group(3), + "username": cred_match.group(4), + "password": cred_match.group(5) + }) + results["target"] = cred_match.group(3) + results["service"] = cred_match.group(2) + + return results + + +# Registry of parsers +PARSERS = { + "nmap": NmapParser(), + "nikto": NiktoParser(), + "sqlmap": SQLMapParser(), + "gobuster": GobusterParser(), + "hydra": HydraParser(), +} + + +def parse_tool_output(tool: str, output: str) -> Dict[str, Any]: + """Parse output from a security tool.""" + parser = PARSERS.get(tool.lower()) + if parser: + try: + return parser.parse(output) + except Exception as e: + return {"error": str(e), "raw": output} + return {"raw": output} diff --git a/services/shared/tools.py b/services/shared/tools.py new file mode 100644 index 0000000..9d41190 --- /dev/null +++ b/services/shared/tools.py @@ -0,0 +1,263 @@ +""" +Security tool definitions and command builders. +""" +from typing import Dict, List, Optional, Any + + +SECURITY_TOOLS = { + # ============== Reconnaissance ============== + "nmap": { + "name": "nmap", + "description": "Network scanner and security auditing tool", + "category": "reconnaissance", + "templates": { + "quick": "nmap -T4 -F {target}", + "full": "nmap -sV -sC -O -p- {target}", + "stealth": "nmap -sS -T2 -f {target}", + "udp": "nmap -sU --top-ports 100 {target}", + "vuln": "nmap --script vuln {target}", + "version": "nmap -sV -p {ports} {target}", + "os": "nmap -O --osscan-guess {target}", + }, + "default_template": "quick", + "output_parser": "nmap" + }, + + "masscan": { + "name": "masscan", + "description": "Fast TCP port scanner", + "category": "reconnaissance", + "templates": { + "quick": "masscan {target} --ports 0-1000 --rate 1000", + "full": "masscan {target} --ports 0-65535 --rate 10000", + "top100": "masscan {target} --top-ports 100 --rate 1000", + }, + "default_template": "quick", + }, + + "amass": { + "name": "amass", + "description": "Subdomain enumeration tool", + "category": "reconnaissance", + "templates": { + "passive": "amass enum -passive -d {target}", + "active": "amass enum -active -d {target}", + "intel": "amass intel -d {target}", + }, + "default_template": "passive", + }, + + "theharvester": { + "name": "theHarvester", + "description": "OSINT tool for gathering emails, names, subdomains", + "category": "reconnaissance", + "templates": { + "all": "theHarvester -d {target} -b all", + "google": "theHarvester -d {target} -b google", + "linkedin": "theHarvester -d {target} -b linkedin", + }, + "default_template": "all", + }, + + "whatweb": { + "name": "whatweb", + "description": "Web technology fingerprinting", + "category": "reconnaissance", + "templates": { + "default": "whatweb {target}", + "aggressive": "whatweb -a 3 {target}", + "verbose": "whatweb -v {target}", + }, + "default_template": "default", + }, + + "dnsrecon": { + "name": "dnsrecon", + "description": "DNS enumeration tool", + "category": "reconnaissance", + "templates": { + "standard": "dnsrecon -d {target}", + "zone": "dnsrecon -d {target} -t axfr", + "brute": "dnsrecon -d {target} -t brt", + }, + "default_template": "standard", + }, + + # ============== Vulnerability Scanning ============== + "nikto": { + "name": "nikto", + "description": "Web server vulnerability scanner", + "category": "vulnerability_scanning", + "templates": { + "default": "nikto -h {target}", + "ssl": "nikto -h {target} -ssl", + "tuning": "nikto -h {target} -Tuning x", + "full": "nikto -h {target} -C all", + }, + "default_template": "default", + "output_parser": "nikto" + }, + + "sqlmap": { + "name": "sqlmap", + "description": "SQL injection detection and exploitation", + "category": "vulnerability_scanning", + "templates": { + "test": "sqlmap -u '{target}' --batch", + "dbs": "sqlmap -u '{target}' --batch --dbs", + "tables": "sqlmap -u '{target}' --batch -D {database} --tables", + "dump": "sqlmap -u '{target}' --batch -D {database} -T {table} --dump", + "forms": "sqlmap -u '{target}' --batch --forms", + }, + "default_template": "test", + "output_parser": "sqlmap" + }, + + "wpscan": { + "name": "wpscan", + "description": "WordPress vulnerability scanner", + "category": "vulnerability_scanning", + "templates": { + "default": "wpscan --url {target}", + "enumerate": "wpscan --url {target} -e vp,vt,u", + "aggressive": "wpscan --url {target} -e ap,at,u --plugins-detection aggressive", + }, + "default_template": "default", + }, + + # ============== Web Testing ============== + "gobuster": { + "name": "gobuster", + "description": "Directory/file brute-forcing", + "category": "web_testing", + "templates": { + "dir": "gobuster dir -u {target} -w /usr/share/wordlists/dirb/common.txt", + "big": "gobuster dir -u {target} -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt", + "dns": "gobuster dns -d {target} -w /usr/share/wordlists/dns/subdomains-top1million-5000.txt", + "vhost": "gobuster vhost -u {target} -w /usr/share/wordlists/dns/subdomains-top1million-5000.txt", + }, + "default_template": "dir", + "output_parser": "gobuster" + }, + + "ffuf": { + "name": "ffuf", + "description": "Fast web fuzzer", + "category": "web_testing", + "templates": { + "dir": "ffuf -u {target}/FUZZ -w /usr/share/wordlists/dirb/common.txt", + "vhost": "ffuf -u {target} -H 'Host: FUZZ.{domain}' -w /usr/share/wordlists/dns/subdomains-top1million-5000.txt", + "param": "ffuf -u '{target}?FUZZ=test' -w /usr/share/wordlists/dirb/common.txt", + }, + "default_template": "dir", + }, + + "dirb": { + "name": "dirb", + "description": "Web content scanner", + "category": "web_testing", + "templates": { + "default": "dirb {target}", + "small": "dirb {target} /usr/share/wordlists/dirb/small.txt", + "big": "dirb {target} /usr/share/wordlists/dirb/big.txt", + }, + "default_template": "default", + }, + + # ============== Exploitation ============== + "searchsploit": { + "name": "searchsploit", + "description": "Exploit database search tool", + "category": "exploitation", + "templates": { + "search": "searchsploit {query}", + "exact": "searchsploit -e {query}", + "json": "searchsploit -j {query}", + "path": "searchsploit -p {exploit_id}", + }, + "default_template": "search", + }, + + "hydra": { + "name": "hydra", + "description": "Network login cracker", + "category": "password_attacks", + "templates": { + "ssh": "hydra -l {user} -P /usr/share/wordlists/rockyou.txt {target} ssh", + "ftp": "hydra -l {user} -P /usr/share/wordlists/rockyou.txt {target} ftp", + "http_post": "hydra -l {user} -P /usr/share/wordlists/rockyou.txt {target} http-post-form '{form}'", + "smb": "hydra -l {user} -P /usr/share/wordlists/rockyou.txt {target} smb", + }, + "default_template": "ssh", + "output_parser": "hydra" + }, + + # ============== Network Tools ============== + "netcat": { + "name": "nc", + "description": "Network utility for TCP/UDP connections", + "category": "network", + "templates": { + "listen": "nc -lvnp {port}", + "connect": "nc -v {target} {port}", + "scan": "nc -zv {target} {port_range}", + }, + "default_template": "scan", + }, + + "curl": { + "name": "curl", + "description": "HTTP client", + "category": "web_testing", + "templates": { + "get": "curl -v {target}", + "headers": "curl -I {target}", + "post": "curl -X POST -d '{data}' {target}", + "follow": "curl -L -v {target}", + }, + "default_template": "get", + }, +} + + +def get_tool(name: str) -> Optional[Dict[str, Any]]: + """Get tool definition by name.""" + return SECURITY_TOOLS.get(name.lower()) + + +def get_tools_by_category(category: str) -> List[Dict[str, Any]]: + """Get all tools in a category.""" + return [tool for tool in SECURITY_TOOLS.values() if tool.get("category") == category] + + +def build_command(tool_name: str, template_name: str = None, **kwargs) -> Optional[str]: + """Build a command from a tool template.""" + tool = get_tool(tool_name) + if not tool: + return None + + template_name = template_name or tool.get("default_template") + template = tool.get("templates", {}).get(template_name) + + if not template: + return None + + try: + return template.format(**kwargs) + except KeyError as e: + return None + + +def list_all_tools() -> Dict[str, List[Dict[str, str]]]: + """List all available tools grouped by category.""" + result = {} + for tool in SECURITY_TOOLS.values(): + category = tool.get("category", "other") + if category not in result: + result[category] = [] + result[category].append({ + "name": tool["name"], + "description": tool["description"], + "templates": list(tool.get("templates", {}).keys()) + }) + return result