Security
Jan 21, 2026

CVE-2026-0603: Second-Order SQL Injection in Hibernate UPDATE/DELETE (InlineIdsOrClauseBuilder)

How a rarely used Hibernate ID strategy enabled high-impact second-order SQL injection in UPDATE and DELETE paths

Give me the TL;DR
CVE-2026-0603: Second-Order SQL Injection in Hibernate UPDATE/DELETE (InlineIdsOrClauseBuilder)
For Qualys admins, NES for .NET directly resolves the EOL/Obsolete Software:   Microsoft .NET Version 6 Detected vulnerability, ensuring your systems remain secure and compliant. Fill out the form to get pricing details and learn more.

Open source powers the modern software stack—but its security depends on a shared commitment to finding and fixing weaknesses before they become incidents.

At HeroDevs, that’s our mission: secure open source. We do it in two ways:

  1. Remediating known CVEs across critical ecosystems, and

  2. Proactively researching vulnerabilities—before attackers can exploit them.

This work is never “just running a scanner.” High-quality security research demands deep framework knowledge and disciplined validation: understanding threat models, reproducing proofs of concept, ruling out false positives, and coordinating ethical vulnerability disclosure with maintainers.

Recently, a high-severity Hibernate ORM vulnerability, CVE-2026-0603, was discovered in partnership with external security researcher Christiaan Swiers. Christiaan identified the issue; HeroDevs handled the coordination, investigation, and validation, then worked with the Hibernate and Red Hat teams to disclose and publish the CVE.

What is CVE-2026-0603?

CVE-2026-0603 is a second-order SQL injection that can occur during UPDATE or DELETE operations in Hibernate, tied to how Hibernate constructs ID predicates when using the InlineIdsOrClauseBuilder.

This is not a “classic” injection where user input immediately becomes part of a query string. Instead, it’s an injection where:

  • Stage 1: an attacker causes malicious data to be stored (in this case, as an entity identifier), and

  • Stage 2: at a later time, Hibernate reuses that stored value in a dynamically built SQL statement—allowing the injected SQL to execute.

This delayed trigger is what makes it second-order—and what makes it easy to miss during ordinary testing and security validation.

High Severity / Infrequent Use Case

This vulnerability is unlikely in many applications because most systems do not let clients control primary keys. In most applications, the IDs are typically auto-incremented integers or server-generated UUIDs.

But CVE-2026-0603 becomes relevant when all (or most) of the following conditions apply:

  • the client can set their own identifiers AND,

  • those IDs are stored as free-form strings (e.g., @Id private String id; with a VARCHAR(...) primary key),

  • there are limited restrictions on length/characters,

  • and Hibernate is using the inline “OR clause” approach for ID selection.

Those conditions are not “default,” but they’re also not purely hypothetical—string IDs and “natural keys” show up in real systems (usernames, external product codes, human-readable IDs, etc.).

Red Hat ultimately published the CVSS vector as:

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L

That mapping aligns with this scenario: confidentiality and integrity impacts can be high when an injection is achieved, even if the application pattern required is less common.

Root cause: InlineIdsOrClauseBuilder reuses ID tokens unsafely

At the center of this vulnerability is how Hibernate builds the ID predicate.

In InlineIdsOrClauseBuilder#toStatement(), Hibernate iterates IDs and constructs an OR expression. The key detail: it inlines the identifier value into the SQL string.

The relevant portion looks like this:

public String toStatement() {
    StringBuilder buffer = new StringBuilder();

    for ( int i = 0; i < getIds().size(); i++ ) {
        Object[] idTokens = getIds().get( i );

        if ( i > 0 ) {
            buffer.append( " or " );
        }

        buffer.append( "(" );

        for ( int j = 0; j < getColumns().length; j++ ) {
            if ( j > 0 ) {
                buffer.append( " and " );
            }
            buffer.append( getColumns()[j] );
            buffer.append( " = " );
		 //The injection happens in the following line
            buffer.append( quoteIdentifier( idTokens[j] ) ); 
        }
        buffer.append( ")" );
    }

    return buffer.toString();
}

The practical issue is straightforward: if an attacker can influence what’s stored in the identifier column, and that value later appears inside an inlined SQL predicate, you have an injection surface.

