mcp: Metasploit parity with Hexstrike — auto-start msfrpcd (no sudo), HTTP transport, adapter updates

This commit is contained in:
giveen
2026-01-14 13:54:53 -07:00
parent 19bdff87b9
commit 23434bb5f1
4 changed files with 142 additions and 16 deletions

View File

@@ -18,7 +18,11 @@
"args": [
"third_party/MetasploitMCP/MetasploitMCP.py",
"--transport",
"stdio"
"http",
"--host",
"127.0.0.1",
"--port",
"7777"
],
"description": "Metasploit MCP (vendored) - local server",
"timeout": 300,

View File

@@ -44,21 +44,106 @@ class MetasploitAdapter:
server_script: Optional[Path] = None,
cwd: Optional[Path] = None,
env: Optional[dict] = None,
transport: str = "http",
) -> None:
self.host = host
self.port = int(port)
self.python_cmd = python_cmd
# Vendored project uses 'MetasploitMCP.py' as the main entrypoint
self.server_script = (
server_script or Path("third_party/MetasploitMCP/metasploit_mcp.py")
server_script or Path("third_party/MetasploitMCP/MetasploitMCP.py")
)
self.cwd = cwd or Path.cwd()
self.env = {**os.environ, **(env or {})}
self.transport = transport
self._process: Optional[asyncio.subprocess.Process] = None
self._reader_task: Optional[asyncio.Task] = None
self._msfrpcd_proc: Optional[asyncio.subprocess.Process] = None
def _build_command(self):
return [self.python_cmd, str(self.server_script), "--port", str(self.port)]
cmd = [self.python_cmd, str(self.server_script)]
# Prefer explicit transport when starting vendored server from adapter
if self.transport:
cmd += ["--transport", str(self.transport)]
# When running HTTP, ensure host/port are provided
if str(self.transport).lower() in ("http", "sse"):
cmd += ["--host", str(self.host), "--port", str(self.port)]
else:
# For other transports, allow default args
cmd += ["--port", str(self.port)]
return cmd
async def _start_msfrpcd_if_needed(self) -> None:
"""Start `msfrpcd` if it's not already reachable at MSF_SERVER:MSF_PORT.
This starts `msfrpcd` as a child process (no sudo) using MSF_* env
values if available. It's intentionally conservative: if the RPC
endpoint is already listening we won't try to start a new daemon.
"""
try:
msf_server = str(self.env.get("MSF_SERVER", "127.0.0.1"))
msf_port = int(self.env.get("MSF_PORT", 55553))
except Exception:
msf_server = "127.0.0.1"
msf_port = 55553
# Quick socket check to see if msfrpcd is already listening
import socket
try:
with socket.create_connection((msf_server, msf_port), timeout=1):
return
except Exception:
pass
# If msfrpcd not available on path, skip starting
if not shutil.which("msfrpcd"):
return
msf_user = str(self.env.get("MSF_USER", "msf"))
msf_password = str(self.env.get("MSF_PASSWORD", ""))
msf_ssl = str(self.env.get("MSF_SSL", "false")).lower() in ("1", "true", "yes", "y")
# Build args for msfrpcd (no sudo). Use -S (SSL optional) flag only if requested.
args = ["msfrpcd", "-U", msf_user, "-P", msf_password, "-a", msf_server, "-p", str(msf_port)]
if msf_ssl:
args.append("-S")
try:
resolved = shutil.which("msfrpcd") or "msfrpcd"
self._msfrpcd_proc = await asyncio.create_subprocess_exec(
resolved,
*args[1:],
cwd=str(self.cwd),
env=self.env,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
start_new_session=True,
)
# Start reader to capture msfrpcd logs
loop = asyncio.get_running_loop()
loop.create_task(self._capture_msfrpcd_output())
# Give msfrpcd a moment to start
await asyncio.sleep(0.5)
except Exception:
return
async def _capture_msfrpcd_output(self) -> None:
if not self._msfrpcd_proc or not self._msfrpcd_proc.stdout:
return
try:
with LOG_FILE.open("ab") as fh:
while True:
line = await self._msfrpcd_proc.stdout.readline()
if not line:
break
fh.write(b"[msfrpcd] " + line)
fh.flush()
except asyncio.CancelledError:
pass
except Exception:
pass
async def start(self, background: bool = True, timeout: int = 30) -> bool:
"""Start the vendored Metasploit MCP server.
@@ -74,6 +159,13 @@ class MetasploitAdapter:
if self._process and self._process.returncode is None:
return await self.health_check(timeout=1)
# If running in HTTP/SSE mode, ensure msfrpcd is started and reachable
if str(self.transport).lower() in ("http", "sse"):
try:
await self._start_msfrpcd_if_needed()
except Exception:
pass
cmd = self._build_command()
resolved = shutil.which(self.python_cmd) or self.python_cmd
@@ -147,6 +239,23 @@ class MetasploitAdapter:
except Exception:
pass
# Stop msfrpcd if we started it
try:
msf_proc = self._msfrpcd_proc
if msf_proc:
try:
msf_proc.terminate()
await asyncio.wait_for(msf_proc.wait(), timeout=timeout)
except asyncio.TimeoutError:
try:
msf_proc.kill()
except Exception:
pass
except Exception:
pass
finally:
self._msfrpcd_proc = None
def stop_sync(self, timeout: int = 5) -> None:
proc = self._process
if not proc:

View File

@@ -152,18 +152,24 @@ if (Test-Path -Path $msReq) {
# Optionally auto-start msfrpcd if configured in .env
if (($env:LAUNCH_METASPLOIT_MCP -eq 'true') -and ($env:MSF_PASSWORD)) {
if (Get-Command bash -ErrorAction SilentlyContinue) {
$msfUser = if ($env:MSF_USER) { $env:MSF_USER } else { 'msf' }
$msfServer = if ($env:MSF_SERVER) { $env:MSF_SERVER } else { '127.0.0.1' }
$msfPort = if ($env:MSF_PORT) { $env:MSF_PORT } else { '55553' }
Write-Host "Attempting to start msfrpcd (user=$msfUser, host=$msfServer, port=$msfPort)..."
$msfUser = if ($env:MSF_USER) { $env:MSF_USER } else { 'msf' }
$msfServer = if ($env:MSF_SERVER) { $env:MSF_SERVER } else { '127.0.0.1' }
$msfPort = if ($env:MSF_PORT) { $env:MSF_PORT } else { '55553' }
Write-Host "Starting msfrpcd (user=$msfUser, host=$msfServer, port=$msfPort) without sudo (background)..."
# Start msfrpcd without sudo; if it's already running the cmd will fail harmlessly.
if (Get-Command msfrpcd -ErrorAction SilentlyContinue) {
try {
& bash -lc "sudo msfrpcd -U $msfUser -P '$($env:MSF_PASSWORD)' -a $msfServer -p $msfPort -S"
if ($env:MSF_SSL -eq 'true' -or $env:MSF_SSL -eq '1') {
Start-Process -FilePath msfrpcd -ArgumentList "-U", $msfUser, "-P", $env:MSF_PASSWORD, "-a", $msfServer, "-p", $msfPort, "-S" -NoNewWindow -WindowStyle Hidden
} else {
Start-Process -FilePath msfrpcd -ArgumentList "-U", $msfUser, "-P", $env:MSF_PASSWORD, "-a", $msfServer, "-p", $msfPort -NoNewWindow -WindowStyle Hidden
}
Write-Host "msfrpcd start requested; check with: netstat -an | Select-String $msfPort"
} catch {
Write-Host "Warning: Failed to start msfrpcd via bash: $($_.Exception.Message)" -ForegroundColor Yellow
Write-Host "Warning: Failed to start msfrpcd: $($_.Exception.Message)" -ForegroundColor Yellow
}
} else {
Write-Host "Warning: Cannot auto-start msfrpcd: 'bash' not available. Start msfrpcd manually with msfrpcd -U <user> -P <password> -a <host> -p <port> -S" -ForegroundColor Yellow
Write-Host "msfrpcd not found; please install Metasploit Framework to enable Metasploit RPC." -ForegroundColor Yellow
}
}

View File

@@ -126,7 +126,7 @@ if [ -f "third_party/MetasploitMCP/requirements.txt" ]; then
fi
# Optionally auto-start Metasploit RPC daemon if configured
# Requires `msfrpcd` (from metasploit-framework) and sudo to run as a service.
# Start `msfrpcd` without sudo if LAUNCH_METASPLOIT_MCP=true and MSF_PASSWORD is set.
if [ "${LAUNCH_METASPLOIT_MCP,,}" = "true" ] && [ -n "${MSF_PASSWORD:-}" ]; then
if command -v msfrpcd >/dev/null 2>&1; then
MSF_USER="${MSF_USER:-msf}"
@@ -134,11 +134,18 @@ if [ "${LAUNCH_METASPLOIT_MCP,,}" = "true" ] && [ -n "${MSF_PASSWORD:-}" ]; then
MSF_PORT="${MSF_PORT:-55553}"
MSF_SSL="${MSF_SSL:-false}"
echo "Starting msfrpcd (user=${MSF_USER}, host=${MSF_SERVER}, port=${MSF_PORT})..."
if sudo -n true 2>/dev/null; then
sudo msfrpcd -U "$MSF_USER" -P "$MSF_PASSWORD" -a "$MSF_SERVER" -p "$MSF_PORT" -S || echo "Warning: msfrpcd failed to start."
# Start msfrpcd as a background process without sudo. The daemon will bind to the loopback
# interface and does not require root privileges on modern systems for ephemeral ports.
msfrpcd_cmd=$(command -v msfrpcd || true)
if [ -n "$msfrpcd_cmd" ]; then
if [ "${MSF_SSL,,}" = "true" ] || [ "${MSF_SSL}" = "1" ]; then
"$msfrpcd_cmd" -U "$MSF_USER" -P "$MSF_PASSWORD" -a "$MSF_SERVER" -p "$MSF_PORT" -S &>/dev/null &
else
"$msfrpcd_cmd" -U "$MSF_USER" -P "$MSF_PASSWORD" -a "$MSF_SERVER" -p "$MSF_PORT" &>/dev/null &
fi
echo "msfrpcd started (check with: ss -ltn | grep $MSF_PORT)"
else
echo "msfrpcd requires sudo. You may be prompted for your password to start it interactively."
sudo msfrpcd -U "$MSF_USER" -P "$MSF_PASSWORD" -a "$MSF_SERVER" -p "$MSF_PORT" -S || echo "Failed to start msfrpcd. Start it manually with: sudo msfrpcd -U $MSF_USER -P <password> -a $MSF_SERVER -p $MSF_PORT -S"
echo "msfrpcd not found; please install Metasploit Framework to enable Metasploit RPC."
fi
else
echo "msfrpcd not found; please install Metasploit Framework to enable Metasploit RPC."