SQL-Injection: Hibernate
Beschreibung
SQL-Injection: Hibernate ist eine Schwachstelle, bei der eine Anwendung Hibernate ORM verwendet, um dynamische SQL- oder HQL-Anweisungen (Hibernate Query Language) auszuführen, die mit benutzergesteuerter Eingabe erstellt wurden, wodurch Angreifer die Bedeutung der Abfrage ändern oder beliebige SQL-Befehle ausführen können. Obwohl Hibernate eine Abstraktionsschicht über direktes SQL bietet, unterstützt es weiterhin dynamische Abfragen über HQL, natives SQL und Criteria API. Wenn Benutzereingaben direkt in diese Abfragen ohne ordnungsgemäße Bereinigung oder Parametrisierung eingefügt werden, können Angreifer bösartige Abfragefragmente injizieren, um auf nicht autorisierte Daten zuzugreifen, Datenbankinhalte zu ändern oder potenziell administrative Operationen auszuführen.
Risiko
Hibernate-basierte SQL-Injection birgt trotz der ORM-Abstraktion schwere Risiken. Angreifer können sensible Daten aus jeder Tabelle lesen, die dem Datenbankbenutzer zugänglich ist. Datenmodifikationsangriffe können Datensätze ändern, Daten löschen oder die Datenbankintegrität beschädigen. Authentifizierungsumgehungen werden durch Manipulation von Login-Abfragen möglich. In einigen Konfigurationen können Angreifer gespeicherte Prozeduren oder Datenbankbefehle ausführen. Das Risiko wird verstärkt, weil Entwickler falsches Vertrauen in Hibernates Sicherheit haben können und annehmen, dass das ORM Injection-Angriffe verhindert. Zusätzlich wird HQL-Injection oft von Sicherheitsscannern übersehen, die sich auf traditionelle SQL-Injection-Muster konzentrieren.
Lösung
Verwenden Sie parametrisierte Abfragen mit benannten oder positionellen Parametern für alle Hibernate-Abfragen, die Benutzereingaben beinhalten. Nutzen Sie Criteria API oder JPA CriteriaBuilder für typsichere, injection-resistente Abfragen. Fügen Sie Benutzereingaben niemals direkt in HQL- oder native SQL-Strings ein. Wenden Sie strikte Eingabevalidierung mit Allowlist-Mustern für Werte an, die in Abfragen verwendet werden müssen. Implementieren Sie das Prinzip der minimalen Rechte für Datenbankkonten, die von der Anwendung verwendet werden. Erwägen Sie die Verwendung eines Object-Relational-Mapping-Ansatzes, der string-basierte Abfragen vollständig vermeidet. Führen Sie Sicherheits-Code-Reviews durch, die speziell auf Hibernate-Abfragekonstruktion abzielen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Anwendungsdaten lesen - Angreifer können sensible Informationen aus der Datenbank extrahieren, indem sie Abfragebedingungen modifizieren oder UNION-basierte Injection-Techniken verwenden. |
| Integrität | Bereich: Integrität Anwendungsdaten ändern - Injection-Angriffe können Datenbankdatensätze ändern, einfügen oder löschen und so die Datenintegrität gefährden. |
| Zugriffskontrolle | Bereich: Zugriffskontrolle Schutzmechanismus umgehen - Authentifizierungs- und Autorisierungsprüfungen basierend auf Datenbankabfragen können durch Injection umgangen werden. |
Beispielcode
Verwundbarer Code
// Verwundbar: HQL mit String-Konkatenation
import org.hibernate.Session;
import org.hibernate.Query;
import java.util.List;
public class VulnerableUserRepository {
private Session session;
// Verwundbar: Benutzereingabe direkt in HQL konkateniert
public List<User> findUsersByCity(String city) {
// Verwundbar: Klassische HQL-Injection
String hql = "FROM User u WHERE u.city = '" + city + "'";
Query query = session.createQuery(hql);
return query.list();
// Angriff: city = "' OR '1'='1"
// Ergebnis: FROM User u WHERE u.city = '' OR '1'='1'
// Gibt alle Benutzer zurück!
}
// Verwundbar: Mehrere Parameter konkateniert
public User authenticateUser(String username, String password) {
// Verwundbar: Beide Parameter injizierbar
String hql = "FROM User u WHERE u.username = '" + username +
"' AND u.password = '" + password + "'";
Query query = session.createQuery(hql);
return (User) query.uniqueResult();
// Angriff: username = "admin' --"
// Ergebnis: FROM User u WHERE u.username = 'admin' --' AND u.password = '...'
// Kommentiert Passwortprüfung aus, meldet sich als admin an!
}
// Verwundbar: Dynamisches ORDER BY
public List<User> findUsersSorted(String sortField) {
// Verwundbar: Order-by-Klausel-Injection
String hql = "FROM User u ORDER BY u." + sortField;
Query query = session.createQuery(hql);
return query.list();
// Angriff: sortField = "id; DELETE FROM User; --"
// Könnte destruktive Befehle ausführen
}
// Verwundbar: Native SQL-Abfrage
public List<Object[]> searchUsers(String searchTerm) {
// Verwundbar: Natives SQL mit Konkatenation
String sql = "SELECT * FROM users WHERE name LIKE '%" + searchTerm + "%'";
Query query = session.createSQLQuery(sql);
return query.list();
// Angriff: searchTerm = "%'; DROP TABLE users; --"
}
}
// Verwundbar: Criteria API Missbrauch
import org.hibernate.Criteria;
import org.hibernate.criterion.Restrictions;
public class VulnerableCriteriaRepository {
private Session session;
// Verwundbar: Dynamischer Eigenschaftsname aus Benutzereingabe
public List<User> findByDynamicField(String fieldName, String value) {
Criteria criteria = session.createCriteria(User.class);
// Verwundbar: Feldname aus Benutzereingabe
criteria.add(Restrictions.eq(fieldName, value));
// Wenn fieldName manipuliert wird, könnten unerwartete Felder abgefragt werden
return criteria.list();
}
// Verwundbar: SQL-Restriction mit Benutzereingabe
public List<User> searchWithRestriction(String userInput) {
Criteria criteria = session.createCriteria(User.class);
// Verwundbar: Direktes SQL-Fragment aus Benutzereingabe
criteria.add(Restrictions.sqlRestriction("name LIKE '%" + userInput + "%'"));
return criteria.list();
}
}
// Verwundbar: JPA mit String-Konkatenation
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
public class VulnerableJpaRepository {
private EntityManager em;
// Verwundbar: JPQL-Injection
public List<Product> findProductsByCategory(String category) {
// Verwundbar: String-Konkatenation in JPQL
String jpql = "SELECT p FROM Product p WHERE p.category = '" + category + "'";
TypedQuery<Product> query = em.createQuery(jpql, Product.class);
return query.getResultList();
}
// Verwundbar: Dynamische WHERE-Klausel
public List<Order> searchOrders(String status, String customerId) {
StringBuilder jpql = new StringBuilder("SELECT o FROM Order o WHERE 1=1");
// Verwundbar: Dynamische Abfrage mit Benutzereingabe erstellen
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();
}
}
Lösungscode
// Behoben: Verwendung benannter Parameter
import org.hibernate.Session;
import org.hibernate.Query;
import java.util.List;
public class SecureUserRepository {
private Session session;
// Behoben: Benannte Parameter verhindern 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); // Behoben: Parametrisiert
return query.list();
// Benutzereingabe wird als Literalwert behandelt, nicht als SQL-Code
}
// Behoben: Mehrere benannte Parameter
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-Versuche wie "admin' --" werden als Literalstrings behandelt
}
// Behoben: Positionsparameter
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();
}
// Behoben: Allowlist für dynamisches ORDER BY
public List<User> findUsersSorted(String sortField) {
// Behoben: Allowlist erlaubter Sortierfelder
Set<String> allowedFields = Set.of("id", "username", "email", "createdAt");
if (!allowedFields.contains(sortField)) {
sortField = "id"; // Standard auf sicheren Wert
}
String hql = "FROM User u ORDER BY u." + sortField;
Query query = session.createQuery(hql);
return query.list();
}
// Behoben: Natives SQL mit Parametern
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();
}
}
// Behoben: Ordnungsgemäße Verwendung der Criteria API
import org.hibernate.Criteria;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.MatchMode;
public class SecureCriteriaRepository {
private Session session;
// Behoben: Typsichere Criteria-Abfragen
public List<User> findByCity(String city) {
Criteria criteria = session.createCriteria(User.class);
criteria.add(Restrictions.eq("city", city)); // Intern parametrisiert
return criteria.list();
}
// Behoben: Sichere LIKE-Abfrage
public List<User> searchByName(String searchTerm) {
Criteria criteria = session.createCriteria(User.class);
// Behoben: MatchMode behandelt Escaping
criteria.add(Restrictions.ilike("name", searchTerm, MatchMode.ANYWHERE));
return criteria.list();
}
// Behoben: Allowlist für dynamische Feldnamen
public List<User> findByDynamicField(String fieldName, String value) {
// Behoben: Allowlist erlaubter Feldnamen
Set<String> allowedFields = Set.of("city", "status", "role");
if (!allowedFields.contains(fieldName)) {
throw new IllegalArgumentException("Ungültiger Feldname");
}
Criteria criteria = session.createCriteria(User.class);
criteria.add(Restrictions.eq(fieldName, value));
return criteria.list();
}
// Behoben: Komplexe Criteria mit mehreren Bedingungen
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();
}
}
// Behoben: JPA mit typsicherer 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;
// Behoben: JPQL mit Parametern
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();
}
// Behoben: JPA Criteria API für typsichere Abfragen
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();
}
// Behoben: Typsichere Suche mit 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);
// Behoben: cb.like mit Parameter
cq.where(cb.like(
cb.lower(user.get("name")),
"%" + searchTerm.toLowerCase() + "%"
));
return em.createQuery(cq).getResultList();
}
}
// Behoben: 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> {
// Behoben: Spring Data Methodenbenennung (auto-parametrisiert)
List<User> findByCity(String city);
List<User> findByUsernameAndPassword(String username, String password);
// Behoben: Named Query mit @Param
@Query("SELECT u FROM User u WHERE u.email LIKE %:email%")
List<User> searchByEmail(@Param("email") String email);
// Behoben: Native Query mit Parametern
@Query(value = "SELECT * FROM users WHERE status = :status",
nativeQuery = true)
List<User> findByStatus(@Param("status") String status);
// Behoben: Komplexe Abfrage mit mehreren Parametern
@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-Beispiele
- CVE-2016-1182: Apache Struts erlaubte SQL-Injection durch Hibernate-Abfragen mit Benutzereingabe.
- CVE-2020-11988: xmlgraphics-commons Schwachstelle in Zusammenhang mit HQL-Injection-Mustern.
Referenzen
- 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."