CVE-2026-0603: Second-Order SQL Injection in Hibernate ORM
In enterprise Java development, Hibernate ORM is commonly viewed as an automatic safeguard against SQL injection. When developers write object-relational code, they expect Hibernate to handle query compilation, type casting, and parameter binding behind the scenes without exposing the data layer to classic concatenation flaws.
While analyzing how Hibernate handles bulk operations on complex entity mappings, I uncovered a second-order SQL injection vulnerability within InlineIdsOrClauseBuilder.
Under specific configurations, Hibernate bypassed JDBC parameter binding entirely and constructed raw SQL query strings by directly concatenating persisted primary keys. When an application permits user-controlled or string-based entity IDs, an attacker can persist an injection payload that executes arbitrary SQL during subsequent update or delete operations.
How It Works: The Inlining Optimization
To understand how this vulnerability happens, we have to look at how Hibernate 5 executes bulk operations (such as DELETE or UPDATE statements via HQL/JPQL) on entities that use multi-table inheritance patterns (joined-subclass or table-per-class).
When an application issues a bulk query against an entity split across multiple tables, Hibernate cannot always translate that operation into a single database-level statement. To solve this, Hibernate implements different bulk ID strategies.
One strategy is InlineIdsOrClauseBulkIdStrategy, configured via:
hibernate.hql.bulk_id_strategy=org.hibernate.hql.spi.id.inline.InlineIdsOrClauseBulkIdStrategyWhen this strategy is active, Hibernate performs the bulk operation in two phases:
- It selects the IDs of all entities matching the target criteria.
- It generates subsequent SQL UPDATE or DELETE statements against the individual underlying tables, constraining the operation with an inline WHERE clause containing the matching IDs.
Root Cause Analysis
The flaw lies inside InlineIdsOrClauseBuilder. When generating the SQL statement for phase two, Hibernate built an OR chain of IDs.
Instead of binding each collected ID as a prepared statement parameter (?), the builder converted each ID value directly into a string and concatenated it into the SQL buffer:
// Conceptual snippet from InlineIdsOrClauseBuilder
StringBuilder buffer = new StringBuilder();
buffer.append(" where ");
for (int i = 0; i < ids.size(); i++) {
if (i > 0) {
buffer.append(" or ");
}
// The entity ID is directly appended into the raw SQL string
buffer.append(idColumnName).append(" = '").append(ids.get(i).toString()).append("'");
}The code assumed that primary keys stored in the database are inherently trusted and safe.
While that assumption holds true for auto-incremented integers or server-generated UUIDs, many production architectures allow string-based or client-supplied IDs, such as custom usernames, product SKU codes, external references, or user-defined slugs.
Because the stored ID value was not sanitized or parameterized when building the subsequent statement, it created a classic second-order SQL injection:
- Stage 1 (Storage) The attacker supplies a crafted ID containing single quotes and SQL operators during normal entity creation. The initial INSERT is properly parameterized by Hibernate, so the payload safely lands in the database.
- Stage 2 (Execution): At a later time, a bulk UPDATE or DELETE query targets the entity. Hibernate retrieves the stored ID and feeds it into InlineIdsOrClauseBuilder. The payload breaks out of the inline SQL literal and executes unauthorized commands.
Proof of Concept
Consider an entity defined with a client-supplied String ID:
@Entity
@Table(name = "tenant_nodes")
public class TenantNode {
@Id
@Column(name = "node_id", length = 255)
private String nodeId;
private String status;
// Getters and setters
}1. Storing the Payload
The application exposes an endpoint allowing a user to register a node:
POST /api/nodes HTTP/1.1
Content-Type: application/json
{
"nodeId": "node-1' OR '1'='1",
"status": "pending"
}Hibernate saves the entity using a standard prepared statement. No SQL injection occurs here; the literal string node-1' OR '1'='1 is stored cleanly in the node_id column.
Triggering the Second-Order Injection
Later, an automated maintenance worker or admin workflow triggers a bulk cleanup operation via JPQL:
entityManager.createQuery("DELETE FROM TenantNode n WHERE n.status = :status")
.setParameter("status", "pending")
.executeUpdate();When InlineIdsOrClauseBuilder processes the matching IDs to delete dependent table rows, it produces the following query:
DELETE FROM tenant_nodes WHERE (node_id = 'node-1' OR '1'='1')Because the single quotes break out of the string boundary, the condition evaluates to true for every single row in the database table, wiping all tenant records instead of just the targeted entity.
In our coordinated testing with HeroDevs, we demonstrated that depending on the database dialect, this vector can be extended beyond simple table truncation to include data exfiltration and file disclosure (such as utilizing database functions to read /etc/passwd).
Remediation
- Upgrade Hibernate ORM:
- For applications running on supported Hibernate 5 release branches, upgrade to version 5.3.38 or newer.
- For applications running on legacy, end-of-life lines like Hibernate 5.6.x, apply patches through HeroDevs Never-Ending Support (NES).
- Hibernate 6 Migration:
- Hibernate 6 completely redesigned the internal query compilation architecture with the Semantic Query Model (SQM). The old inlining builders were removed and replaced with safe, parameterized bulk handling.
- Configuration Check:
- Review your persistence.xml or database properties. If hibernate.hql.bulk_id_strategy is explicitly configured to use InlineIdsOrClauseBulkIdStrategy, switch to a temporary-table strategy or default handling.
- Input Validation:
- Ensure entity IDs supplied by external clients are strictly validated against strict alphanumeric patterns before persistence.
Timeline
- October 2025: Vulnerability identified during source analysis and validated with a working PoC.
- December 2025: Vulnerability reproduced by the HerodDevs team
- January 2026: Coordinated disclosure handled in partnership with Tommy Williams and HeroDevs, working alongside the Red Hat and Hibernate security teams.
- January 19, 2026: Red Hat and Hibernate publicly confirmed the vulnerability and issued advisories.
- January 23, 2026: CVE-2026-0603 published to the NVD with an 8.3 base score.
References
YouGina
Software engineer and security researcher. I write about bug bounty hunting, application security, and the craft of building software that is harder to break.
Get in touch
@yougina
For research questions, disclosure conversations, or a second opinion on something you are working through.