Skip to content

msf_host_info does not honor CIDR filtering #21405

@smcintyre-r7

Description

@smcintyre-r7

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
#!/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())

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.

11:19:35 fedora ~ python repro_msf_mcp_cidr.py --url http://localhost:3000/mcp                        
case                              returned   total
----------------------------------------------------
baseline (no filter)                     5       5
only_up=true                             5       5
single IP 192.168.159.10                 1       1
CIDR 192.168.159.0/24                    0       0
CIDR + only_up                           0       0

BUG REPRODUCED: CIDR 192.168.159.0/24 returned 0 hosts but baseline has 5.
exit status: 1                                                                                                                                                                                                                                                                           
11:20:25 fedora ~ cd Projects/metasploit-framework 
  : metasploit-framework: 11:20:34 fedora metasploit-framework ./msfconsole -x hosts
Metasploit tip: The use command supports fuzzy searching to try and 
select the intended module, e.g., use kerberos/get_ticket or use 
kerberos forge silver ticket
[*] Using configured payload linux/x64/meterpreter/reverse_tcp
[!] The following modules were loaded with warnings:
                                                  
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::##############                              :::::::::::::::::::
############################  ##############################  :::::::::::::::::
#########################  ######???????????????????????######  :::::::::::::::
=========================  ####??????????()????()?????????####  :::::::::::::::
=========================  ##????()??????????????    ()?????##  ::::    :::::::
------------=============  ##??????????????????  ;;;;  ?????##  ::  ;;;;  :::::
-------------------------  ##??????????()??????  ;;;;;;?????##    ;;;;;;  :::::
-------------------------  ##??????????????????  ;;;;;;         ;;;;;;;;  :::::
++++++++++++-------------  ##??????????????????  ;;;;;;;;;;;;;;;;;;;;;;;  :::::
+++++++++++++++++++++++++  ##????????????()??  ;;;;;;;;;;;;;;;;;;;;;;;;;;;  :::
+++++++++++++++++++++++++  ##??()????????????  ;;;;;;@@  ;;;;;;;;@@  ;;;;;  :::
%%%%%%%%%%%%%++++    ;;;;  ##????????????????  ;;;;;;    ;;;  ;;;    ;;;;;  :::
%%%%%%%%%%%%%%%%%;;;;;;;;  ####??????()??????  ;;[];;;;;;;;;;;;;;;;;;;;;[]  :::
$$$$$$$$$$$$$%%  ;; %%%%%  ######?????????????  ;;;;;;              ;;;;  :::::
$$$$$$$$$$$$$$$$$  $$$$$$    ###################  ;;;;;;;;;;;;;;;;;;;;  :::::::
$$$$$$$$$$$$$$$$$$$$$$$  ;;;;                                       :::::::::::
:::::::::::::$$$$$$$$$$  ;;;;  ::  ;;  ::::::::::::  ;;  ::  ;;;;  ::::::::::::
:::::::::::::::::::::::      ::::::    :::::::::::::     ::::      ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::NN::::NN::YY::::YY:::AAAAAA:::NN::::NN:::!!::::::::::::::::::::
::::::::::::::::NNNN::NN::YY::::YY::AA::::AA::NNNN::NN:::!!::::::::::::::::::::
::::::::::::::::NNNN::NN::YY::::YY::AA::::AA::NNNN::NN:::!!::::::::::::::::::::
::::::::::::::::NN::NNNN::::YYYY::::AAAAAAAA::NN::NNNN:::!!::::::::::::::::::::
::::::::::::::::NN::NNNN:::::YY:::::AA::::AA::NN::NNNN:::::::::::::::::::::::::
::::::::::::::::NN::::NN:::::YY:::::AA::::AA::NN::::NN:::!!::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::YOU HAVE DONE THE NYAN FOR 31337 SECONDS!:::::::::::::::::::::


       =[ metasploit v6.4.132-dev-bbb2452063                    ]
+ -- --=[ 2,644 exploits - 1,334 auxiliary - 2,141 payloads     ]
+ -- --=[ 431 post - 49 encoders - 14 nops - 12 evasion         ]

Metasploit Documentation: https://docs.metasploit.com/
The Metasploit Framework is a Rapid7 Open Source Project

[*] Processing /home/smcintyre/.msf4/msfconsole.rc for ERB directives.
resource (/home/smcintyre/.msf4/msfconsole.rc)> loadpath test/modules
Loaded 45 modules:
    15 auxiliary modules
    13 exploit modules
    17 post modules

Hosts
=====

address          mac  name              os_name                                                       os_flavor  os_sp  purpose  info  comments
-------          ---  ----              -------                                                       ---------  -----  -------  ----  --------
10.5.132.212          kali-raspberrypi  Linux 5.15.44-Re4son-v7+ #1 SMP Debian kali-pi (2022-07-03)                     server
10.5.132.215          kali-raspberrypi  Linux 6.6.63-v8+ #1 SMP PREEMPT Tue Dec  3 18:51:49 UTC 2024                    server
192.168.159.10                          Unknown                                                                         device
192.168.159.132       ubuntu2404        Ubuntu 24.04 (Linux 6.8.0-79-generic)                                           server
192.168.159.136                         Unknown                                                                         device

msf exploit(multi/ssh/sshexec) > 

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Status

Todo

Status

In Progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions