Secure Bulletin Navigating the cyber sea with knowledge
Home > Articolo > Five-Year-Old Bugs in a JSON Parser Open a Code Execution Hole in Self-Managed GitLab
Five-Year-Old Bugs in a JSON Parser Open a Code Execution Hole in Self-Managed GitLab
Read Time:4 Minute, 18 Second

A newly disclosed exploit chain shows how two memory-corruption bugs that sat quietly inside a popular Ruby JSON library for nearly five years could be stitched together into a working remote code execution attack against self-managed GitLab instances. The flaw affects any project member with ordinary push and commit-diff access — no administrator rights, no CI/CD access, and no victim interaction required.

How the Bug Chain Was Found

The discovery came out of an automated vulnerability-hunting effort by researcher Yuhang Wu, working under the Open Defense Initiative. The team pointed its scanning system at Oj, a fast, native C-based JSON parser that many Ruby applications rely on, GitLab included. The scan flagged 18 candidate issues, seven of them genuine memory-safety defects, and two of those turned out to be exploitable in tandem.

The two underlying bugs were an unchecked nesting-stack write inside Oj’s parser and a 16-bit key-length calculation that could leak a heap pointer. On their own, neither looked especially dangerous: one gave an attacker a single repeated byte-write, the other exposed a fixed slice of memory. Combined with deliberate heap manipulation, though, they gave attackers enough control to hijack a callback pointer and defeat address space layout randomization (ASLR), the standard defense that randomizes where code lives in memory.

Why GitLab Was Vulnerable

The entry point turned out to be an unusually mundane feature: GitLab’s diff viewer for Jupyter Notebook (.ipynb) files. Because a notebook file is really just JSON, GitLab parses each revision with Oj before rendering a human-readable diff. Any authenticated user who could push a commit and view its diff could smuggle a malicious JSON structure into that parsing step.

In practice, the attack involved pushing two specially crafted notebook files within a single commit-diff request. The first file abused the nesting-stack bug to redirect an internal buffer pointer; later heap activity caused a Ruby array to overlap with the parser’s callback pointer, letting the attacker plant a chosen memory address there. The second file leaked a heap pointer hidden inside an oversized JSON key, which the diff viewer then rendered back into its own HTML output — handing the attacker exactly the address information needed to calculate where core libraries like libc and libruby sit in memory, defeating ASLR in the process.

Because GitLab’s Puma application server processes multiple requests through a single shared parser instance per worker, both crafted files could be handled in the same request, letting the second file trigger the corrupted callback and execute a shell command directly.

A Different Kind of GitLab RCE

Previous high-profile GitLab remote code execution issues have generally relied on server-side request forgery against internal services like Redis. This chain sidesteps that entire class of defense by attacking a memory-unsafe native extension bundled inside otherwise memory-safe Ruby code — a reminder that a language’s safety guarantees end wherever a native C dependency begins.

Successful exploitation lets an attacker run commands as the “git” system account that powers GitLab’s application workers, potentially exposing:

  • Repository source code across the instance
  • Rails application secrets and service credentials
  • Any internal services reachable from the GitLab host

That combination opens the door to data theft, source tampering, and lateral movement into other internal systems, particularly on shared or multi-tenant self-managed deployments.

Affected Versions and What to Patch

GitLab.com was already patched by the time the issue was disclosed, and GitLab Dedicated customers needed no action. Self-managed operators, however, need to upgrade directly:

  • GitLab CE/EE 15.2.0 through 18.10.7 → upgrade to 18.10.8
  • GitLab CE/EE 18.11.0 through 18.11.4 → upgrade to 18.11.5
  • GitLab CE/EE 19.0.0 through 19.0.1 → upgrade to 19.0.2
  • The underlying Oj gem, versions 3.13.0 through 3.17.1 → upgrade to 3.17.3

The timeline is a case study in how long a native-extension bug can hide: the vulnerable Oj code was merged in August 2021, and GitLab began using the affected parsing call the following year. The researcher reported the core Oj bugs in May 2026; a fix followed roughly a week later, after the bugs had lingered for close to five years. The GitLab-specific exploit chain was reported, confirmed, and patched within about a week of that disclosure.

The same research effort produced nine additional published CVEs for Oj beyond the two used in this particular chain, covering stack and heap buffer overflows, several use-after-free conditions, a negative-size memory copy, and an integer overflow tied to large file handling.

What Security Teams Should Do

Organizations running self-managed GitLab should treat this as an urgent patching priority, particularly if the instance is shared across multiple teams or exposes commit-diff functionality to lower-trust users. Beyond patching, security teams should take the opportunity to audit their broader Ruby dependency trees for native C extensions, since a single memory-unsafe gem can quietly undermine the safety guarantees of an entire application stack built on a “safe” language.

Share: Twitter  |  Facebook  |  LinkedIn
Join the discussion

This is a blog in the Fediverse: you can find this article everywhere with @blog@securebulletin.com and every comment/answer will appear here.

If you want to comment on Five-Year-Old Bugs in a JSON Parser Open a Code Execution Hole in Self-Managed GitLab, use the discussion on Forum.

>> forum community

Comments

Leave a Reply