SQL Injection: Hibernate
Description
SQL Injection: Hibernate is a vulnerability where an application uses Hibernate ORM to execute dynamic SQL or HQL (Hibernate Query Language) statements built with user-controlled input, allowing attackers to modify the query's meaning or execute arbitrary SQL commands. Although Hibernate provides an abstraction layer over direct SQL, it still supports dynamic queries through HQL, native SQL, and Criteria API. When user input is concatenated directly into these queries without proper sanitization or parameterization, attackers can inject malicious query fragments to access unauthorized data, modify database contents, or potentially execute administrative operations.
Risk
Hibernate-based SQL injection poses severe risks despite the ORM abstraction. Attackers can read sensitive data from any table accessible to the database user. Data modification attacks can alter records, delete data, or corrupt database integrity. Authentication bypasses become possible by manipulating login queries. In some configurations, attackers may execute stored procedures or database commands. The risk is amplified because developers may have false confidence in Hibernate's security, assuming the ORM prevents injection attacks. Additionally, HQL injection is often overlooked by security scanners focused on traditional SQL injection patterns.
Solution
Use parameterized queries with named or positional parameters for all Hibernate queries involving user input. Utilize Criteria API or JPA CriteriaBuilder for type-safe, injection-resistant queries. Never concatenate user input directly into HQL or native SQL strings. Apply strict input validation using allowlist patterns for values that must be used in queries. Implement the principle of least privilege for database accounts used by the application. Consider using an Object-Relational Mapping approach that completely avoids string-based queries. Perform security code reviews specifically targeting Hibernate query construction.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Read Application Data - Attackers can extract sensitive information from the database by modifying query conditions or using UNION-based injection techniques. |
| Integrity | Scope: Integrity Modify Application Data - Injection attacks can alter, insert, or delete database records, compromising data integrity. |
| Access Control | Scope: Access Control Bypass Protection Mechanism - Authentication and authorization checks based on database queries can be bypassed through injection. |
Example Code
Vulnerable Code
// Vulnerable: HQL with string concatenation
import org.hibernate.Session;
import org.hibernate.Query;
import java.util.List;
public class VulnerableUserRepository {
private Session session;
// Vulnerable: User input directly concatenated into HQL
public List<User> findUsersByCity(String city) {
// Vulnerable: Classic HQL injection
String hql = "FROM User u WHERE u.city = '" + city + "'";
Query query = session.createQuery(hql);
return query.list();
// Attack: city = "' OR '1'='1"
// Results in: FROM User u WHERE u.city = '' OR '1'='1'
// Returns all users!
}
// Vulnerable: Multiple parameters concatenated
public User authenticateUser(String username, String password) {
// Vulnerable: Both parameters injectable
String hql = "FROM User u WHERE u.username = '" + username +
"' AND u.password = '" + password + "'";
Query query = session.createQuery(hql);
return (User) query.uniqueResult();
// Attack: username = "admin' --"
// Results in: FROM User u WHERE u.username = 'admin' --' AND u.password = '...'
// Comments out password check, logs in as admin!
}
// Vulnerable: Dynamic ORDER BY
public List<User> findUsersSorted(String sortField) {
// Vulnerable: Order by clause injection
String hql = "FROM User u ORDER BY u." + sortField;
Query query = session.createQuery(hql);
return query.list();
// Attack: sortField = "id; DELETE FROM User; --"
// Could execute destructive commands
}
// Vulnerable: Native SQL query
public List<Object[]> searchUsers(String searchTerm) {
// Vulnerable: Native SQL with concatenation
String sql = "SELECT * FROM users WHERE name LIKE '%" + searchTerm + "%'";
Query query = session.createSQLQuery(sql);
return query.list();
// Attack: searchTerm = "%'; DROP TABLE users; --"
}
}
// Vulnerable: Criteria API misuse
import org.hibernate.Criteria;
import org.hibernate.criterion.Restrictions;
public class VulnerableCriteriaRepository {
private Session session;
// Vulnerable: Dynamic property name from user input
public List<User> findByDynamicField(String fieldName, String value) {
Criteria criteria = session.createCriteria(User.class);
// Vulnerable: Field name from user input
criteria.add(Restrictions.eq(fieldName, value));
// If fieldName is manipulated, unexpected fields could be queried
return criteria.list();
}
// Vulnerable: SQL restriction with user input
public List<User> searchWithRestriction(String userInput) {
Criteria criteria = session.createCriteria(User.class);
// Vulnerable: Direct SQL fragment from user input
criteria.add(Restrictions.sqlRestriction("name LIKE '%" + userInput + "%'"));
return criteria.list();
}
}
// Vulnerable: JPA with string concatenation
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
public class VulnerableJpaRepository {
private EntityManager em;
// Vulnerable: JPQL injection
public List<Product> findProductsByCategory(String category) {
// Vulnerable: String concatenation in JPQL
String jpql = "SELECT p FROM Product p WHERE p.category = '" + category + "'";
TypedQuery<Product> query = em.createQuery(jpql, Product.class);
return query.getResultList();
}
// Vulnerable: Dynamic WHERE clause
public List<Order> searchOrders(String status, String customerId) {
StringBuilder jpql = new StringBuilder("SELECT o FROM Order o WHERE 1=1");
// Vulnerable: Building dynamic query with user input
if (status != null) {
jpql.append(" AND o.status = '").append(status).append("'");
}
if (customerId != null) {
jpql.append(" AND o.customerId = '").append(customerId).append("'");
}
return em.createQuery(jpql.toString(), Order.class).getResultList();
}
}
Fixed Code
// Fixed: Using named parameters
import org.hibernate.Session;
import org.hibernate.Query;
import java.util.List;
public class SecureUserRepository {
private Session session;
// Fixed: Named parameters prevent injection
public List<User> findUsersByCity(String city) {
String hql = "FROM User u WHERE u.city = :city";
Query query = session.createQuery(hql);
query.setParameter("city", city); // Fixed: Parameterized
return query.list();
// User input is treated as a literal value, not SQL code
}
// Fixed: Multiple named parameters
public User authenticateUser(String username, String password) {
String hql = "FROM User u WHERE u.username = :username " +
"AND u.password = :password";
Query query = session.createQuery(hql);
query.setParameter("username", username);
query.setParameter("password", password);
return (User) query.uniqueResult();
// Injection attempts like "admin' --" are treated as literal strings
}
// Fixed: Positional parameters
public List<User> findUsersByAgeRange(int minAge, int maxAge) {
String hql = "FROM User u WHERE u.age >= ?0 AND u.age <= ?1";
Query query = session.createQuery(hql);
query.setParameter(0, minAge);
query.setParameter(1, maxAge);
return query.list();
}
// Fixed: Whitelist for dynamic ORDER BY
public List<User> findUsersSorted(String sortField) {
// Fixed: Whitelist allowed sort fields
Set<String> allowedFields = Set.of("id", "username", "email", "createdAt");
if (!allowedFields.contains(sortField)) {
sortField = "id"; // Default to safe value
}
String hql = "FROM User u ORDER BY u." + sortField;
Query query = session.createQuery(hql);
return query.list();
}
// Fixed: Native SQL with parameters
public List<Object[]> searchUsers(String searchTerm) {
String sql = "SELECT * FROM users WHERE name LIKE :searchTerm";
Query query = session.createSQLQuery(sql);
query.setParameter("searchTerm", "%" + searchTerm + "%");
return query.list();
}
}
// Fixed: Using Criteria API properly
import org.hibernate.Criteria;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.MatchMode;
public class SecureCriteriaRepository {
private Session session;
// Fixed: Type-safe Criteria queries
public List<User> findByCity(String city) {
Criteria criteria = session.createCriteria(User.class);
criteria.add(Restrictions.eq("city", city)); // Parameterized internally
return criteria.list();
}
// Fixed: Safe LIKE query
public List<User> searchByName(String searchTerm) {
Criteria criteria = session.createCriteria(User.class);
// Fixed: MatchMode handles escaping
criteria.add(Restrictions.ilike("name", searchTerm, MatchMode.ANYWHERE));
return criteria.list();
}
// Fixed: Whitelist dynamic field names
public List<User> findByDynamicField(String fieldName, String value) {
// Fixed: Whitelist allowed field names
Set<String> allowedFields = Set.of("city", "status", "role");
if (!allowedFields.contains(fieldName)) {
throw new IllegalArgumentException("Invalid field name");
}
Criteria criteria = session.createCriteria(User.class);
criteria.add(Restrictions.eq(fieldName, value));
return criteria.list();
}
// Fixed: Complex criteria with multiple conditions
public List<User> searchUsers(String name, String city, Integer minAge, Integer maxAge) {
Criteria criteria = session.createCriteria(User.class);
if (name != null && !name.isEmpty()) {
criteria.add(Restrictions.ilike("name", name, MatchMode.ANYWHERE));
}
if (city != null && !city.isEmpty()) {
criteria.add(Restrictions.eq("city", city));
}
if (minAge != null) {
criteria.add(Restrictions.ge("age", minAge));
}
if (maxAge != null) {
criteria.add(Restrictions.le("age", maxAge));
}
return criteria.list();
}
}
// Fixed: JPA with type-safe Criteria API
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.List;
public class SecureJpaRepository {
private EntityManager em;
// Fixed: JPQL with parameters
public List<Product> findProductsByCategory(String category) {
String jpql = "SELECT p FROM Product p WHERE p.category = :category";
TypedQuery<Product> query = em.createQuery(jpql, Product.class);
query.setParameter("category", category);
return query.getResultList();
}
// Fixed: JPA Criteria API for type-safe queries
public List<Order> searchOrders(String status, String customerId) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> cq = cb.createQuery(Order.class);
Root<Order> order = cq.from(Order.class);
List<Predicate> predicates = new ArrayList<>();
if (status != null && !status.isEmpty()) {
predicates.add(cb.equal(order.get("status"), status));
}
if (customerId != null && !customerId.isEmpty()) {
predicates.add(cb.equal(order.get("customerId"), customerId));
}
cq.where(predicates.toArray(new Predicate[0]));
return em.createQuery(cq).getResultList();
}
// Fixed: Type-safe search with like
public List<User> searchUsersByName(String searchTerm) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> user = cq.from(User.class);
// Fixed: cb.like with parameter
cq.where(cb.like(
cb.lower(user.get("name")),
"%" + searchTerm.toLowerCase() + "%"
));
return em.createQuery(cq).getResultList();
}
}
// Fixed: Spring Data JPA Repository
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface SecureUserJpaRepository extends JpaRepository<User, Long> {
// Fixed: Spring Data method naming (auto-parameterized)
List<User> findByCity(String city);
List<User> findByUsernameAndPassword(String username, String password);
// Fixed: Named query with @Param
@Query("SELECT u FROM User u WHERE u.email LIKE %:email%")
List<User> searchByEmail(@Param("email") String email);
// Fixed: Native query with parameters
@Query(value = "SELECT * FROM users WHERE status = :status",
nativeQuery = true)
List<User> findByStatus(@Param("status") String status);
// Fixed: Complex query with multiple parameters
@Query("SELECT u FROM User u WHERE " +
"(:name IS NULL OR u.name LIKE %:name%) AND " +
"(:city IS NULL OR u.city = :city) AND " +
"(:minAge IS NULL OR u.age >= :minAge)")
List<User> searchUsers(
@Param("name") String name,
@Param("city") String city,
@Param("minAge") Integer minAge
);
}
CVE Examples
- CVE-2016-1182: Apache Struts allowed SQL injection through Hibernate queries with user input.
- CVE-2020-11988: xmlgraphics-commons vulnerability related to HQL injection patterns.
References
- MITRE Corporation. "CWE-564: SQL Injection: Hibernate." https://cwe.mitre.org/data/definitions/564.html
- OWASP. "SQL Injection Prevention Cheat Sheet."
- Hibernate. "HQL and JPQL Query Language."