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
Impact
Resource Development
Reconnaissance
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
- GitLab Vulnerabilities Enable Code Execution
- Going depthfirst: Achieving GitLab RCE via Two Ruby Memory Corruption Vulnerabilities
- wupco/gitlab-rce-demo (PoC repository)
- GitLab Patch Release: 19.0.2, 18.11.5, 18.10.8
- Oj: Stack Buffer Overflow in Oj::Doc#each_child via Deeply Nested Input (CVE-2026-54592)
- Oj: Use-After-Free in Oj::Parser array_class/hash_class GC Marking (CVE-2026-54901)
- Oj: Use-After-Free in Oj::Parser SAJ Callback via Input Mutation (CVE-2026-54898)
- Oj: Heap Buffer Overflow in Oj.dump Exception Serialization via Large Indent (CVE-2026-54896)
- CVE-2026-54500: Oj Ruby Gem Information Disclosure Flaw
- CVE-2026-54900: Oj Ruby Gem Buffer Overflow Vulnerability
- CVE-2026-54899 - Oj: Use-After-Free in Oj::Parser Symbol Key Cache Toggle
- CVE-2026-54903: Oj Integer Overflow / Heap Corruption via >2GB JSON Strings
- Researcher Publishes GitLab RCE PoC Letting Authenticated Users Run Commands as Git
- Researcher Publishes GitLab RCE PoC Letting Authenticated Users Run Commands as Git
- depthfirst | Autonomous Security from Design to Production
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.