GitLab RCE via Chained Oj JSON Parser Memory-Safety Flaws (Code Execution as 'git' User)

GitLab RCE via Chained Oj JSON Parser Memory-Safety Flaws (TL-2026-1691), also tracked as Oj/GitLab RCE Chain, is a high-severity software vulnerability, first published 2026-07-25. It has no confirmed attribution, affects GitLab Inc. GitLab CE/EE (self-managed), maps to 16 MITRE ATT&CK techniques (T1005, T1033, T1055), and is covered by 9 detection rules and 22 indicators of compromise.

Key facts for TL-2026-1691

Threat ID
TL-2026-1691
Also known as
Oj/GitLab RCE Chain, GitLab Jupyter Notebook Diff RCE
Severity
HIGH
Status
ACTIVE
Category
VULNERABILITY
First published
2026-07-25
Last reviewed
2026-07-25
Attribution confidence
LOW
Motivation
UNKNOWN
Target sectors
technology, software development, financial services, government administration
Target regions
Global
Detection rules
9
Indicators of compromise
22

Malware and tooling in GitLab RCE via Chained Oj JSON Parser Memory-Safety Flaws

Malware and tooling: gitlab-rce-demo

Security researcher Yuhang Wu (depthfirst / Open Defense Initiative) chained two memory-safety bugs in Ruby's Oj native JSON parser — an unchecked nesting-depth stack write and a 16-bit key-length integer narrowing that leaks a heap pointer — into a full remote code execution chain against self-managed GitLab CE/EE via crafted Jupyter notebook (.ipynb) diffs. Any authenticated user with ordinary push and commit-diff-view access can trigger the chain to run arbitrary commands as the 'git' system user, with no admin rights, CI/CD access, or victim interaction required.

How GitLab RCE via Chained Oj JSON Parser Memory-Safety Flaws works

GitLab's commit-diff renderer for Jupyter notebooks (lib/gitlab/diff/rendered/notebook/diff_file.rb, via the in-tree gems/ipynbdiff gem) feeds repository-controlled notebook JSON directly into Oj::Parser.usual.parse, a high-performance native-C JSON parser. Researcher Yuhang Wu found two independent memory-safety bugs in Oj's native extension code that, combined, produce a reliable RCE primitive reachable from ordinary GitLab push/diff-view privileges.

The first bug is an unchecked nesting-depth stack write in ext/oj/parser.h: opening a JSON array increments a 'depth' counter and selects a callback-table entry without any bounds check against Oj's fixed internal arrays. Driving depth past 1,024 produces a forward sweep of repeated 0x01 bytes (the ARRAY_FUN selector) across adjacent parser memory, eventually flipping the low byte of the parser's internal buf.head pointer at depth 2,132. A subsequent 4,001-digit number forces a realloc() onto the corrupted, attacker-influenced address, and a 446-element Ruby Array reclaims that memory via rb_ary_new_from_values(), letting the attacker overwrite two adjacent VALUE slots that encode the parser's p->start callback pointer.

The second bug is a 16-bit integer-narrowing flaw in Oj's union _key length fields (ext/oj/usual.h): submitting a JSON object key of length 0x10000+29 (65,565 bytes) truncates the stored length to 16 bits, yielding 29. The parser then reads the key back through its short inline buffer path even though the data was written through the heap/pointer path, disclosing a heap pointer at bytes 6-13 of the returned 29-byte slice. That pointer establishes an offset from a known heap allocation to the libruby/libc base, letting the attacker defeat ASLR via a timing-based self-loop probe (writing candidate_base + 0x8470e, i.e. a jmp $, into p->start and observing request timeouts vs. clean HTTP 200 responses).

With ASLR defeated, the attacker rewrites the hijacked callback pointer to a short libruby/libc gadget chain (libruby+0x269b4a -> libruby+0x22d565 -> system()) and supplies a command string (up to 39 bytes) via a trigger blob. The full chain is delivered as two crafted Jupyter notebooks pushed in a single commit ('a01' corrupts parser/heap state and raises a trailing exception; 'a02' triggers the corrupted p->start callback when its diff is viewed), executing arbitrary commands in the context of the GitLab Puma worker process running as the 'git' system user — which has read/write access to all repository storage on the instance.

The underlying Oj bugs were introduced in Oj 3.13.0 (released 2021-08-08) and shipped in GitLab from 15.2.0 onward (2022-07-07), remaining unpatched for roughly 1,753 days. Both bugs were fixed upstream in Oj 3.17.3 (2026-06-04); GitLab shipped the fix in 18.10.8, 18.11.5, and 19.0.2 (2026-06-10). GitLab's official patch-release advisory for that release lists 12 unrelated CVEs and does not credit or reference this specific Oj/RCE chain, and no CVE identifier or CVSS score has been assigned to the chain itself as of publication (2026-07-25) — distinct from the eleven individually-CVE'd Oj bugs (CVE-2026-54500, 54502, 54592, 54896-54903) fixed in the same 3.17.3 release, none of which maps 1:1 to the two primitives used here. A working PoC and Docker demo environment (targeting GitLab 18.11.3) are public at github.com/wupco/gitlab-rce-demo.

MITRE ATT&CK techniques used in TL-2026-1691

Collection

T1005 Data from Local System; T1213 Data from Information Repositories

