Skip to content

CVE-2026-27960: Verified Reproduction

CVE-2026-27960: OpenCTI authentication bypass via user impersonation

CVE-2026-27960 is verified against opencti · docker. Affected versions: >= 6.6.0, < 6.9.13 (i.e. 6.6.0 through 6.9.12). Fixed in 6.9.13. Vulnerability class: Auth Bypass. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00314.

REPRO-2026-00314 opencti · docker Auth Bypass Jul 30, 2026 CVE entry ↗ .txt
Severity
CRITICAL
Confidence
HIGH
Reproduced in
44m 34s
Tool calls
173
Spend
$3.15
01 · Overview

What Is CVE-2026-27960?

CVE-2026-27960 is a critical-severity Auth Bypass vulnerability affecting opencti >= 6.6.0, < 6.9.13 (i.e. 6.6.0 through 6.9.12). Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00314).

02 · Severity & CVSS

CVE-2026-27960 Severity

CVE-2026-27960 is rated critical severity.

CRITICAL threat level
Weakness CWE-287 Improper Authentication — Improper Authentication

Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.

03 · Affected Versions

Affected opencti Versions

opencti · docker versions >= 6.6.0, < 6.9.13 (i.e. 6.6.0 through 6.9.12) are affected.

How to Reproduce CVE-2026-27960

$ pruva-verify REPRO-2026-00314
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00314/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-27960

Authorization bypass — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

Authorization: Bearer 88ec0c6a-13ce-5e39-b486-354fe4a7084f (hardcoded default-admin internal_id, no credentials) on unauthenticated POST /graphql

Attack chain
  1. HTTP POST /graphql
  2. authenticateUserFromRequest
  3. authenticateUserByTokenOrUserId
  4. user cache map keyed by internal_id/standard_id/api_token (opencti-graphql src/domain/user.js, src/database/cache.ts)
How the agent worked 359 events · 173 tool calls · 45 min
45 minDuration
173Tool calls
65Reasoning steps
359Events
7Dead-ends
Agent activity over 45 min
Policy
1
Support
12
Repro
170
Judge
29
Variant
142
Verify
1
0:0044:34

Root Cause and Exploit Chain for CVE-2026-27960

Versions: >= 6.6.0, < 6.9.13 (fixed in 6.9.13).

OpenCTI versions 6.6.0 through 6.9.12 contain an improper-authentication flaw (CWE-287) in the GraphQL API bearer-token resolution path. The function authenticateUserByTokenOrUserId() in opencti-platform/opencti-graphql/src/domain/user.js resolves the HTTP Authorization: Bearer <value> credential against the platform user cache map, which is keyed not only by each user's secret api_token, but also by every non-secret identifier of the user: internal_id, standard_id, and STIX ids (buildStoreEntityMap() in opencti-platform/opencti-graphql/src/database/cache.ts explicitly pushes entity.api_token into the same id list as internal_id/standard_id). As a result, an unauthenticated remote attacker can present any known or guessable user identifier — in particular the hard-coded default-admin internal_id OPENCTI_ADMIN_UUID = 88ec0c6a-13ce-5e39-b486-354fe4a7084f (opencti-platform/opencti-graphql/src/schema/general.js) — as the bearer token and is authenticated as that user without ever proving knowledge of the secret API token, password, or any credential.

  • Package/component affected: opencti/platform (OpenCTI GraphQL API, opencti-graphql), all deployment modes that expose the HTTP/GraphQL endpoint.
  • Affected versions: >= 6.6.0, < 6.9.13 (fixed in 6.9.13).
  • Risk level: critical (CVSS 9.8 per public advisories). An unauthenticated network attacker can query and mutate the GraphQL API as any existing user, including the default admin: full read access to threat-intelligence data and full administrative control (user management, settings, data destruction).

Impact Parity

  • Disclosed/claimed maximum impact: unauthenticated remote authentication bypass / authorization bypass allowing API access as any existing user, including the default admin (impact class authz_bypass).
  • Reproduced impact from this run: unauthenticated GraphQL request carrying only the public, hard-coded default-admin internal_id as bearer token was accepted by OpenCTI 6.9.12 and executed both me (returning the admin identity) and the admin-only users listing query. The identical request was rejected on the fixed 6.9.13 build, while the real secret api_token remained accepted on both builds.
  • Parity: full (unauthenticated admin impersonation through the production GraphQL boundary demonstrated end-to-end).

