The Analyzer v2.0 — 30 attack vectors, 9 new exploiters
New vectors added: - 22: SSRF Proof — cloud metadata exfiltration (CRITICAL) - 23: Prototype Pollution — Node.js client/server (HIGH) - 24: WebSocket Hijack — WS origin bypass + injection (HIGH) - 25: Mass Assignment — protected field modification (HIGH) - 26: HTTP Parameter Pollution — WAF bypass (HIGH) - 27: Insecure Deserialization — PHP/Java/Node (CRITICAL) - 28: OAuth Takeover — redirect_uri / state / CSRF (CRITICAL) - 29: Web Cache Poisoning — unkeyed header injection (HIGH) - 30: CRLF Injection — HTTP response splitting (CRITICAL) All vectors PROVE exploitation by dumping data/credentials, not just detecting config issues.
This commit is contained in:
333
commando_mcp_server.py
Normal file
333
commando_mcp_server.py
Normal file
@@ -0,0 +1,333 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Commando VM — MCP Tool Server
|
||||
Exposes all 112+ Commando VM tools as MCP tools via HTTP/SSE.
|
||||
Run: python3 commando_mcp_server.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
# ─── MCP Protocol Implementation (minimal) ────────────────
|
||||
|
||||
TOOLS_DIR = Path("C:/tools")
|
||||
COMMANDS = {
|
||||
"mimikatz": str(TOOLS_DIR / "Mimikatz" / "mimikatz.exe"),
|
||||
"hashcat": str(TOOLS_DIR / "hashcat" / "hashcat.exe"),
|
||||
"bloodhound": str(TOOLS_DIR / "BloodHound"),
|
||||
"crackmapexec": str(TOOLS_DIR / "CrackMapExecWin"),
|
||||
"impacket": str(TOOLS_DIR / "impacket"),
|
||||
"sysinternals": str(TOOLS_DIR / "Sysinternals"),
|
||||
}
|
||||
|
||||
def discover_tools():
|
||||
"""Discover all executable tools in C:\\tools."""
|
||||
tools = []
|
||||
for dirpath, dirnames, filenames in os.walk(str(TOOLS_DIR)):
|
||||
for f in filenames:
|
||||
if f.endswith(('.exe', '.ps1', '.py', '.bat')):
|
||||
rel = os.path.relpath(os.path.join(dirpath, f), str(TOOLS_DIR))
|
||||
tools.append(rel.split('\\')[0]) # category
|
||||
return sorted(set(tools))
|
||||
|
||||
def run_tool(tool_name, args, timeout=60):
|
||||
"""Run a tool from Commando VM tools directory and return output."""
|
||||
# Find the tool
|
||||
for dirpath, dirnames, filenames in os.walk(str(TOOLS_DIR)):
|
||||
for f in filenames:
|
||||
if f.lower().startswith(tool_name.lower()) or tool_name.lower() in f.lower():
|
||||
exe = os.path.join(dirpath, f)
|
||||
try:
|
||||
cmd = [exe] + args.split() if args else [exe]
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=timeout
|
||||
)
|
||||
return {
|
||||
"tool": f,
|
||||
"stdout": result.stdout[:5000],
|
||||
"stderr": result.stderr[:1000],
|
||||
"exit_code": result.returncode
|
||||
}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {"tool": f, "error": f"Timed out after {timeout}s"}
|
||||
except Exception as e:
|
||||
return {"tool": f, "error": str(e)}
|
||||
return {"error": f"Tool '{tool_name}' not found in Commando VM"}
|
||||
|
||||
def run_powershell(script, timeout=60):
|
||||
"""Run a PowerShell command and return output."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["powershell", "-Command", script],
|
||||
capture_output=True, text=True, timeout=timeout
|
||||
)
|
||||
return {
|
||||
"stdout": result.stdout[:5000],
|
||||
"stderr": result.stderr[:1000],
|
||||
"exit_code": result.returncode
|
||||
}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {"error": f"Timed out after {timeout}s"}
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
# ─── MCP Tool Definitions ────────────────────────────────
|
||||
|
||||
def handle_request(request):
|
||||
"""Handle MCP protocol request."""
|
||||
req = json.loads(request) if isinstance(request, str) else request
|
||||
method = req.get("method", "")
|
||||
req_id = req.get("id", 1)
|
||||
|
||||
if method == "initialize":
|
||||
return {"jsonrpc": "2.0", "id": req_id, "result": {
|
||||
"protocolVersion": "0.1.0",
|
||||
"capabilities": {"tools": {}},
|
||||
"serverInfo": {"name": "commando-mcp", "version": "1.0.0"}
|
||||
}}
|
||||
|
||||
elif method == "tools/list":
|
||||
tools = discover_tools()
|
||||
categories = sorted(set(tools))
|
||||
|
||||
tool_list = [
|
||||
{
|
||||
"name": f"commando_{cat.lower().replace('-','_').replace(' ','_')}",
|
||||
"description": f"Run {cat} tools from Commando VM",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tool": {
|
||||
"type": "string",
|
||||
"description": f"Tool name from {cat} category"
|
||||
},
|
||||
"args": {
|
||||
"type": "string",
|
||||
"description": "Command-line arguments"
|
||||
},
|
||||
"timeout": {
|
||||
"type": "number",
|
||||
"description": "Timeout in seconds (default: 60)"
|
||||
}
|
||||
},
|
||||
"required": ["tool"]
|
||||
}
|
||||
}
|
||||
for cat in categories[:50] # Max 50 tools to avoid overwhelm
|
||||
]
|
||||
|
||||
# Add generic tool runner
|
||||
tool_list.append({
|
||||
"name": "commando_run",
|
||||
"description": "Run any tool from Commando VM's C:\\tools directory",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tool": {"type": "string", "description": "Tool name (e.g., mimikatz, hashcat, nmap)"},
|
||||
"args": {"type": "string", "description": "Command-line arguments"},
|
||||
"timeout": {"type": "number", "description": "Timeout in seconds (default: 60)"}
|
||||
},
|
||||
"required": ["tool"]
|
||||
}
|
||||
})
|
||||
|
||||
tool_list.append({
|
||||
"name": "commando_powershell",
|
||||
"description": "Run a PowerShell command on Commando VM",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {"type": "string", "description": "PowerShell command to execute"},
|
||||
"timeout": {"type": "number", "description": "Timeout in seconds (default: 60)"}
|
||||
},
|
||||
"required": ["command"]
|
||||
}
|
||||
})
|
||||
|
||||
tool_list.append({
|
||||
"name": "commando_list_tools",
|
||||
"description": "List all available tools in Commando VM",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
})
|
||||
|
||||
return {"jsonrpc": "2.0", "id": req_id, "result": {"tools": tool_list}}
|
||||
|
||||
elif method == "tools/call":
|
||||
params = req.get("params", {})
|
||||
name = params.get("name", "")
|
||||
args = params.get("arguments", {})
|
||||
|
||||
if name == "commando_list_tools":
|
||||
tools = discover_tools()
|
||||
categories = sorted(set(tools))
|
||||
return {"jsonrpc": "2.0", "id": req_id, "result": {
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": f"Commando VM has {len(tools)} tool categories:\n" + "\n".join(f" - {t}" for t in categories)
|
||||
}]
|
||||
}}
|
||||
|
||||
elif name == "commando_run":
|
||||
result = run_tool(args.get("tool", ""), args.get("args", ""), args.get("timeout", 60))
|
||||
output = ""
|
||||
if "stdout" in result:
|
||||
output = result["stdout"]
|
||||
elif "error" in result:
|
||||
output = f"Error: {result['error']}"
|
||||
return {"jsonrpc": "2.0", "id": req_id, "result": {
|
||||
"content": [{"type": "text", "text": output}]
|
||||
}}
|
||||
|
||||
elif name == "commando_powershell":
|
||||
result = run_powershell(args.get("command", ""), args.get("timeout", 60))
|
||||
output = result.get("stdout", result.get("error", "No output"))
|
||||
return {"jsonrpc": "2.0", "id": req_id, "result": {
|
||||
"content": [{"type": "text", "text": output}]
|
||||
}}
|
||||
|
||||
else:
|
||||
# Try running by tool name from description
|
||||
cat_name = name.replace("commando_", "").replace("_", "-")
|
||||
result = run_tool(cat_name, args.get("args", ""), args.get("timeout", 60))
|
||||
return {"jsonrpc": "2.0", "id": req_id, "result": {
|
||||
"content": [{"type": "text", "text": result.get("stdout", result.get("error", "No output"))}]
|
||||
}}
|
||||
|
||||
return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": "Method not found"}}
|
||||
|
||||
# ─── STDIO Transport ─────────────────────────────────────
|
||||
|
||||
def main():
|
||||
"""Run MCP server over stdio (for Hermes integration)."""
|
||||
import sys
|
||||
for line in sys.stdin:
|
||||
if not line.strip():
|
||||
continue
|
||||
try:
|
||||
response = handle_request(line.strip())
|
||||
print(json.dumps(response), flush=True)
|
||||
except Exception as e:
|
||||
print(json.dumps({"jsonrpc": "2.0", "error": {"code": -32700, "message": str(e)}}), flush=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
if "--http" in sys.argv:
|
||||
# HTTP/SSE mode — for Hermes MCP integration
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
import urllib.parse
|
||||
import queue
|
||||
|
||||
PORT = int(sys.argv[sys.argv.index("--http") + 1]) if "--http" in sys.argv and len(sys.argv) > sys.argv.index("--http") + 1 else 8092
|
||||
|
||||
# Store SSE response queues per session
|
||||
sse_clients = {}
|
||||
next_session = 1
|
||||
|
||||
class MCPHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
parsed = urllib.parse.urlparse(self.path)
|
||||
|
||||
if parsed.path == "/health":
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"status": "ok", "server": "commando-mcp", "tools": len(discover_tools())}).encode())
|
||||
return
|
||||
|
||||
if parsed.path == "/mcp" or parsed.path == "/sse":
|
||||
# SSE connection
|
||||
accept = self.headers.get("Accept", "")
|
||||
if "text/event-stream" not in accept and parsed.path == "/mcp":
|
||||
# Return initialize for non-SSE clients
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.send_header("MCP-Version", "0.1.0")
|
||||
self.end_headers()
|
||||
resp = handle_request({"jsonrpc": "2.0", "id": "init", "method": "initialize"})
|
||||
self.wfile.write(json.dumps(resp).encode())
|
||||
return
|
||||
|
||||
# SSE mode
|
||||
global next_session
|
||||
session_id = f"sess_{next_session}"
|
||||
next_session += 1
|
||||
msg_queue = queue.Queue()
|
||||
sse_clients[session_id] = msg_queue
|
||||
|
||||
endpoint = f"/mcp?session={session_id}"
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/event-stream")
|
||||
self.send_header("Cache-Control", "no-cache")
|
||||
self.send_header("Connection", "keep-alive")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
|
||||
# Send session event
|
||||
self.wfile.write(f"event: endpoint\ndata: {endpoint}\n\n".encode())
|
||||
self.wfile.write(f"event: session_id\ndata: {session_id}\n\n".encode())
|
||||
self.wfile.flush()
|
||||
|
||||
# Keep connection open and send responses
|
||||
while True:
|
||||
try:
|
||||
msg = msg_queue.get(timeout=30)
|
||||
self.wfile.write(f"data: {json.dumps(msg)}\n\n".encode())
|
||||
self.wfile.flush()
|
||||
except queue.Empty:
|
||||
self.wfile.write(": keepalive\n\n".encode())
|
||||
self.wfile.flush()
|
||||
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
|
||||
def do_POST(self):
|
||||
global next_session
|
||||
content_length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(content_length) if content_length > 0 else b"{}"
|
||||
parsed = urllib.parse.urlparse(self.path)
|
||||
|
||||
# Parse session from query string
|
||||
params = urllib.parse.parse_qs(parsed.query)
|
||||
session_id = params.get("session", [None])[0]
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
|
||||
try:
|
||||
req = json.loads(body.decode())
|
||||
resp = handle_request(req)
|
||||
|
||||
if session_id and session_id in sse_clients:
|
||||
# Send response via SSE
|
||||
sse_clients[session_id].put(resp)
|
||||
self.wfile.write(json.dumps({"jsonrpc": "2.0", "id": req.get("id"), "result": {"_delivered": "via_sse"}}).encode())
|
||||
else:
|
||||
# Direct response
|
||||
self.wfile.write(json.dumps(resp).encode())
|
||||
except Exception as e:
|
||||
self.wfile.write(json.dumps({"jsonrpc": "2.0", "error": {"code": -32700, "message": str(e)}}).encode())
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass # Suppress logs
|
||||
|
||||
print(f"Commando MCP server running on port {PORT}")
|
||||
print(f" Endpoint: http://0.0.0.0:{PORT}/mcp")
|
||||
print(f" Health: http://0.0.0.0:{PORT}/health")
|
||||
server = HTTPServer(("0.0.0.0", PORT), MCPHandler)
|
||||
server.serve_forever()
|
||||
else:
|
||||
# STDIO mode
|
||||
main()
|
||||
Reference in New Issue
Block a user