Discovery

T1033 System Owner/User Discovery; T1083 File and Directory Discovery

Defense Evasion

T1055 Process Injection; T1211 Exploitation for Stealth

Execution

T1059 Command and Scripting Interpreter; T1203 Exploitation for Client Execution

Privilege Escalation

T1068 Exploitation for Privilege Escalation

Initial Access

T1078 Valid Accounts; T1190 Exploit Public-Facing Application

Command and Control

T1095 Non-Application Layer Protocol

Credential Access

T1552 Unsecured Credentials

Impact

T1565 Data Manipulation

Resource Development

T1587 Develop Capabilities

Reconnaissance

T1595 Active Scanning

Affected products and versions in GitLab RCE via Chained Oj JSON Parser Memory-Safety Flaws

  • GitLab Inc. — GitLab CE/EE (self-managed)
    Vulnerable versions: 15.2.0-18.10.7; 18.11.0-18.11.4; 19.0.0-19.0.1
    Fixed in: 18.10.8; 18.11.5; 19.0.2
  • ohler55 — Oj (Optimized JSON) Ruby gem
    Vulnerable versions: 3.13.0-3.17.1
    Fixed in: 3.17.3

Remediation for GitLab RCE via Chained Oj JSON Parser Memory-Safety Flaws

Patches

  • GitLab 18.10.8
  • GitLab 18.11.5
  • GitLab 19.0.2
  • Oj gem 3.17.3

Immediate actions

  • Upgrade self-managed GitLab CE/EE to 18.10.8, 18.11.5, or 19.0.2 (or later)
  • Independently verify that any vendored/custom GitLab build bundles Oj gem >= 3.17.3
  • Audit recent commits and diffs on self-managed instances for anomalous Jupyter notebook (.ipynb) files, especially pairs pushed together in a single commit

Workarounds

  • If patching is delayed, disable or restrict Jupyter notebook diff rendering for untrusted projects/users
  • Temporarily restrict repository push access to trusted, vetted users on internet-reachable self-managed instances

Longer-term hardening

  • Restrict push and commit-diff-view access to the minimum necessary for external or lower-trust users and groups on self-managed instances
  • Run GitLab Puma/Rails workers with reduced OS privileges and isolate the 'git' repository storage account from other sensitive filesystem access
  • Monitor Puma worker crash/restart patterns and anomalous request-timing (potential ASLR-probe timeouts vs. clean HTTP 200 responses)
  • Track and patch the full Oj gem CVE set (CVE-2026-54500 through CVE-2026-54903) across every Ruby application that vendors Oj, not only GitLab

Weaknesses (CWE) in GitLab RCE via Chained Oj JSON Parser Memory-Safety Flaws

CWE-787, CWE-190, CWE-125, CWE-269

Timeline of GitLab RCE via Chained Oj JSON Parser Memory-Safety Flaws

  • Oj 3.13.0 released, introducing the vulnerable parser.h nesting-depth handling and usual.h union _key length fields later chained into the GitLab RCE.
  • GitLab 15.2.0 ships, adopting the Oj native JSON parser for Jupyter notebook diff rendering (gems/ipynbdiff), introducing the vulnerable code path into GitLab.
  • Yuhang Wu (depthfirst) reports the underlying Oj memory-safety bugs to the Oj maintainer.
  • Upstream Oj maintainer merges fixes for the reported memory-safety issues.
  • Oj 3.17.3 published on RubyGems, fixing the nesting-depth stack write, the key-length narrowing bug, and nine other independently-CVE'd Oj issues.
  • The GitLab-specific RCE chain (exploiting Oj via ipynbdiff notebook-diff rendering) is reported to GitLab.
  • GitLab confirms the reported RCE chain.
  • GitLab ships security releases 19.0.2, 18.11.5, and 18.10.8, bundling Oj 3.17.3; the official patch-release advisory lists 12 unrelated CVEs and does not credit this Oj/RCE chain.
  • GitHub Security Advisories/CVEs for the broader set of Oj memory-safety bugs (including CVE-2026-54592 and CVE-2026-54901) are published, none mapping 1:1 to the two primitives used in the GitLab chain.
  • Yuhang Wu publishes the full technical writeup ('Going depthfirst') and a public PoC/Docker demo (github.com/wupco/gitlab-rce-demo) targeting GitLab 18.11.3; picked up by multiple news outlets the same day.

Sources cited for GitLab RCE via Chained Oj JSON Parser Memory-Safety Flaws

Threats related to GitLab RCE via Chained Oj JSON Parser Memory-Safety Flaws

Detection coverage for TL-2026-1691

As of 2026-07-25, Threadlinqs Intelligence publishes 9 detection rule(s) for TL-2026-1691 across Splunk SPL, Microsoft KQL and Sigma, covering 22 indicator(s) of compromise. The whole corpus is readable without an account; a free account unlocks full detection query text in Splunk SPL, Microsoft KQL and Sigma; paid tiers add raw indicator values, correlation and the MCP server. Threadlinqs MCP server · View plans.

Threadlinqs Intelligence — Real-Time Threat Detection Platform

[ 0 threats ] [ 0 det ] [ CRIT: 0 ] [ HIGH: 0 ]
// threat_feed
$ sort --newest
Showing all threats

Latest Threats