Root Cause

authenticateUserFromRequest() extracts the bearer value and calls authenticateUserByTokenOrUserId(context, req, tokenUUID). That function only tests platformUsers.has(tokenOrId) on the user cache map. getEntitiesMapFromCache() builds this map via buildStoreEntityMap(), which indexes each user under internal_id, standard_id, x_opencti_stix_ids and api_token. The code therefore conflates public identifiers with secret credentials: possession of a user's internal UUID (for the default admin a constant compiled into the shipped source, OPENCTI_ADMIN_UUID) is treated as proof of identity.

Fix (6.9.13, diff of src/domain/user.js between tags 6.9.12 and 6.9.13): the function was split into authenticateUserByToken() — which additionally verifies crypto.timingSafeEqual(Buffer.from(user.api_token), Buffer.from(token)) — and authenticateUserByUserId(), which is only reachable after a successfully authenticated header-provider login (HEADERS_AUTHENTICATORS), restoring the invariant that a bearer value must be the secret token.

Reproduction Steps

  1. Run bundle/repro/reproduction_steps.sh (self-contained; only needs Docker and network access to pull images).
  2. The script:
    • starts the real dependency stack (Elasticsearch 8.19.16, Redis 7, RabbitMQ 3.13, MinIO) on an isolated Docker network;
    • starts opencti/platform:6.9.12 with a configured admin email/password/token, waits for the platform health endpoint (migrations included);
    • attack: POSTs {"query":"{ me { id name user_email } }"} to /graphql with Authorization: Bearer 88ec0c6a-13ce-5e39-b486-354fe4a7084f (the hard-coded default-admin internal_id, no credentials);
    • attack 2: POSTs the admin-only users(first: 5) listing with the same header;
    • controls: no Authorization header, a random unknown UUID bearer, and the real secret api_token bearer;
    • tears the stack down and repeats attack + valid-token control against opencti/platform:6.9.13 (fixed);
    • writes bundle/repro/runtime_manifest.json and exits 0 only if the vulnerable build impersonates the admin and the fixed build rejects the same request.
  3. Expected evidence: on 6.9.12 the attack response contains "user_email":"admin@opencti.io" for both me and users queries; controls without a valid secret token return no identity; on 6.9.13 the attack returns no identity while the real token still authenticates.

Evidence

  • Driver log: bundle/logs/reproduction_steps.log
  • Attack responses: bundle/artifacts/opencti/vuln_attack_me_response.json, bundle/artifacts/opencti/vuln_attack_users_response.json
  • Controls: bundle/artifacts/opencti/vuln_control_noauth_response.json, bundle/artifacts/opencti/vuln_control_random_uuid_response.json, bundle/artifacts/opencti/vuln_control_valid_token_response.json
  • Fixed-version negative control: bundle/artifacts/opencti/fixed_attack_me_response.json, bundle/artifacts/opencti/fixed_control_valid_token_response.json
  • Platform logs: bundle/artifacts/opencti/platform_vuln.log, bundle/artifacts/opencti/platform_fixed.log
  • Runtime manifest: bundle/repro/runtime_manifest.json
  • Environment: Docker 29, opencti/platform:6.9.12 vs opencti/platform:6.9.13, Elasticsearch 8.19.16, Redis 7-alpine, RabbitMQ 3.13-management-alpine, MinIO latest.

Key excerpts are recorded in bundle/logs/reproduction_steps.log (vulnerable build returns the admin identity for the hard-coded UUID bearer; fixed build rejects it).

Recommendations / Next Steps

  • Upgrade to OpenCTI >= 6.9.13.
  • Interim (partial) workaround per vendor: set APP__ADMIN__EXTERNALLY_MANAGED to disable the default admin account — note this does not close the bypass for other users, since any user internal_id/standard_id remains a valid bearer on vulnerable builds.
  • Treat all user internal_id/standard_id values as public; rotate admin API tokens if a vulnerable version was exposed.
  • Regression test: assert that Authorization: Bearer <user internal_id> is rejected by the GraphQL endpoint while the user's api_token is accepted.

Additional Notes

  • The script is idempotent: it recreates the Docker network/containers on each run and cleans them up on exit (trap). It was executed twice consecutively with identical pass results.
  • No sanitizer, mock, or reimplementation is used: the proof exercises the shipped opencti/platform container through its real HTTP/GraphQL listener.
  • The exploit requires no information beyond what is compiled into the public source tree (OPENCTI_ADMIN_UUID), so default deployments are exploitable with zero reconnaissance; impersonating other users additionally requires their internal_id/standard_id, which are routinely exposed in API responses to authenticated parties.

