mirror of
https://github.com/SunZhimin2021/AIPentest.git
synced 2025-11-05 10:53:16 +00:00
Add files via upload
通过MCP使用kali的方法
This commit is contained in:
parent
4ce3d540e3
commit
e3c70c5944
572
mcpforpentest/kali_server.py
Normal file
572
mcpforpentest/kali_server.py
Normal file
@ -0,0 +1,572 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# This script connect the MCP AI agent to Kali Linux terminal and API Server.
|
||||
|
||||
# some of the code here was inspired from https://github.com/whit3rabbit0/project_astro , be sure to check them out
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import traceback
|
||||
import threading
|
||||
from typing import Dict, Any
|
||||
from flask import Flask, request, jsonify
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||
handlers=[
|
||||
logging.StreamHandler(sys.stdout)
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Configuration
|
||||
API_PORT = int(os.environ.get("API_PORT", 5000))
|
||||
DEBUG_MODE = os.environ.get("DEBUG_MODE", "0").lower() in ("1", "true", "yes", "y")
|
||||
COMMAND_TIMEOUT = 180 # 5 minutes default timeout
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
class CommandExecutor:
|
||||
"""Class to handle command execution with better timeout management"""
|
||||
|
||||
def __init__(self, command: str, timeout: int = COMMAND_TIMEOUT):
|
||||
self.command = command
|
||||
self.timeout = timeout
|
||||
self.process = None
|
||||
self.stdout_data = ""
|
||||
self.stderr_data = ""
|
||||
self.stdout_thread = None
|
||||
self.stderr_thread = None
|
||||
self.return_code = None
|
||||
self.timed_out = False
|
||||
|
||||
def _read_stdout(self):
|
||||
"""Thread function to continuously read stdout"""
|
||||
for line in iter(self.process.stdout.readline, ''):
|
||||
self.stdout_data += line
|
||||
|
||||
def _read_stderr(self):
|
||||
"""Thread function to continuously read stderr"""
|
||||
for line in iter(self.process.stderr.readline, ''):
|
||||
self.stderr_data += line
|
||||
|
||||
def execute(self) -> Dict[str, Any]:
|
||||
"""Execute the command and handle timeout gracefully"""
|
||||
logger.info(f"Executing command: {self.command}")
|
||||
|
||||
try:
|
||||
self.process = subprocess.Popen(
|
||||
self.command,
|
||||
shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=1 # Line buffered
|
||||
)
|
||||
|
||||
# Start threads to read output continuously
|
||||
self.stdout_thread = threading.Thread(target=self._read_stdout)
|
||||
self.stderr_thread = threading.Thread(target=self._read_stderr)
|
||||
self.stdout_thread.daemon = True
|
||||
self.stderr_thread.daemon = True
|
||||
self.stdout_thread.start()
|
||||
self.stderr_thread.start()
|
||||
|
||||
# Wait for the process to complete or timeout
|
||||
try:
|
||||
self.return_code = self.process.wait(timeout=self.timeout)
|
||||
# Process completed, join the threads
|
||||
self.stdout_thread.join()
|
||||
self.stderr_thread.join()
|
||||
except subprocess.TimeoutExpired:
|
||||
# Process timed out but we might have partial results
|
||||
self.timed_out = True
|
||||
logger.warning(f"Command timed out after {self.timeout} seconds. Terminating process.")
|
||||
|
||||
# Try to terminate gracefully first
|
||||
self.process.terminate()
|
||||
try:
|
||||
self.process.wait(timeout=5) # Give it 5 seconds to terminate
|
||||
except subprocess.TimeoutExpired:
|
||||
# Force kill if it doesn't terminate
|
||||
logger.warning("Process not responding to termination. Killing.")
|
||||
self.process.kill()
|
||||
|
||||
# Update final output
|
||||
self.return_code = -1
|
||||
|
||||
# Always consider it a success if we have output, even with timeout
|
||||
success = True if self.timed_out and (self.stdout_data or self.stderr_data) else (self.return_code == 0)
|
||||
|
||||
return {
|
||||
"stdout": self.stdout_data,
|
||||
"stderr": self.stderr_data,
|
||||
"return_code": self.return_code,
|
||||
"success": success,
|
||||
"timed_out": self.timed_out,
|
||||
"partial_results": self.timed_out and (self.stdout_data or self.stderr_data)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing command: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return {
|
||||
"stdout": self.stdout_data,
|
||||
"stderr": f"Error executing command: {str(e)}\n{self.stderr_data}",
|
||||
"return_code": -1,
|
||||
"success": False,
|
||||
"timed_out": False,
|
||||
"partial_results": bool(self.stdout_data or self.stderr_data)
|
||||
}
|
||||
|
||||
|
||||
def execute_command(command: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Execute a shell command and return the result
|
||||
|
||||
Args:
|
||||
command: The command to execute
|
||||
|
||||
Returns:
|
||||
A dictionary containing the stdout, stderr, and return code
|
||||
"""
|
||||
executor = CommandExecutor(command)
|
||||
return executor.execute()
|
||||
|
||||
|
||||
@app.route("/api/command", methods=["POST"])
|
||||
def generic_command():
|
||||
"""Execute any command provided in the request."""
|
||||
try:
|
||||
params = request.json
|
||||
command = params.get("command", "")
|
||||
|
||||
if not command:
|
||||
logger.warning("Command endpoint called without command parameter")
|
||||
return jsonify({
|
||||
"error": "Command parameter is required"
|
||||
}), 400
|
||||
|
||||
result = execute_command(command)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in command endpoint: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return jsonify({
|
||||
"error": f"Server error: {str(e)}"
|
||||
}), 500
|
||||
|
||||
|
||||
@app.route("/api/tools/nmap", methods=["POST"])
|
||||
def nmap():
|
||||
"""Execute nmap scan with the provided parameters."""
|
||||
try:
|
||||
params = request.json
|
||||
target = params.get("target", "")
|
||||
scan_type = params.get("scan_type", "-sCV")
|
||||
ports = params.get("ports", "")
|
||||
additional_args = params.get("additional_args", "-T4 -Pn")
|
||||
|
||||
if not target:
|
||||
logger.warning("Nmap called without target parameter")
|
||||
return jsonify({
|
||||
"error": "Target parameter is required"
|
||||
}), 400
|
||||
|
||||
command = f"nmap {scan_type}"
|
||||
|
||||
if ports:
|
||||
command += f" -p {ports}"
|
||||
|
||||
if additional_args:
|
||||
# Basic validation for additional args - more sophisticated validation would be better
|
||||
command += f" {additional_args}"
|
||||
|
||||
command += f" {target}"
|
||||
|
||||
result = execute_command(command)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in nmap endpoint: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return jsonify({
|
||||
"error": f"Server error: {str(e)}"
|
||||
}), 500
|
||||
|
||||
@app.route("/api/tools/gobuster", methods=["POST"])
|
||||
def gobuster():
|
||||
"""Execute gobuster with the provided parameters."""
|
||||
try:
|
||||
params = request.json
|
||||
url = params.get("url", "")
|
||||
mode = params.get("mode", "dir")
|
||||
wordlist = params.get("wordlist", "/usr/share/wordlists/dirb/common.txt")
|
||||
additional_args = params.get("additional_args", "")
|
||||
|
||||
if not url:
|
||||
logger.warning("Gobuster called without URL parameter")
|
||||
return jsonify({
|
||||
"error": "URL parameter is required"
|
||||
}), 400
|
||||
|
||||
# Validate mode
|
||||
if mode not in ["dir", "dns", "fuzz", "vhost"]:
|
||||
logger.warning(f"Invalid gobuster mode: {mode}")
|
||||
return jsonify({
|
||||
"error": f"Invalid mode: {mode}. Must be one of: dir, dns, fuzz, vhost"
|
||||
}), 400
|
||||
|
||||
command = f"gobuster {mode} -u {url} -w {wordlist}"
|
||||
|
||||
if additional_args:
|
||||
command += f" {additional_args}"
|
||||
|
||||
result = execute_command(command)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in gobuster endpoint: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return jsonify({
|
||||
"error": f"Server error: {str(e)}"
|
||||
}), 500
|
||||
|
||||
@app.route("/api/tools/dirb", methods=["POST"])
|
||||
def dirb():
|
||||
"""Execute dirb with the provided parameters."""
|
||||
try:
|
||||
params = request.json
|
||||
url = params.get("url", "")
|
||||
wordlist = params.get("wordlist", "/usr/share/wordlists/dirb/common.txt")
|
||||
additional_args = params.get("additional_args", "")
|
||||
|
||||
if not url:
|
||||
logger.warning("Dirb called without URL parameter")
|
||||
return jsonify({
|
||||
"error": "URL parameter is required"
|
||||
}), 400
|
||||
|
||||
command = f"dirb {url} {wordlist}"
|
||||
|
||||
if additional_args:
|
||||
command += f" {additional_args}"
|
||||
|
||||
result = execute_command(command)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in dirb endpoint: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return jsonify({
|
||||
"error": f"Server error: {str(e)}"
|
||||
}), 500
|
||||
|
||||
@app.route("/api/tools/nikto", methods=["POST"])
|
||||
def nikto():
|
||||
"""Execute nikto with the provided parameters."""
|
||||
try:
|
||||
params = request.json
|
||||
target = params.get("target", "")
|
||||
additional_args = params.get("additional_args", "")
|
||||
|
||||
if not target:
|
||||
logger.warning("Nikto called without target parameter")
|
||||
return jsonify({
|
||||
"error": "Target parameter is required"
|
||||
}), 400
|
||||
|
||||
command = f"nikto -h {target}"
|
||||
|
||||
if additional_args:
|
||||
command += f" {additional_args}"
|
||||
|
||||
result = execute_command(command)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in nikto endpoint: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return jsonify({
|
||||
"error": f"Server error: {str(e)}"
|
||||
}), 500
|
||||
|
||||
@app.route("/api/tools/sqlmap", methods=["POST"])
|
||||
def sqlmap():
|
||||
"""Execute sqlmap with the provided parameters."""
|
||||
try:
|
||||
params = request.json
|
||||
url = params.get("url", "")
|
||||
data = params.get("data", "")
|
||||
additional_args = params.get("additional_args", "")
|
||||
|
||||
if not url:
|
||||
logger.warning("SQLMap called without URL parameter")
|
||||
return jsonify({
|
||||
"error": "URL parameter is required"
|
||||
}), 400
|
||||
|
||||
command = f"sqlmap -u {url} --batch"
|
||||
|
||||
if data:
|
||||
command += f" --data=\"{data}\""
|
||||
|
||||
if additional_args:
|
||||
command += f" {additional_args}"
|
||||
|
||||
result = execute_command(command)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in sqlmap endpoint: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return jsonify({
|
||||
"error": f"Server error: {str(e)}"
|
||||
}), 500
|
||||
|
||||
@app.route("/api/tools/metasploit", methods=["POST"])
|
||||
def metasploit():
|
||||
"""Execute metasploit module with the provided parameters."""
|
||||
try:
|
||||
params = request.json
|
||||
module = params.get("module", "")
|
||||
options = params.get("options", {})
|
||||
|
||||
if not module:
|
||||
logger.warning("Metasploit called without module parameter")
|
||||
return jsonify({
|
||||
"error": "Module parameter is required"
|
||||
}), 400
|
||||
|
||||
# Format options for Metasploit
|
||||
options_str = ""
|
||||
for key, value in options.items():
|
||||
options_str += f" {key}={value}"
|
||||
|
||||
# Create an MSF resource script
|
||||
resource_content = f"use {module}\n"
|
||||
for key, value in options.items():
|
||||
resource_content += f"set {key} {value}\n"
|
||||
resource_content += "exploit\n"
|
||||
|
||||
# Save resource script to a temporary file
|
||||
resource_file = "/tmp/mcp_msf_resource.rc"
|
||||
with open(resource_file, "w") as f:
|
||||
f.write(resource_content)
|
||||
|
||||
command = f"msfconsole -q -r {resource_file}"
|
||||
result = execute_command(command)
|
||||
|
||||
# Clean up the temporary file
|
||||
try:
|
||||
os.remove(resource_file)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error removing temporary resource file: {str(e)}")
|
||||
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in metasploit endpoint: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return jsonify({
|
||||
"error": f"Server error: {str(e)}"
|
||||
}), 500
|
||||
|
||||
@app.route("/api/tools/hydra", methods=["POST"])
|
||||
def hydra():
|
||||
"""Execute hydra with the provided parameters."""
|
||||
try:
|
||||
params = request.json
|
||||
target = params.get("target", "")
|
||||
service = params.get("service", "")
|
||||
username = params.get("username", "")
|
||||
username_file = params.get("username_file", "")
|
||||
password = params.get("password", "")
|
||||
password_file = params.get("password_file", "")
|
||||
additional_args = params.get("additional_args", "")
|
||||
|
||||
if not target or not service:
|
||||
logger.warning("Hydra called without target or service parameter")
|
||||
return jsonify({
|
||||
"error": "Target and service parameters are required"
|
||||
}), 400
|
||||
|
||||
if not (username or username_file) or not (password or password_file):
|
||||
logger.warning("Hydra called without username/password parameters")
|
||||
return jsonify({
|
||||
"error": "Username/username_file and password/password_file are required"
|
||||
}), 400
|
||||
|
||||
command = f"hydra -t 4"
|
||||
|
||||
if username:
|
||||
command += f" -l {username}"
|
||||
elif username_file:
|
||||
command += f" -L {username_file}"
|
||||
|
||||
if password:
|
||||
command += f" -p {password}"
|
||||
elif password_file:
|
||||
command += f" -P {password_file}"
|
||||
|
||||
if additional_args:
|
||||
command += f" {additional_args}"
|
||||
|
||||
command += f" {target} {service}"
|
||||
|
||||
result = execute_command(command)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in hydra endpoint: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return jsonify({
|
||||
"error": f"Server error: {str(e)}"
|
||||
}), 500
|
||||
|
||||
@app.route("/api/tools/john", methods=["POST"])
|
||||
def john():
|
||||
"""Execute john with the provided parameters."""
|
||||
try:
|
||||
params = request.json
|
||||
hash_file = params.get("hash_file", "")
|
||||
wordlist = params.get("wordlist", "/usr/share/wordlists/rockyou.txt")
|
||||
format_type = params.get("format", "")
|
||||
additional_args = params.get("additional_args", "")
|
||||
|
||||
if not hash_file:
|
||||
logger.warning("John called without hash_file parameter")
|
||||
return jsonify({
|
||||
"error": "Hash file parameter is required"
|
||||
}), 400
|
||||
|
||||
command = f"john"
|
||||
|
||||
if format_type:
|
||||
command += f" --format={format_type}"
|
||||
|
||||
if wordlist:
|
||||
command += f" --wordlist={wordlist}"
|
||||
|
||||
if additional_args:
|
||||
command += f" {additional_args}"
|
||||
|
||||
command += f" {hash_file}"
|
||||
|
||||
result = execute_command(command)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in john endpoint: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return jsonify({
|
||||
"error": f"Server error: {str(e)}"
|
||||
}), 500
|
||||
|
||||
@app.route("/api/tools/wpscan", methods=["POST"])
|
||||
def wpscan():
|
||||
"""Execute wpscan with the provided parameters."""
|
||||
try:
|
||||
params = request.json
|
||||
url = params.get("url", "")
|
||||
additional_args = params.get("additional_args", "")
|
||||
|
||||
if not url:
|
||||
logger.warning("WPScan called without URL parameter")
|
||||
return jsonify({
|
||||
"error": "URL parameter is required"
|
||||
}), 400
|
||||
|
||||
command = f"wpscan --url {url}"
|
||||
|
||||
if additional_args:
|
||||
command += f" {additional_args}"
|
||||
|
||||
result = execute_command(command)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in wpscan endpoint: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return jsonify({
|
||||
"error": f"Server error: {str(e)}"
|
||||
}), 500
|
||||
|
||||
@app.route("/api/tools/enum4linux", methods=["POST"])
|
||||
def enum4linux():
|
||||
"""Execute enum4linux with the provided parameters."""
|
||||
try:
|
||||
params = request.json
|
||||
target = params.get("target", "")
|
||||
additional_args = params.get("additional_args", "-a")
|
||||
|
||||
if not target:
|
||||
logger.warning("Enum4linux called without target parameter")
|
||||
return jsonify({
|
||||
"error": "Target parameter is required"
|
||||
}), 400
|
||||
|
||||
command = f"enum4linux {additional_args} {target}"
|
||||
|
||||
result = execute_command(command)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in enum4linux endpoint: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return jsonify({
|
||||
"error": f"Server error: {str(e)}"
|
||||
}), 500
|
||||
|
||||
|
||||
# Health check endpoint
|
||||
@app.route("/health", methods=["GET"])
|
||||
def health_check():
|
||||
"""Health check endpoint."""
|
||||
# Check if essential tools are installed
|
||||
essential_tools = ["nmap", "gobuster", "dirb", "nikto"]
|
||||
tools_status = {}
|
||||
|
||||
for tool in essential_tools:
|
||||
try:
|
||||
result = execute_command(f"which {tool}")
|
||||
tools_status[tool] = result["success"]
|
||||
except:
|
||||
tools_status[tool] = False
|
||||
|
||||
all_essential_tools_available = all(tools_status.values())
|
||||
|
||||
return jsonify({
|
||||
"status": "healthy",
|
||||
"message": "Kali Linux Tools API Server is running",
|
||||
"tools_status": tools_status,
|
||||
"all_essential_tools_available": all_essential_tools_available
|
||||
})
|
||||
|
||||
@app.route("/mcp/capabilities", methods=["GET"])
|
||||
def get_capabilities():
|
||||
# Return tool capabilities similar to our existing MCP server
|
||||
pass
|
||||
|
||||
@app.route("/mcp/tools/kali_tools/<tool_name>", methods=["POST"])
|
||||
def execute_tool(tool_name):
|
||||
# Direct tool execution without going through the API server
|
||||
pass
|
||||
|
||||
def parse_args():
|
||||
"""Parse command line arguments."""
|
||||
parser = argparse.ArgumentParser(description="Run the Kali Linux API Server")
|
||||
parser.add_argument("--debug", action="store_true", help="Enable debug mode")
|
||||
parser.add_argument("--port", type=int, default=API_PORT, help=f"Port for the API server (default: {API_PORT})")
|
||||
return parser.parse_args()
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
|
||||
# Set configuration from command line arguments
|
||||
if args.debug:
|
||||
DEBUG_MODE = True
|
||||
os.environ["DEBUG_MODE"] = "1"
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
if args.port != API_PORT:
|
||||
API_PORT = args.port
|
||||
|
||||
logger.info(f"Starting Kali Linux Tools API Server on port {API_PORT}")
|
||||
app.run(host="0.0.0.0", port=API_PORT, debug=DEBUG_MODE)
|
||||
417
mcpforpentest/mcp_server.py
Normal file
417
mcpforpentest/mcp_server.py
Normal file
@ -0,0 +1,417 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# This script connect the MCP AI agent to Kali Linux terminal and API Server.
|
||||
|
||||
# some of the code here was inspired from https://github.com/whit3rabbit0/project_astro , be sure to check them out
|
||||
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
import requests
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||
handlers=[
|
||||
logging.StreamHandler(sys.stdout)
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Default configuration
|
||||
DEFAULT_KALI_SERVER = "http://localhost:5000" # change to your linux IP
|
||||
DEFAULT_REQUEST_TIMEOUT = 300 # 5 minutes default timeout for API requests
|
||||
|
||||
class KaliToolsClient:
|
||||
"""Client for communicating with the Kali Linux Tools API Server"""
|
||||
|
||||
def __init__(self, server_url: str, timeout: int = DEFAULT_REQUEST_TIMEOUT):
|
||||
"""
|
||||
Initialize the Kali Tools Client
|
||||
|
||||
Args:
|
||||
server_url: URL of the Kali Tools API Server
|
||||
timeout: Request timeout in seconds
|
||||
"""
|
||||
self.server_url = server_url.rstrip("/")
|
||||
self.timeout = timeout
|
||||
logger.info(f"Initialized Kali Tools Client connecting to {server_url}")
|
||||
|
||||
def safe_get(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Perform a GET request with optional query parameters.
|
||||
|
||||
Args:
|
||||
endpoint: API endpoint path (without leading slash)
|
||||
params: Optional query parameters
|
||||
|
||||
Returns:
|
||||
Response data as dictionary
|
||||
"""
|
||||
if params is None:
|
||||
params = {}
|
||||
|
||||
url = f"{self.server_url}/{endpoint}"
|
||||
|
||||
try:
|
||||
logger.debug(f"GET {url} with params: {params}")
|
||||
response = requests.get(url, params=params, timeout=self.timeout)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Request failed: {str(e)}")
|
||||
return {"error": f"Request failed: {str(e)}", "success": False}
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error: {str(e)}")
|
||||
return {"error": f"Unexpected error: {str(e)}", "success": False}
|
||||
|
||||
def safe_post(self, endpoint: str, json_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Perform a POST request with JSON data.
|
||||
|
||||
Args:
|
||||
endpoint: API endpoint path (without leading slash)
|
||||
json_data: JSON data to send
|
||||
|
||||
Returns:
|
||||
Response data as dictionary
|
||||
"""
|
||||
url = f"{self.server_url}/{endpoint}"
|
||||
|
||||
try:
|
||||
logger.debug(f"POST {url} with data: {json_data}")
|
||||
response = requests.post(url, json=json_data, timeout=self.timeout)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Request failed: {str(e)}")
|
||||
return {"error": f"Request failed: {str(e)}", "success": False}
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error: {str(e)}")
|
||||
return {"error": f"Unexpected error: {str(e)}", "success": False}
|
||||
|
||||
def execute_command(self, command: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Execute a generic command on the Kali server
|
||||
|
||||
Args:
|
||||
command: Command to execute
|
||||
|
||||
Returns:
|
||||
Command execution results
|
||||
"""
|
||||
return self.safe_post("api/command", {"command": command})
|
||||
|
||||
def check_health(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Check the health of the Kali Tools API Server
|
||||
|
||||
Returns:
|
||||
Health status information
|
||||
"""
|
||||
return self.safe_get("health")
|
||||
|
||||
def setup_mcp_server(kali_client: KaliToolsClient) -> FastMCP:
|
||||
"""
|
||||
Set up the MCP server with all tool functions
|
||||
|
||||
Args:
|
||||
kali_client: Initialized KaliToolsClient
|
||||
|
||||
Returns:
|
||||
Configured FastMCP instance
|
||||
"""
|
||||
mcp = FastMCP("kali-mcp")
|
||||
|
||||
@mcp.tool()
|
||||
def nmap_scan(target: str, scan_type: str = "-sV", ports: str = "", additional_args: str = "") -> Dict[str, Any]:
|
||||
"""
|
||||
Execute an Nmap scan against a target.
|
||||
|
||||
Args:
|
||||
target: The IP address or hostname to scan
|
||||
scan_type: Scan type (e.g., -sV for version detection)
|
||||
ports: Comma-separated list of ports or port ranges
|
||||
additional_args: Additional Nmap arguments
|
||||
|
||||
Returns:
|
||||
Scan results
|
||||
"""
|
||||
data = {
|
||||
"target": target,
|
||||
"scan_type": scan_type,
|
||||
"ports": ports,
|
||||
"additional_args": additional_args
|
||||
}
|
||||
return kali_client.safe_post("api/tools/nmap", data)
|
||||
|
||||
@mcp.tool()
|
||||
def gobuster_scan(url: str, mode: str = "dir", wordlist: str = "/usr/share/wordlists/dirb/common.txt", additional_args: str = "") -> Dict[str, Any]:
|
||||
"""
|
||||
Execute Gobuster to find directories, DNS subdomains, or virtual hosts.
|
||||
|
||||
Args:
|
||||
url: The target URL
|
||||
mode: Scan mode (dir, dns, fuzz, vhost)
|
||||
wordlist: Path to wordlist file
|
||||
additional_args: Additional Gobuster arguments
|
||||
|
||||
Returns:
|
||||
Scan results
|
||||
"""
|
||||
data = {
|
||||
"url": url,
|
||||
"mode": mode,
|
||||
"wordlist": wordlist,
|
||||
"additional_args": additional_args
|
||||
}
|
||||
return kali_client.safe_post("api/tools/gobuster", data)
|
||||
|
||||
@mcp.tool()
|
||||
def dirb_scan(url: str, wordlist: str = "/usr/share/wordlists/dirb/common.txt", additional_args: str = "") -> Dict[str, Any]:
|
||||
"""
|
||||
Execute Dirb web content scanner.
|
||||
|
||||
Args:
|
||||
url: The target URL
|
||||
wordlist: Path to wordlist file
|
||||
additional_args: Additional Dirb arguments
|
||||
|
||||
Returns:
|
||||
Scan results
|
||||
"""
|
||||
data = {
|
||||
"url": url,
|
||||
"wordlist": wordlist,
|
||||
"additional_args": additional_args
|
||||
}
|
||||
return kali_client.safe_post("api/tools/dirb", data)
|
||||
|
||||
@mcp.tool()
|
||||
def nikto_scan(target: str, additional_args: str = "") -> Dict[str, Any]:
|
||||
"""
|
||||
Execute Nikto web server scanner.
|
||||
|
||||
Args:
|
||||
target: The target URL or IP
|
||||
additional_args: Additional Nikto arguments
|
||||
|
||||
Returns:
|
||||
Scan results
|
||||
"""
|
||||
data = {
|
||||
"target": target,
|
||||
"additional_args": additional_args
|
||||
}
|
||||
return kali_client.safe_post("api/tools/nikto", data)
|
||||
|
||||
@mcp.tool()
|
||||
def sqlmap_scan(url: str, data: str = "", additional_args: str = "") -> Dict[str, Any]:
|
||||
"""
|
||||
Execute SQLmap SQL injection scanner.
|
||||
|
||||
Args:
|
||||
url: The target URL
|
||||
data: POST data string
|
||||
additional_args: Additional SQLmap arguments
|
||||
|
||||
Returns:
|
||||
Scan results
|
||||
"""
|
||||
post_data = {
|
||||
"url": url,
|
||||
"data": data,
|
||||
"additional_args": additional_args
|
||||
}
|
||||
return kali_client.safe_post("api/tools/sqlmap", post_data)
|
||||
|
||||
@mcp.tool()
|
||||
def metasploit_run(module: str, options: Dict[str, Any] = {}) -> Dict[str, Any]:
|
||||
"""
|
||||
Execute a Metasploit module.
|
||||
|
||||
Args:
|
||||
module: The Metasploit module path
|
||||
options: Dictionary of module options
|
||||
|
||||
Returns:
|
||||
Module execution results
|
||||
"""
|
||||
data = {
|
||||
"module": module,
|
||||
"options": options
|
||||
}
|
||||
return kali_client.safe_post("api/tools/metasploit", data)
|
||||
|
||||
@mcp.tool()
|
||||
def hydra_attack(
|
||||
target: str,
|
||||
service: str,
|
||||
username: str = "",
|
||||
username_file: str = "",
|
||||
password: str = "",
|
||||
password_file: str = "",
|
||||
additional_args: str = ""
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Execute Hydra password cracking tool.
|
||||
|
||||
Args:
|
||||
target: Target IP or hostname
|
||||
service: Service to attack (ssh, ftp, http-post-form, etc.)
|
||||
username: Single username to try
|
||||
username_file: Path to username file
|
||||
password: Single password to try
|
||||
password_file: Path to password file
|
||||
additional_args: Additional Hydra arguments
|
||||
|
||||
Returns:
|
||||
Attack results
|
||||
"""
|
||||
data = {
|
||||
"target": target,
|
||||
"service": service,
|
||||
"username": username,
|
||||
"username_file": username_file,
|
||||
"password": password,
|
||||
"password_file": password_file,
|
||||
"additional_args": additional_args
|
||||
}
|
||||
return kali_client.safe_post("api/tools/hydra", data)
|
||||
|
||||
@mcp.tool()
|
||||
def john_crack(
|
||||
hash_file: str,
|
||||
wordlist: str = "/usr/share/wordlists/rockyou.txt",
|
||||
format_type: str = "",
|
||||
additional_args: str = ""
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Execute John the Ripper password cracker.
|
||||
|
||||
Args:
|
||||
hash_file: Path to file containing hashes
|
||||
wordlist: Path to wordlist file
|
||||
format_type: Hash format type
|
||||
additional_args: Additional John arguments
|
||||
|
||||
Returns:
|
||||
Cracking results
|
||||
"""
|
||||
data = {
|
||||
"hash_file": hash_file,
|
||||
"wordlist": wordlist,
|
||||
"format": format_type,
|
||||
"additional_args": additional_args
|
||||
}
|
||||
return kali_client.safe_post("api/tools/john", data)
|
||||
|
||||
@mcp.tool()
|
||||
def wpscan_analyze(url: str, additional_args: str = "") -> Dict[str, Any]:
|
||||
"""
|
||||
Execute WPScan WordPress vulnerability scanner.
|
||||
|
||||
Args:
|
||||
url: The target WordPress URL
|
||||
additional_args: Additional WPScan arguments
|
||||
|
||||
Returns:
|
||||
Scan results
|
||||
"""
|
||||
data = {
|
||||
"url": url,
|
||||
"additional_args": additional_args
|
||||
}
|
||||
return kali_client.safe_post("api/tools/wpscan", data)
|
||||
|
||||
@mcp.tool()
|
||||
def enum4linux_scan(target: str, additional_args: str = "-a") -> Dict[str, Any]:
|
||||
"""
|
||||
Execute Enum4linux Windows/Samba enumeration tool.
|
||||
|
||||
Args:
|
||||
target: The target IP or hostname
|
||||
additional_args: Additional enum4linux arguments
|
||||
|
||||
Returns:
|
||||
Enumeration results
|
||||
"""
|
||||
data = {
|
||||
"target": target,
|
||||
"additional_args": additional_args
|
||||
}
|
||||
return kali_client.safe_post("api/tools/enum4linux", data)
|
||||
|
||||
@mcp.tool()
|
||||
def server_health() -> Dict[str, Any]:
|
||||
"""
|
||||
Check the health status of the Kali API server.
|
||||
|
||||
Returns:
|
||||
Server health information
|
||||
"""
|
||||
return kali_client.check_health()
|
||||
|
||||
@mcp.tool()
|
||||
def execute_command(command: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Execute an arbitrary command on the Kali server.
|
||||
|
||||
Args:
|
||||
command: The command to execute
|
||||
|
||||
Returns:
|
||||
Command execution results
|
||||
"""
|
||||
return kali_client.execute_command(command)
|
||||
|
||||
return mcp
|
||||
|
||||
def parse_args():
|
||||
"""Parse command line arguments."""
|
||||
parser = argparse.ArgumentParser(description="Run the Kali MCP Client")
|
||||
parser.add_argument("--server", type=str, default=DEFAULT_KALI_SERVER,
|
||||
help=f"Kali API server URL (default: {DEFAULT_KALI_SERVER})")
|
||||
parser.add_argument("--timeout", type=int, default=DEFAULT_REQUEST_TIMEOUT,
|
||||
help=f"Request timeout in seconds (default: {DEFAULT_REQUEST_TIMEOUT})")
|
||||
parser.add_argument("--debug", action="store_true", help="Enable debug logging")
|
||||
return parser.parse_args()
|
||||
|
||||
def main():
|
||||
"""Main entry point for the MCP server."""
|
||||
args = parse_args()
|
||||
|
||||
# Configure logging based on debug flag
|
||||
if args.debug:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.debug("Debug logging enabled")
|
||||
|
||||
# Initialize the Kali Tools client
|
||||
kali_client = KaliToolsClient(args.server, args.timeout)
|
||||
|
||||
# Check server health and log the result
|
||||
health = kali_client.check_health()
|
||||
if "error" in health:
|
||||
logger.warning(f"Unable to connect to Kali API server at {args.server}: {health['error']}")
|
||||
logger.warning("MCP server will start, but tool execution may fail")
|
||||
else:
|
||||
logger.info(f"Successfully connected to Kali API server at {args.server}")
|
||||
logger.info(f"Server health status: {health['status']}")
|
||||
if not health.get("all_essential_tools_available", False):
|
||||
logger.warning("Not all essential tools are available on the Kali server")
|
||||
missing_tools = [tool for tool, available in health.get("tools_status", {}).items() if not available]
|
||||
if missing_tools:
|
||||
logger.warning(f"Missing tools: {', '.join(missing_tools)}")
|
||||
|
||||
# Set up and run the MCP server
|
||||
mcp = setup_mcp_server(kali_client)
|
||||
logger.info("Starting Kali MCP server")
|
||||
mcp.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
x
Reference in New Issue
Block a user