mirror of
https://github.com/GH05TCREW/pentestagent.git
synced 2026-03-07 14:23:20 +00:00
mcp: Metasploit parity with Hexstrike — auto-start msfrpcd (no sudo), HTTP transport, adapter updates
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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."
|
||||
|
||||
Reference in New Issue
Block a user