{"repro_id":"REPRO-2026-00314","version":6,"title":"OpenCTI authentication bypass via user impersonation","repro_type":"security","status":"published","severity":"critical","description":"Deep-researched manual dossier: OpenCTI versions 6.6.0 through 6.9.12 are affected by an authentication bypass that allows an unauthenticated attacker to query the API as any existing user, including the default admin account. The issue impacts the GraphQL API and results in unauthorized access to threat intelligence data and administrative actions. Fixed in OpenCTI 6.9.13. Canonical repository: https://github.com/OpenCTI-Platform/opencti.","root_cause":"# CVE-2026-27960 — OpenCTI Unauthenticated Authentication Bypass via User Impersonation\n\n## Summary\n\nOpenCTI versions 6.6.0 through 6.9.12 contain an improper-authentication flaw (CWE-287) in the\nGraphQL API bearer-token resolution path. The function `authenticateUserByTokenOrUserId()` in\n`opencti-platform/opencti-graphql/src/domain/user.js` resolves the HTTP\n`Authorization: Bearer <value>` credential against the platform user cache map, which is keyed not\nonly by each user's secret `api_token`, but also by every non-secret identifier of the user:\n`internal_id`, `standard_id`, and STIX ids (`buildStoreEntityMap()` in\n`opencti-platform/opencti-graphql/src/database/cache.ts` explicitly pushes `entity.api_token`\ninto the same id list as `internal_id`/`standard_id`). As a result, an unauthenticated remote\nattacker can present **any known or guessable user identifier** — in particular the hard-coded\ndefault-admin `internal_id` `OPENCTI_ADMIN_UUID = 88ec0c6a-13ce-5e39-b486-354fe4a7084f`\n(`opencti-platform/opencti-graphql/src/schema/general.js`) — as the bearer token and is\nauthenticated as that user without ever proving knowledge of the secret API token, password, or\nany credential.\n\n## Impact\n\n- Package/component affected: `opencti/platform` (OpenCTI GraphQL API, `opencti-graphql`), all\n  deployment modes that expose the HTTP/GraphQL endpoint.\n- Affected versions: >= 6.6.0, < 6.9.13 (fixed in 6.9.13).\n- Risk level: critical (CVSS 9.8 per public advisories). An unauthenticated network attacker can\n  query and mutate the GraphQL API as any existing user, including the default admin: full read\n  access to threat-intelligence data and full administrative control (user management, settings,\n  data destruction).\n\n## Impact Parity\n\n- Disclosed/claimed maximum impact: unauthenticated remote authentication bypass / authorization\n  bypass allowing API access as any existing user, including the default admin (impact class\n  `authz_bypass`).\n- Reproduced impact from this run: unauthenticated GraphQL request carrying only the public,\n  hard-coded default-admin `internal_id` as bearer token was accepted by OpenCTI 6.9.12 and\n  executed both `me` (returning the admin identity) and the admin-only `users` listing query.\n  The identical request was rejected on the fixed 6.9.13 build, while the real secret\n  `api_token` remained accepted on both builds.\n- Parity: `full` (unauthenticated admin impersonation through the production GraphQL boundary\n  demonstrated end-to-end).\n\n## Root Cause\n\n`authenticateUserFromRequest()` extracts the bearer value and calls\n`authenticateUserByTokenOrUserId(context, req, tokenUUID)`. That function only tests\n`platformUsers.has(tokenOrId)` on the user cache map. `getEntitiesMapFromCache()` builds this map\nvia `buildStoreEntityMap()`, which indexes each user under `internal_id`, `standard_id`,\n`x_opencti_stix_ids` **and** `api_token`. The code therefore conflates *public identifiers* with\n*secret credentials*: possession of a user's internal UUID (for the default admin a constant\ncompiled into the shipped source, `OPENCTI_ADMIN_UUID`) is treated as proof of identity.\n\nFix (6.9.13, diff of `src/domain/user.js` between tags 6.9.12 and 6.9.13): the function was split\ninto `authenticateUserByToken()` — which additionally verifies\n`crypto.timingSafeEqual(Buffer.from(user.api_token), Buffer.from(token))` — and\n`authenticateUserByUserId()`, which is only reachable after a successfully authenticated\nheader-provider login (`HEADERS_AUTHENTICATORS`), restoring the invariant that a bearer value must\nbe the secret token.\n\n- Vendor advisory: https://github.com/OpenCTI-Platform/opencti/security/advisories/GHSA-6vvv-vmfr-xhrx\n- Fix: `opencti-platform/opencti-graphql/src/domain/user.js` changes between tags 6.9.12 and 6.9.13.\n\n## Reproduction Steps\n\n1. Run `bundle/repro/reproduction_steps.sh` (self-contained; only needs Docker and network access\n   to pull images).\n2. The script:\n   - starts the real dependency stack (Elasticsearch 8.19.16, Redis 7, RabbitMQ 3.13, MinIO) on an\n     isolated Docker network;\n   - starts `opencti/platform:6.9.12` with a configured admin email/password/token, waits for the\n     platform health endpoint (migrations included);\n   - **attack**: POSTs `{\"query\":\"{ me { id name user_email } }\"}` to `/graphql` with\n     `Authorization: Bearer 88ec0c6a-13ce-5e39-b486-354fe4a7084f` (the hard-coded default-admin\n     `internal_id`, no credentials);\n   - **attack 2**: POSTs the admin-only `users(first: 5)` listing with the same header;\n   - **controls**: no `Authorization` header, a random unknown UUID bearer, and the real secret\n     `api_token` bearer;\n   - tears the stack down and repeats attack + valid-token control against\n     `opencti/platform:6.9.13` (fixed);\n   - writes `bundle/repro/runtime_manifest.json` and exits 0 only if the vulnerable build\n     impersonates the admin **and** the fixed build rejects the same request.\n3. Expected evidence: on 6.9.12 the attack response contains\n   `\"user_email\":\"admin@opencti.io\"` for both `me` and `users` queries; controls without a valid\n   secret token return no identity; on 6.9.13 the attack returns no identity while the real token\n   still authenticates.\n\n## Evidence\n\n- Driver log: `bundle/logs/reproduction_steps.log`\n- Attack responses: `bundle/artifacts/opencti/vuln_attack_me_response.json`,\n  `bundle/artifacts/opencti/vuln_attack_users_response.json`\n- Controls: `bundle/artifacts/opencti/vuln_control_noauth_response.json`,\n  `bundle/artifacts/opencti/vuln_control_random_uuid_response.json`,\n  `bundle/artifacts/opencti/vuln_control_valid_token_response.json`\n- Fixed-version negative control: `bundle/artifacts/opencti/fixed_attack_me_response.json`,\n  `bundle/artifacts/opencti/fixed_control_valid_token_response.json`\n- Platform logs: `bundle/artifacts/opencti/platform_vuln.log`,\n  `bundle/artifacts/opencti/platform_fixed.log`\n- Runtime manifest: `bundle/repro/runtime_manifest.json`\n- Environment: Docker 29, `opencti/platform:6.9.12` vs `opencti/platform:6.9.13`,\n  Elasticsearch 8.19.16, Redis 7-alpine, RabbitMQ 3.13-management-alpine, MinIO latest.\n\nKey excerpts are recorded in `bundle/logs/reproduction_steps.log` (vulnerable build returns the\nadmin identity for the hard-coded UUID bearer; fixed build rejects it).\n\n## Recommendations / Next Steps\n\n- Upgrade to OpenCTI >= 6.9.13.\n- Interim (partial) workaround per vendor: set `APP__ADMIN__EXTERNALLY_MANAGED` to disable the\n  default admin account — note this does not close the bypass for other users, since any user\n  `internal_id`/`standard_id` remains a valid bearer on vulnerable builds.\n- Treat all user `internal_id`/`standard_id` values as public; rotate admin API tokens if a\n  vulnerable version was exposed.\n- Regression test: assert that `Authorization: Bearer <user internal_id>` is rejected by the\n  GraphQL endpoint while the user's `api_token` is accepted.\n\n## Additional Notes\n\n- The script is idempotent: it recreates the Docker network/containers on each run and cleans them\n  up on exit (trap). It was executed twice consecutively with identical pass results.\n- No sanitizer, mock, or reimplementation is used: the proof exercises the shipped\n  `opencti/platform` container through its real HTTP/GraphQL listener.\n- The exploit requires no information beyond what is compiled into the public source tree\n  (`OPENCTI_ADMIN_UUID`), so default deployments are exploitable with zero reconnaissance;\n  impersonating *other* users additionally requires their `internal_id`/`standard_id`, which are\n  routinely exposed in API responses to authenticated parties.\n","cve_id":"CVE-2026-27960","cwe_id":"CWE-287 Improper Authentication","source_url":"https://github.com/OpenCTI-Platform/opencti/security/advisories/GHSA-6vvv-vmfr-xhrx","package":{"name":"opencti","ecosystem":"docker","affected_versions":">= 6.6.0, < 6.9.13 (i.e. 6.6.0 through 6.9.12)","fixed_version":"6.9.13"},"reproduced_at":"2026-07-30T07:51:01.428321+00:00","duration_secs":2674.0,"tool_calls":173,"handoffs":2,"total_cost_usd":3.154107,"agent_costs":{"claim_matcher":0.013113,"judge":0.343116,"learning_policy":0.012185,"repro":1.459298,"support":0.067323,"vuln_variant":1.259072},"cost_breakdown":{"claim_matcher":{"gpt-5.4-mini-2026-03-17":0.013113},"judge":{"gpt-5.6-sol":0.343116},"learning_policy":{"gpt-5.4-mini-2026-03-17":0.012185},"repro":{"accounts/fireworks/models/kimi-k3":1.459298},"support":{"accounts/fireworks/models/kimi-k3":0.067323},"vuln_variant":{"accounts/fireworks/models/kimi-k3":1.259072}},"quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"evidence":{"workflow":{"profile":"known_vulnerability","schema_version":2,"stages":["support","claim_contract","repro","judge","vuln_variant"]}},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-30T07:51:02.017540+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":12327,"category":"reproduction_script"},{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":7619,"category":"analysis"},{"path":"bundle/artifacts/opencti/rabbitmq.conf","filename":"rabbitmq.conf","size":56,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":1162,"category":"other"},{"path":"bundle/logs/reproduction_steps.log","filename":"reproduction_steps.log","size":6144,"category":"log"},{"path":"bundle/artifacts/opencti/vuln_attack_me_response.json","filename":"vuln_attack_me_response.json","size":109,"category":"other"},{"path":"bundle/artifacts/opencti/vuln_attack_users_response.json","filename":"vuln_attack_users_response.json","size":118,"category":"other"},{"path":"bundle/artifacts/opencti/fixed_attack_me_response.json","filename":"fixed_attack_me_response.json","size":231,"category":"other"},{"path":"bundle/artifacts/opencti/vuln_control_noauth_response.json","filename":"vuln_control_noauth_response.json","size":231,"category":"other"},{"path":"bundle/artifacts/opencti/vuln_control_random_uuid_response.json","filename":"vuln_control_random_uuid_response.json","size":231,"category":"other"},{"path":"bundle/artifacts/opencti/vuln_control_valid_token_response.json","filename":"vuln_control_valid_token_response.json","size":109,"category":"other"},{"path":"bundle/artifacts/opencti/fixed_control_valid_token_response.json","filename":"fixed_control_valid_token_response.json","size":109,"category":"other"},{"path":"bundle/artifacts/opencti/platform_vuln.log","filename":"platform_vuln.log","size":8208,"category":"log"},{"path":"bundle/artifacts/opencti/platform_fixed.log","filename":"platform_fixed.log","size":8208,"category":"log"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":900,"category":"other"}]}