GHSA-5QW5-WF2Q-F538

The Questionable Substitution: SQL Injection in JRuby's JDBC Adapter

Alon Barad
Alon Barad
Software Engineer

Jan 17, 2026·6 min read

Executive Summary (TL;DR)

Older versions of the `activerecord-jdbc-adapter` (< 1.2.8) used a naive `gsub` strategy to replace SQL bind parameters (`?`). If a user input string contained a `?`, the adapter effectively hallucinated a new placeholder, injecting the *next* bind parameter directly into the current string literal. This breaks SQL syntax and allows for classic SQL injection attacks.

A recursive string substitution vulnerability in the activerecord-jdbc-adapter gem allowed attackers to inject malicious SQL by simply including a question mark in their input. This flaw affects JRuby applications connecting to databases via JDBC.

The Hook: Lost in Translation

JRuby is a fascinating beast. It takes the elegance of Ruby and runs it on the JVM, giving you access to the massive ecosystem of Java libraries. One of the most critical bridges in this architecture is the activerecord-jdbc-adapter. Its job is unglamorous but essential: it translates ActiveRecord's Ruby-centric database calls into JDBC (Java Database Connectivity) commands that the JVM can execute.

But translation is hard. When you are sitting between a dynamic language like Ruby and a rigid, type-safe interface like JDBC, you have to handle data marshalling carefully. For a long time, this adapter relied on a mechanism that seemed intuitive but was fundamentally broken. It treated SQL generation not as a structured tree traversal, but as a game of find-and-replace strings.

This vulnerability is a classic example of "string manipulation gone wrong." It highlights why Modern SQL handling has moved almost entirely to Abstract Syntax Trees (ASTs). When you treat code (SQL) as simple text, you eventually forget that the text you are manipulating has semantic meaning. And when you forget that, you leave the door open for an attacker to rewrite your sentences for you.

The Flaw: Recursion is a Trap

The root cause here is a logic bug known as "double substitution" or "recursive replacement." In SQL, we use placeholders (usually ?) to safely insert user data into queries. The database driver is supposed to handle this so that the input is treated strictly as data, not executable code. However, the activerecord-jdbc-adapter (versions prior to 1.2.8) took a shortcut. It tried to build the final SQL string before handing it off to the driver in certain contexts, using Ruby's gsub method.

The logic was roughly: "Find every ? in the query and replace it with the quoted version of the user's input." Sounds safe, right? Wrong. The fatal flaw was that the substitution loop didn't account for what was inside the user's input. If the first user input contained a question mark (e.g., "Who is the woodchuck?"), the gsub engine—depending on the specific implementation and version of JRuby/Ruby at the time—could interpret that new ? as a valid target for the next substitution.

It’s like ordering a burger, and when the cashier asks "Fries with that?", you reply "Yes, and also: free burgers for everyone?". If the cashier is a poorly programmed robot, they might process your sentence, hear the second question mark, and assume it's a prompt to give away the store inventory. The adapter effectively parsed the user's data as the developer's template.

The Code: The Smoking Gun

Let's look at the vulnerable implementation in lib/arjdbc/jdbc/adapter.rb. This is code that makes security researchers wince. It uses gsub with a block, iterating through the bind parameters and stuffing them into the string.

# VULNERABLE CODE (v1.2.5)
def substitute_binds(manager, binds = [])
  sql = extract_sql(manager)
  if binds.empty?
    sql
  else
    copy = binds.dup
    # This is where the magic (and the tragedy) happens
    sql.gsub('?') { quote(*copy.shift.reverse) }
  end
end

The issue is subtle. When gsub performs a replacement, if the replacement string acts as a future match target, you lose control of the structure. The fix involved moving away from raw string manipulation for modern ActiveRecord versions (3.0+) and leveraging the Arel AST visitor pattern. This ensures that parameters are inserted into the structure of the query object, not just pasted into a text blob.

# FIXED CODE (v1.2.8)
if ActiveRecord::VERSION::MAJOR >= 3
  def to_sql(arel, binds = [])
    if arel.respond_to?(:ast)
      # SAFE: The visitor handles structural insertion
      visitor.accept(arel.ast) do
        quote(*binds.shift.reverse)
      end
    else
      # ... fallback logic ...
    end
  end
end

The Exploit: Woodchucks and Rogue Quotes

To exploit this, an attacker doesn't need complex hex encoding or buffer overflows. They just need to ask a question. Let's assume a simple query intended to log a Q&A pair:

Target SQL: INSERT INTO questions ("question", "answer") VALUES (?, ?)

The attacker provides two inputs:

  1. Input A: "How much wood could a woodchuck chuck?"
  2. Input B: "Who cares"

When the adapter processes this:

  1. It sees the first ? in the template.
  2. It replaces it with Input A (quoted): 'How much wood could a woodchuck chuck?'.
  3. The Glitch: The adapter scans for the next placeholder. It sees the ? inside the string we just inserted.
  4. It eagerly grabs Input B ("Who cares") and stuffs it into that spot.

Resulting Malformed SQL:

INSERT INTO questions ("question", "answer") 
VALUES ('How much wood could a woodchuck chuck'Who cares'', ?)

Notice the syntax breakage? The string literal ends prematurely at chuck. The text Who cares is now interpreted as SQL commands. By carefully crafting the second input (Input B), an attacker can turn Who cares into DROP TABLE users --. The original second placeholder (the legitimate one) is left dangling or ignored, leaving the query completely compromised.

The Impact: Game Over for Data Integrity

This vulnerability is particularly nasty because it exists in the adapter layer. This means it sits beneath your application logic and even beneath the ActiveRecord ORM itself. It doesn't matter how careful you are with your Rails validations; if the adapter constructs the final SQL string incorrectly, you are vulnerable.

The impact is a high-severity SQL Injection. This allows for:

  • Data Exfiltration: Reading sensitive tables by injecting UNION SELECT statements.
  • Data Destruction: Injecting DELETE or DROP commands.
  • Authentication Bypass: manipulating login queries to always return true.

Since this affects JRuby applications, it often hits enterprise environments where legacy Java integration is required. The CVSS score of 8.8 reflects the high confidentiality and integrity impact, even though it requires network access to the application (AV:N).

The Fix: Stop Using String Glue

The remediation is straightforward: Update activerecord-jdbc-adapter to version 1.2.8 or later.

The patch fundamentally changes the strategy for generating SQL. Instead of treating the query as a flat string with holes in it, the patched version utilizes the Abstract Syntax Tree (AST) provided by the Arel library (the engine behind ActiveRecord). By using visitor.accept(arel.ast), the adapter delegates the construction of the SQL to a component that understands the structure of the query, not just the characters.

If you are stuck on an ancient version of JRuby or ActiveRecord that cannot support the newer adapter, you are in a tight spot. You would effectively need to sanitize all user input to remove ? characters before they reach the database layer, which is functionally impossible for many applications. Upgrade or die (virtually speaking).

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
RubyGems
< 1.2.81.2.8
AttributeDetail
Vulnerability TypeSQL Injection (Recursive Substitution)
CWE IDCWE-89
CVSS Score8.8 (High)
CVSS VectorCVSS: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
Attack VectorNetwork
Affected Componentlib/arjdbc/jdbc/adapter.rb
CWE-89
SQL Injection

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

Known Exploits & Detection

Vulnerability Timeline

Vulnerability reported in GitHub Issue #322
2013-02-05
Assigned OSVDB-114854
2013-02-25
Fix released in version 1.2.8
2013-02-28
GHSA Advisory published
2026-01-16

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.