#!/usr/bin/env python3
"""Reproduce the msf_host_info CIDR-filter bug against a running msfmcpd.
Usage:
./repro_msf_mcp_cidr.py
./repro_msf_mcp_cidr.py --url http://127.0.0.1:3000/mcp --workspace default --cidr 192.168.159.0/24
Exits 0 if the CIDR query returns hosts (bug fixed), 1 if it returns 0 (bug present).
"""
import argparse
import json
import sys
import urllib.request
PROTOCOL_VERSION = "2025-06-18"
class MCPClient:
def __init__(self, url):
self.url = url
self.session_id = None
self.next_id = 0
def _send(self, payload, expect_response=True):
body = json.dumps(payload).encode()
headers = {
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
}
if self.session_id:
headers["Mcp-Session-Id"] = self.session_id
req = urllib.request.Request(self.url, data=body, headers=headers, method="POST")
with urllib.request.urlopen(req) as resp:
sid = resp.headers.get("Mcp-Session-Id")
if sid:
self.session_id = sid
if not expect_response:
return None
ctype = resp.headers.get("Content-Type", "")
raw = resp.read().decode()
if "text/event-stream" in ctype:
for line in raw.splitlines():
if line.startswith("data:"):
return json.loads(line[5:].strip())
raise RuntimeError(f"no SSE data frame in response: {raw!r}")
return json.loads(raw)
def _rpc(self, method, params=None):
self.next_id += 1
msg = {"jsonrpc": "2.0", "id": self.next_id, "method": method}
if params is not None:
msg["params"] = params
resp = self._send(msg)
if "error" in resp:
raise RuntimeError(f"{method} failed: {resp['error']}")
return resp["result"]
def initialize(self):
self._rpc("initialize", {
"protocolVersion": PROTOCOL_VERSION,
"capabilities": {},
"clientInfo": {"name": "msf-cidr-repro", "version": "0.1.0"},
})
self._send({"jsonrpc": "2.0", "method": "notifications/initialized"}, expect_response=False)
def call_tool(self, name, arguments):
return self._rpc("tools/call", {"name": name, "arguments": arguments})
def host_count(tool_result):
for item in tool_result.get("content", []):
if item.get("type") == "text":
payload = json.loads(item["text"])
return len(payload.get("data", [])), payload.get("metadata", {}).get("total_items")
raise RuntimeError(f"no text content in tool result: {tool_result!r}")
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--url", default="http://127.0.0.1:3000/mcp")
p.add_argument("--workspace", default="default")
p.add_argument("--cidr", default="192.168.159.0/24")
p.add_argument("--single-ip", default="192.168.159.10",
help="A single IP known to exist in the workspace (for the control case)")
args = p.parse_args()
client = MCPClient(args.url)
client.initialize()
cases = [
("baseline (no filter)", {"workspace": args.workspace}),
("only_up=true", {"workspace": args.workspace, "only_up": True}),
(f"single IP {args.single_ip}", {"workspace": args.workspace, "addresses": args.single_ip}),
(f"CIDR {args.cidr}", {"workspace": args.workspace, "addresses": args.cidr}),
(f"CIDR + only_up", {"workspace": args.workspace, "addresses": args.cidr, "only_up": True}),
]
print(f"{'case':<32} {'returned':>9} {'total':>7}")
print("-" * 52)
results = []
for label, arguments in cases:
result = client.call_tool("msf_host_info", arguments)
returned, total = host_count(result)
results.append((label, returned, total, arguments))
print(f"{label:<32} {returned:>9} {total!s:>7}")
cidr_returned = results[3][1]
baseline_returned = results[0][1]
print()
if baseline_returned == 0:
print("INCONCLUSIVE: workspace is empty; cannot test CIDR filter.")
return 2
if cidr_returned == 0:
print(f"BUG REPRODUCED: CIDR {args.cidr} returned 0 hosts but baseline has {baseline_returned}.")
return 1
print(f"OK: CIDR filter returned {cidr_returned} hosts — bug appears fixed.")
return 0
if __name__ == "__main__":
sys.exit(main())
Steps to reproduce
Query hosts with a network in CIDR notation. Use this script to reproduce the bug after starting the MCP server on http://localhost:3000/mcp (use
--mcp-transport http):Test Script
In the following demo you can see that there are 5 total hosts in the database, 3 of which reside in the 192.168.159.0/24 network. None of which are returned when the CIDR query is 192.168.159.0/24; which is the root problem here.
Were you following a specific guide/tutorial or reading documentation?
No I was testing the MCP server.
Expected behavior
What should happen?
Current behavior
What happens instead?
Metasploit version
bbb2452
Additional Information