Übermäßige Anzahl ineffizienter serverseitiger Datenzugriffe
Beschreibung
Übermäßige Anzahl ineffizienter serverseitiger Datenzugriffe tritt auf, wenn Software zu viele Datenabfragen durchführt, ohne effiziente Datenverarbeitungsfunktionalität wie Stored Procedures, Batch-Operationen oder optimierte Abfragemuster zu verwenden. CISQ empfiehlt einen Standardschwellenwert von maximal 5 Datenabfragen für eine ineffiziente Funktion oder Prozedur. Wenn Code zahlreiche einzelne Datenbankaufrufe macht, wo weniger, effizientere Aufrufe ausreichen würden, erzeugt dies Leistungsengpässe und erhöht die Latenz. Dies ist allgemein als "N+1 Query Problem" oder "geschwätziger" Datenzugriff bekannt.
Risiko
Obwohl hauptsächlich ein Leistungsproblem, haben übermäßige Datenzugriffe Sicherheitsimplikationen. Angreifer können langsame Endpunkte für Denial-of-Service-Angriffe ausnutzen, indem sie Pfade mit vielen Datenbankabfragen auslösen. Datenbankverbindungspool-Erschöpfung kann unter Last auftreten. Erhöhte Latenz kann Timeouts verursachen, die zu inkonsistenten Zuständen führen. Die zusätzlichen Round-Trips vergrößern das Fenster für Race Conditions. Hohe Datenbanklast kann die gesamte Systemverfügbarkeit beeinträchtigen. Geschwätziger Datenzugriffsmuster erhöhen auch die Angriffsfläche für SQL-Injection, wenn Abfragen nicht ordnungsgemäß parametrisiert sind.
Lösung
Verwenden Sie Batch-Operationen anstelle von einzelnen Abfragen in Schleifen. Implementieren Sie Eager Loading oder JOIN-Abfragen, um verwandte Daten in einzelnen Abfragen abzurufen. Verwenden Sie Stored Procedures für komplexe mehrstufige Operationen. Implementieren Sie Query-Ergebnis-Caching. Verwenden Sie Paginierung für große Ergebnismengen. Setzen Sie ORM-Features wie Eager Loading, Batch-Fetching und Query-Optimierung ein. Setzen Sie Datenbankabfragezählungslimits und Warnungen. Verwenden Sie Query-Profiling, um N+1-Probleme zu identifizieren. Erwägen Sie Denormalisierung für leseintensive Operationen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Ressourcenverbrauch - Mehrere ineffiziente Abfragen verbrauchen Datenbankressourcen und können Verbindungspools erschöpfen. |
| Verfügbarkeit | Bereich: Verfügbarkeit Leistungsreduktion - Latenz erhöht sich signifikant mit jedem zusätzlichen Datenbank-Round-Trip. |
| Andere | Bereich: Ändere Qualitätsverschlechterung - Ineffiziente Datenzugriffsmuster machen das System schwerer zu skalieren und zu warten. |
Beispielcode
Anfälliger Code
// Anfällig: N+1 Query Problem - eine Abfrage pro Bestellposition
public class VulnerableOrderService {
public OrderDetails getOrderDetails(Long orderId) {
// Abfrage 1: Bestellung abrufen
Order order = orderRepository.findById(orderId);
// N Abfragen: Eine Abfrage pro Position für Produktdetails
List<ItemDetails> itemDetails = new ArrayList<>();
for (OrderItem item : order.getItems()) {
// Anfällig: Abfrage in einer Schleife!
Product product = productRepository.findById(item.getProductId());
// Weitere Abfrage pro Position für Bestand
Inventory inventory = inventoryRepository.findByProductId(item.getProductId());
// Weitere Abfrage pro Position für Preise
Pricing pricing = pricingRepository.findByProductId(item.getProductId());
itemDetails.add(new ItemDetails(item, product, inventory, pricing));
}
// Bei 100 Positionen: 1 + (100 * 3) = 301 Abfragen!
return new OrderDetails(order, itemDetails);
}
}
# Anfällig: Mehrere Abfragen in Schleife
class VulnerableUserService:
def get_users_with_details(self, user_ids):
users = []
for user_id in user_ids:
# Abfrage 1: Benutzer abrufen
user = self.db.query(User).get(user_id)
# Abfrage 2: Profil abrufen (pro Benutzer)
profile = self.db.query(Profile).filter(
Profile.user_id == user_id
).first()
# Abfrage 3: Rollen abrufen (pro Benutzer)
roles = self.db.query(Role).join(UserRole).filter(
UserRole.user_id == user_id
).all()
# Abfrage 4: Einstellungen abrufen (pro Benutzer)
preferences = self.db.query(Preference).filter(
Preference.user_id == user_id
).all()
users.append({
'user': user,
'profile': profile,
'roles': roles,
'preferences': preferences
})
# Bei 50 Benutzern: 50 * 4 = 200 Abfragen!
return users
Korrigierter Code
// Korrigiert: Batch-Abfragen und Eager Loading
public class FixedOrderService {
public OrderDetails getOrderDetails(Long orderId) {
// Einzelne Abfrage mit JOINs um alle verwandten Daten abzurufen
Order order = orderRepository.findByIdWithDetails(orderId);
// Alle Daten in einer Abfrage via JPQL mit JOIN FETCH geladen
return mapToOrderDetails(order);
}
// Batch-Fetch für mehrere Bestellungen verwenden
public List<OrderDetails> getMultipleOrderDetails(List<Long> orderIds) {
// Einzelne Abfrage für alle Bestellungen mit allen Beziehungen
List<Order> orders = orderRepository.findAllByIdWithDetails(orderIds);
return orders.stream().map(this::mapToOrderDetails).collect(toList());
}
}
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// Korrigiert: Alle Beziehungen in einzelner Abfrage eager laden
@Query("SELECT DISTINCT o FROM Order o " +
"JOIN FETCH o.items i " +
"JOIN FETCH i.product p " +
"JOIN FETCH p.inventory " +
"JOIN FETCH p.pricing " +
"WHERE o.id = :orderId")
Order findByIdWithDetails(@Param("orderId") Long orderId);
// Korrigiert: Batch-Fetch für mehrere Bestellungen
@Query("SELECT DISTINCT o FROM Order o " +
"JOIN FETCH o.items i " +
"JOIN FETCH i.product p " +
"WHERE o.id IN :orderIds")
List<Order> findAllByIdWithDetails(@Param("orderIds") List<Long> orderIds);
}
# Korrigiert: Eager Loading mit SQLAlchemy
from sqlalchemy.orm import joinedload, selectinload
class FixedUserService:
def get_users_with_details(self, user_ids):
# Korrigiert: Einzelne Abfrage mit Eager Loading
users = self.db.query(User)\
.options(
joinedload(User.profile), # Profil eager laden
selectinload(User.roles), # Rollen batch laden
selectinload(User.preferences) # Einstellungen batch laden
)\
.filter(User.id.in_(user_ids))\
.all()
# Gibt alle Daten mit nur 1-4 Abfragen insgesamt zurück
# (Hauptabfrage + eine für jedes selectinload)
return [{
'user': user,
'profile': user.profile,
'roles': user.roles,
'preferences': user.preferences
} for user in users]
# Alternative: Rohes SQL mit JOINs verwenden
class FixedUserServiceSQL:
def get_users_with_details(self, user_ids):
# Einzelne Abfrage mit JOINs
query = """
SELECT u.*, p.*, r.name as role_name
FROM users u
LEFT JOIN profiles p ON p.user_id = u.id
LEFT JOIN user_roles ur ON ur.user_id = u.id
LEFT JOIN roles r ON r.id = ur.role_id
WHERE u.id = ANY(:user_ids)
"""
results = self.db.execute(query, {'user_ids': user_ids}).fetchall()
return self._group_results(results)
// Korrigiert: Batch-Abfragen mit JOINs
async function fixedGetOrdersWithCustomers(orderIds) {
if (orderIds.length === 0) return [];
// Korrigiert: Einzelne Abfrage mit JOINs
const results = await db.query(`
SELECT
o.*,
c.id as customer_id, c.name as customer_name, c.email,
a.id as address_id, a.street, a.city, a.country
FROM orders o
JOIN customers c ON c.id = o.customer_id
LEFT JOIN addresses a ON a.id = o.shipping_address_id
WHERE o.id = ANY($1)
`, [orderIds]);
// Korrigiert: Positionen für alle Bestellungen auf einmal batch-fetchen
const allItems = await db.query(`
SELECT * FROM order_items
WHERE order_id = ANY($1)
`, [orderIds]);
// Positionen nach order_id in Anwendung gruppieren
const itemsByOrder = allItems.reduce((acc, item) => {
if (!acc[item.order_id]) acc[item.order_id] = [];
acc[item.order_id].push(item);
return acc;
}, {});
// Ergebnisse kombinieren
return results.map(row => ({
order: extractOrder(row),
customer: extractCustomer(row),
address: extractAddress(row),
items: itemsByOrder[row.id] || []
}));
// Jetzt nur 2 Abfragen insgesamt statt 400!
}
CVE-Beispiele
Diese CWE ist für direkte CVE-Zuordnung als VERBOTEN markiert, da sie ein Leistungs-/Qualitätsproblem und keine direkte Sicherheitsschwachstelle darstellt.
Verwandte CWEs
- CWE-1120: Excessive Code Complexity (Eltern)
- CWE-1226: Complexity Issues (Kategoriemitglied)
- CWE-400: Uncontrolled Resource Consumption (kann führen zu)
Referenzen
-
MITRE Corporation. "CWE-1060: Excessive Number of Inefficient Server-Side Data Accesses." https://cwe.mitre.org/data/definitions/1060.html
-
CISQ. "Automated Source Code Quality Measures."
-
Fowler, Martin. "OrmHate" - discussing N+1 query problems.