CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-5QW5-WF2Q-F538
8.80.10%

The Questionable Query: AR-JDBC SQL Injection Deep Dive

Alon Barad
Alon Barad
Software Engineer

Feb 17, 2026·5 min read·6 visits

PoC Available

Executive Summary (TL;DR)

The JRuby database adapter, `activerecord-jdbc-adapter`, failed to safely handle SQL parameter substitution. Attackers can inject a `?` character into a field, which the adapter mistakenly treats as a new placeholder for the *next* value. This allows the second value to be injected inside the first value's quoted string, enabling SQL injection. Patch to version 1.2.8 immediately.

A critical SQL injection vulnerability in the ActiveRecord-JDBC-Adapter allows attackers to manipulate database queries via malformed bind parameters. By injecting specific characters into placeholder values, attackers can trigger a 'double substitution' event, effectively breaking out of quoted strings and executing arbitrary SQL commands on JRuby-based applications.

The Hook: JRuby's Dirty Little Secret

In the world of Ruby on Rails, we take certain things for granted. We assume ActiveRecord will protect us. We assume that when we use Model.where(name: params[:name]), the framework acts as an impenetrable shield against SQL injection. For the most part, we're right. But if you are running Rails on the Java Virtual Machine (JRuby), you rely on a bridge called activerecord-jdbc-adapter (AR-JDBC).

This library is the translator between Ruby's elegant active record pattern and Java's rigid JDBC drivers. It's a critical piece of infrastructure for enterprise Ruby apps. But for years, a logic bomb was ticking inside its string handling code—a flaw so fundamental it turns the concept of 'parameterized queries' on its head.

GHSA-5qw5-wf2q-f538 isn't just a failure of input sanitization; it's a failure of logic. It is a vulnerability that allows an attacker to confuse the database driver about what is code and what is data, simply by asking a question (mark).

The Flaw: The Ouroboros of Substitution

To understand this bug, you have to understand how poor man's parameterized queries work. True prepared statements send the SQL template and the data separately to the database engine. But many adapters, for legacy or compatibility reasons, emulate this by taking the template (SELECT * FROM users WHERE name = ?) and the data (['Alice']), and mashing them together into a string before sending it over the wire.

The flaw in AR-JDBC lay in how it performed this mash-up. The adapter used a loop—specifically Ruby's gsub with a block—to find every ? and replace it with a quoted value. In theory, this is fine. In practice, the implementation suffered from a 'double substitution' or 'nested substitution' issue depending on the specific JRuby/Ruby version behaviors engaged.

When the adapter replaced the first ? with a user-supplied string, it didn't verify if that new string contained a ? of its own. If an attacker supplied Hacker? as the first parameter, the adapter inserted it. Then, due to the iterative nature of how the parameters were processed or subsequent passes in the driver chain, the engine would spot the ? inside the attacker's input and mistake it for the placeholder meant for the second parameter. It's the software equivalent of a snake eating its own tail, effectively allowing data to overwrite the structure of the query.

The Code: When `gsub` Goes Rogue

Let's look at the smoking gun in lib/arjdbc/jdbc/adapter.rb. The developers needed a way to swap binds into the SQL string. They chose this implementation:

# Vulnerable method in lib/arjdbc/jdbc/adapter.rb
def to_sql(sql, binds = [])
  sql = sql.send(:to_sql) if sql.respond_to?(:to_sql)
  return sql if binds.blank?
  
  copy = binds.dup
  # The critical failure:
  sql.gsub('?') { quote(*copy.shift.reverse) }
end

At first glance, this looks like standard Ruby. gsub iterates over matches. copy.shift pulls the next value. quote makes it safe. Done, right? Wrong.

The issue is subtle. If copy.shift returns a string that contains a ?, and the to_sql method logic (or the surrounding infrastructure in older versions) allows for re-scanning or improper cursor management, the ? in the payload consumes the next item in copy.

This means the boundaries between separate bind parameters dissolve. Parameter A can consume Parameter B, injecting Parameter B's raw value into the middle of Parameter A's quoted string.

The Exploit: Inception-Level Injection

Let's weaponize this. Imagine a simple profile update query taking two parameters: a username and a bio.

Target Query:

UPDATE users SET username = ?, bio = ? WHERE id = 123;

The Setup: We need two payloads.

  1. Payload A (The Trap): A string containing a ?. Let's use bad_user?.
  2. Payload B (The Payload): The malicious SQL we want to execute, carefully crafted to break out of quotes. Let's use ', admin=1 --.

The Execution:

  1. The adapter sees the first ? in the SQL template.
  2. It replaces it with the quoted version of Payload A: 'bad_user?'.
  3. The adapter (erroneously) continues scanning or re-scans the string. It finds the ? inside 'bad_user?'.
  4. It thinks, "Ah, another placeholder!" and grabs the next bind value (Payload B).
  5. It shoves Payload B inside the quotes of Payload A.

The Resulting SQL:

UPDATE users SET username = 'bad_user', admin=1 --'', bio = ...

See what happened? The injected ? acted as a portal. The second payload landed inside the string literal of the first, closing the quote (') early and injecting admin=1. The rest of the query (bio = ...) is commented out by the --. We just escalated privileges.

The Fix: Stop Using Regex for Parsing

The fix for this is simple in principle but requires discipline: Do not use simple string replacement for SQL generation. The maintainers patched this in version 1.2.8.

The remediation strategy involves upgrading the gem. If you are stuck on a legacy version of JRuby or Rails, you are in dangerous waters. The patched versions likely move away from naive gsub usage or implement a scanner that is strictly position-aware, ensuring that replaced text is never eligible for future replacement passes.

> [!NOTE] > Developer Lesson: If you find yourself writing a regex to parse or modify SQL, stop. You are almost certainly creating a vulnerability. Use the tokenizer or parser provided by your database driver, or ensure your replacement logic consumes the template linearly without backtracking.

Official Patches

GitHubComparison view of the fix

Technical Appendix

CVSS Score
8.8/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:P
EPSS Probability
0.10%
Top 100% most exploited

Affected Systems

JRuby applications using ActiveRecordactiverecord-jdbc-adapter < 1.2.8

Affected Versions Detail

Product
Affected Versions
Fixed Version
activerecord-jdbc-adapter
JRuby
< 1.2.81.2.8
AttributeDetail
Attack VectorNetwork (Remote)
CVSS v4.08.8 (High)
CWE IDCWE-89 (SQL Injection)
PlatformJRuby / Java
Componentlib/arjdbc/jdbc/adapter.rb
Exploit StatusPoC Available (Theoretical)

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1059.006Command and Scripting Interpreter: Python
Execution
CWE-89
SQL Injection

Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

Known Exploits & Detection

GitHub IssueOriginal discussion and discovery of the flaw

Vulnerability Timeline

Vulnerability reported in GitHub Issue #322
2013-02-05
Approximate release of fixed version 1.2.8
2013-02-01
Advisory formally published in GHSA database
2026-01-16

References & Sources

  • [1]GitHub Advisory
  • [2]OSV Record

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.