Even though the method name suggests “quoting,” this is not the same as safe parameter binding. A string literal that includes attacker-controlled quote characters or SQL fragments can still break out of the intended context.

Understanding the exploit class: second-order injection in operations

The easiest way to reason about this is to model the flow:

Stage 1: store the payload

An attacker creates an entity (or otherwise causes one to be persisted) whose ID includes SQL control characters, e.g.:

' or TRUE or id=’

Hibernate stores it. Nothing “explodes” yet.

Stage 2: trigger an UPDATE/DELETE

If a user is able to trigger an Update or Delete of their own objects, then they can trigger Stage 2 on their own. Otherwise, later, an admin action or background job performs an operation that touches the attacker-controlled row—by name, status, timestamp, etc.

Hibernate builds a statement that must match a set of identifiers, and (in the affected path) uses the inline OR builder.

If the stored ID is treated as plain text and pasted into the predicate, the payload can turn a specific filter into something broader—up to and including “match everything.”

That’s the defining characteristic of second-order SQL injection: the application stores the dangerous input first, then later “activates” it when that stored value is interpolated into a new SQL statement.

What we validated in our investigation

A key part of responsible research is proving exploitability without overfitting to a contrived setup. HeroDevs validated two core impacts in controlled PoCs:

1) Integrity/availability impact: deleting one row deletes everything

We confirmed a scenario where an attacker creates a record with a malicious ID, and then a delete triggered later can remove all rows—not just the intended record.

This is a dangerous ORM bug: the developer didn’t write raw SQL, and the delete looks normal at the service layer, but the generated SQL predicate becomes attacker-controlled at execution time. This contributes to RedHat’s rating of Integrity as High even though Availability is Low.

2) Confidentiality impact: extracting sensitive data via blind techniques

We also validated that—even though the injection triggers in the context of UPDATE/DELETE—an attacker can still extract data using boolean/time-based inference (classic “blind SQLi” techniques).

In the PoC, this was demonstrated by retrieving server-side file content one character at a time using repeated requests and conditional logic. The key takeaway is not the exact payload, it’s that an injection in a DELETE path can still be repurposed into a data exfiltration channel when the database engine supports relevant functions and the environment permits it.

Why this matters for framework security

Two aspects here are worth highlighting for the broader Hibernate ecosystem:

  • This is not a “developer concatenated strings into HQL” situation. It’s a framework-level behavior in how ID clauses can be generated.

  • There is an important design lesson: ID strategies must be consistently parameterized. If one strategy binds values safely and another inlines them, the “unsafe” one becomes a sharp edge that only appears in certain configurations—making it harder for applications to defend themselves consistently.

During triage, we also looked for similar patterns in related ID clause builders. That’s exactly the sort of follow-on investigation that turns a single report into a more robust security posture for the project.

Mitigation guidance

If you’re responsible for a Hibernate-based application:

  • Upgrade Hibernate to a version that includes the fix for CVE-2026-0603 (follow your vendor guidance and the Red Hat CVE page above).  
  • If you are using Hibernate 5.6, likely in combination with Spring Boot 2.7, community support is no longer available but commercial support, like the kind HeroDevs provides, can provide a secure drop-in replacement.
  • Audit whether your application allows client-controlled identifiers (especially String primary keys).
  • If you must accept externally-derived identifiers (product codes, usernames, etc.), implement strict validation:

    • Constrain allowed characters
    • Enforce length limits
    • Avoid accepting quotes/control characters
    • Consider separating “public identifiers” from actual primary keys
    • Review whether you rely on Hibernate operations that might invoke inline ID clause strategies

Closing thoughts

CVE-2026-0603 is a good example of why proactive research matters even in mature, widely used projects. The exploit conditions aren’t “default,” but when they line up, the impact can be severe—ranging from mass updates/deletes to sensitive data extraction.

We’re grateful to Christiaan Swiers for the original discovery and for working through follow-up questions and validation details. And we appreciate the coordination with the Hibernate and Red Hat teams to publish the CVE responsibly.

If your organization depends on OSS frameworks at scale, this is exactly the kind of work HeroDevs exists to do: reduce risk in the open source supply chain—before it becomes an incident.

Table of Contents
Author
Tommy Williams
Senior Software Developer
Open Source Insights Delivered Monthly