CVE-2026-27960 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:002:18
0:00
session startedaccounts/fireworks/models/kimi-k3 · CVE-2026-27960 · REPRO-20
0:07
0:09
web search
0:18
0:19
0:38
0:38
extract_facts
no facts extracted
0:46
0:46
0:46
supportclaim_contract
1:29
1:29
1:29
1:36
1:36
1:36
1:36
1:44
1:46
web search
1:53
1:54
$curl -s "https://api.github.com/repos/OpenCTI-Platform/opencti/compare/6.9.12...6.9.13" | jq -r '.files[] | .filename' | head -100
1.2s
{"cwd":"/workspace","duration_ms":772,"exit_code":0,"stderr":"","stdout":".github/workflows/ci-test-api.yml\n.github/workflows/ci-test-client-python.yml\n.github/workflows/ci-test-frontend.yml\nclient-python/pycti/__init__.py\nopencti-platform/opencti-front/package.json\nopencti-platform/opencti-front/src/components/dataGrid/DataTableWithoutFragment.tsx\nopencti-platform/opencti-front/src/components/dataGrid/components/DataTableComponent.tsx\nopencti-platform/opencti-front/src/components/dataGrid/dataTableUtils.tsx\nopencti-platform/opencti-front/src/private/components/drafts/DraftEntities.tsx\nopencti-platform/opencti-front/src/private/components/drafts/DraftRelationships.tsx\nopencti-platform/opencti-front/src/private/components/drafts/DraftSightings.tsx\nopencti-platform/opencti-front/src/private/components/settings/VocabularyCategories.tsx\nopencti-platform/opencti-graphql/package.json\nopencti-platform/opencti-graphql/src/database/generate-message.js\nopencti-platform/opencti-graphql/src/database/middleware.js\nopencti-platform/opencti-graphql/src/domain/user.js\nopencti-platform/opencti-graphql/src/manager/ingestionManager.ts\nopencti-platform/opencti-graphql/src/modules/inge… [truncated]
1:56
1:59
2:00
$curl -s "https://api.github.com/repos/OpenCTI-Platform/opencti/compare/6.9.12...6.9.13" | jq -r '.files[] | select(.filename=="opencti-platform/opencti-graphql/src/domain/user.js") | .patch'
0.6s
{"cwd":"/workspace","duration_ms":163,"exit_code":0,"stderr":"","stdout":"@@ -1,4 +1,5 @@\n import bcrypt from 'bcryptjs';\n+import crypto from 'crypto';\n import * as R from 'ramda';\n import { uniq } from 'ramda';\n import { v4 as uuid } from 'uuid';\n@@ -1636,22 +1637,39 @@ export const resolveUserById = async (context, id) => {\n   return buildCompleteUser(context, client);\n };\n \n-export const authenticateUserByTokenOrUserId = async (context, req, tokenOrId) => {\n+export const authenticateUserByToken = async (context, req, token) => {\n   const platformUsers = await getEntitiesMapFromCache(context, SYSTEM_USER, ENTITY_TYPE_USER);\n-  if (platformUsers.has(tokenOrId)) {\n-    let authenticatedUser = platformUsers.get(tokenOrId);\n-    const settings = await getEntityFromCache(context, SYSTEM_USER, ENTITY_TYPE_SETTINGS);\n-    const applicantId = req.headers['opencti-applicant-id'];\n-    if (applicantId && isBypassUser(authenticatedUser)) {\n-      authenticatedUser = platformUsers.get(applicantId) || INTERNAL_USERS[applicantId];\n-      if (!authenticatedUser) {\n-        throw FunctionalError(`Cant impersonate applicant ${applicantId}`);\n-      }\n+  if (platformUsers.has… [truncated]
2:13
2:14
$curl -s "https://raw.githubusercontent.com/OpenCTI-Platform/opencti/6.9.12/opencti-platform/opencti-graphql/src/database/cache.ts" | grep -n -A 30 "getEntitiesMapFromCache\|buildCacheEntity\|const maps" | head -80
0.8s
{"cwd":"/workspace","duration_ms":310,"exit_code":0,"stderr":"","stdout":"119:// use either getEntitiesMapFromCache or getEntitiesListFromCache in export\n120-const getEntitiesFromCache = async <T extends BasicStoreIdentifier | StixObject>(\n121-  context: AuthContext, user: AuthUser, type: string,\n122-): Promise<Array<T> | Map<string, T>> => {\n123-  const getEntitiesFromCacheFn = async (): Promise<Array<T> | Map<string, T>> => {\n124-    const fromCache = cache[type];\n125-    if (!fromCache) {\n126-      throw UnsupportedError('Cache configuration type not supported', { type });\n127-    }\n128-    if (!fromCache.values) {\n129-      // If cache already in progress build, just wait for completion\n130-      if (fromCache.inProgress) {\n131-        while (fromCache.inProgress) {\n132-          await wait(100);\n133-        }\n134-        return fromCache.values ?? (type === ENTITY_TYPE_RESOLVED_FILTERS ? new Map() : []);\n135-      }\n136-      // If not in progress, re fetch the data\n137-      fromCache.inProgress = true;\n138-      try {\n139-        fromCache.values = await fromCache.fn();\n140-      } finally {\n141-        fromCache.inProgress = false;\n142-      }\n143-  … [truncated]
2:18
$curl -s "https://raw.githubusercontent.com/OpenCTI-Platform/opencti/6.9.12/opencti-platform/opencti-graphql/src/database/cache.ts" | grep -n -B2 -A 20 "buildStoreEntityMap"
0.6s
{"cwd":"/workspace","duration_ms":252,"exit_code":0,"stderr":"","stdout":"33-const cache: any = {};\n34-\n35:const buildStoreEntityMap = <T extends BasicStoreIdentifier>(entities: Array<T>) => {\n36-  const entityById = new Map();\n37-  for (let i = 0; i < entities.length; i += 1) {\n38-    const entity = entities[i];\n39-    const ids = [entity.internal_id, ...(entity.x_opencti_stix_ids ?? [])];\n40-    // Use the user api_token as an id\n41-    if ('api_token' in entity && entity.api_token) {\n42-      ids.push(entity.api_token as string);\n43-    }\n44-    if (entity.standard_id) {\n45-      ids.push(entity.standard_id);\n46-    }\n47-    if (entity.entity_type === ENTITY_TYPE_PUBLIC_DASHBOARD) {\n48-      ids.push((entity as unknown as BasicStoreEntityPublicDashboard).uri_key);\n49-    }\n50-    for (let index = 0; index < ids.length; index += 1) {\n51-      const id = ids[index];\n52-      entityById.set(id, entity);\n53-    }\n54-  }\n55-  return entityById;\n--\n174-  }\n175-  const data = await getEntitiesFromCache(context, user, type) as BasicStoreIdentifier[];\n176:  return buildStoreEntityMap(data); // map of <id, instance> for all the instance ids (internal_id, standard… [truncated]
08 · How to Fix

How to Fix CVE-2026-27960

Upgrade opencti · docker to 6.9.13 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-27960 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-27960

Is CVE-2026-27960 exploitable?

Yes. Pruva independently reproduced CVE-2026-27960 in opencti and verified the exploit fires end-to-end in a sandboxed environment. A runnable proof-of-concept script and the full agent transcript are on this page (reproduction REPRO-2026-00314).

How severe is CVE-2026-27960?

CVE-2026-27960 is rated critical severity.

What type of vulnerability is CVE-2026-27960?

CVE-2026-27960 is classified as CWE-287 Improper Authentication (Improper Authentication), a Auth Bypass vulnerability.

Which versions of opencti are affected by CVE-2026-27960?

opencti >= 6.6.0, < 6.9.13 (i.e. 6.6.0 through 6.9.12) is affected by CVE-2026-27960.

Is there a fix for CVE-2026-27960?

Yes. CVE-2026-27960 is fixed in opencti 6.9.13. Upgrading to the fixed version remediates the issue.

How can I reproduce CVE-2026-27960?

Pruva provides a verified reproduction script on this page. Download it and run it inside an isolated environment such as a container or virtual machine — never against production. The reproduction was confirmed end-to-end by Pruva's automated agents.

Is the CVE-2026-27960 reproduction verified?

Yes. Pruva reproduced CVE-2026-27960 with high confidence in a sandboxed environment, capturing the full agent transcript and artifacts as evidence.
11 · References

References for CVE-2026-27960

Authoritative sources for CVE-2026-27960 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.