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

ImpactDetails
ConfidentialityScope: Confidentiality

Read Application Data - Attackers can extract sensitive information from the database by modifying query conditions or using UNION-based injection techniques.
IntegrityScope: Integrity

Modify Application Data - Injection attacks can alter, insert, or delete database records, compromising data integrity.
Access ControlScope: 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

  1. MITRE Corporation. "CWE-564: SQL Injection: Hibernate." https://cwe.mitre.org/data/definitions/564.html
  2. OWASP. "SQL Injection Prevention Cheat Sheet."
  3. Hibernate. "HQL and JPQL Query Language."