Datenressourcenzugriff ohne Verwendung von Connection Pooling
Beschreibung
Datenressourcenzugriff ohne Verwendung von Connection Pooling tritt auf, wenn ein Produkt auf eine Datenressource über eine Datenbank zugreift, ohne Connection-Pooling-Fähigkeit zu implementieren. Das Erstellen einer neuen Datenbankverbindung für jede Anfrage verursacht erheblichen Overhead: TCP-Handshake, Authentifizierung, Sitzungsinitialisierung und Ressourcenallokation. Verbindungspools halten einen Cache wiederverwendbarer Verbindungen und reduzieren diesen Overhead dramatisch. Ohne Pooling verschwenden Anwendungen Ressourcen durch wiederholtes Erstellen und Zerstoren von Verbindungen, was zu Leistungsverschlechterung und potentieller Ressourcenerschöpfung führt.
Risiko
Obwohl hauptsächlich ein Leistungsproblem, hat fehlendes Connection Pooling Sicherheitsimplikationen. Ressourcenerschöpfung durch Erstellen zu vieler Verbindungen kann zu Denial-of-Service führen. Verbindungslimits können erreicht werden und legitime Benutzer am Systemzugriff hindern. Der Overhead der Verbindungserstellung erhöht die Latenz und macht das System anfälliger für Slowloris-artige Angriffe. Unter Last können Verbindungserstellungsfehler Fehlerpfade auslösen, die Informationen leaken oder unsicher fehlschlagen. Aufgegebene Verbindungen aufgrund von Fehlern können Verbindungslecks verursachen und das Verbindungslimit allmählich erschöpfen.
Lösung
Verwenden Sie Connection-Pooling-Bibliotheken, die für Ihre Plattform geeignet sind (HikariCP, c3p0 für Java; pg-pool für Node.js; SQLAlchemy-Pool für Python). Konfigurieren Sie angemessene Poolgrößen basierend auf erwarteter Last. Implementieren Sie Verbindungsvalidierung, um abgestandene Verbindungen zu erkennen. Setzen Sie ordnungsgemäße Verbindungs-Timeouts. Verwenden Sie Connection-Pool-Monitoring, um Lecks und Probleme zu erkennen. Erwägen Sie containerverwaltete Verbindungspools in Application Servern. Konfigurieren Sie maximales Verbindungsalter, um Verbindungsveralterung zu verhindern. Implementieren Sie ordnungsgemäße Verbindungsbereinigung in Fehlerbehandlern.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Ressourcenverbrauch - Wiederholte Verbindungserstellung/-zerstörung verschwendet Ressourcen und kann Limits erschöpfen. |
| Verfügbarkeit | Bereich: Verfügbarkeit Leistungsreduktion - Verbindungserstellungs-Overhead verschlechtert Durchsatz und Latenz signifikant. |
| Andere | Bereich: Ändere Qualitätsverschlechterung - Anwendungsskalierbarkeit ist ohne Connection Pooling stark eingeschränkt. |
Beispielcode
Anfälliger Code
// Anfällig: Neue Verbindung für jede Anfrage erstellen
public class VulnerableUserRepository {
private static final String URL = "jdbc:mysql://localhost:3306/users";
private static final String USER = "app_user";
private static final String PASSWORD = "secret";
// Anfällig: Neue Verbindung für jeden Methodenaufruf erstellt
public User findById(Long id) {
Connection conn = null;
try {
// Anfällig: Erstellt jedes Mal neue Verbindung
conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM users WHERE id = ?"
);
stmt.setLong(1, id);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return mapUser(rs);
}
return null;
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
// Verbindung wird geschlossen, nicht an Pool zurückgegeben
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// Verbindungsleck wenn close fehlschlägt
}
}
}
}
// Gleiches Muster für jede Methode wiederholt - sehr ineffizient!
public List<User> findAll() {
Connection conn = null;
try {
conn = DriverManager.getConnection(URL, USER, PASSWORD);
// ...
} finally {
if (conn != null) {
try { conn.close(); } catch (SQLException e) {}
}
}
return null;
}
}
# Anfällig: Neue Verbindung pro Anfrage erstellen
import psycopg2
class VulnerableOrderService:
def __init__(self):
self.db_config = {
'host': 'localhost',
'database': 'orders',
'user': 'app_user',
'password': 'secret'
}
def get_order(self, order_id):
# Anfällig: Neue Verbindung für jeden Aufruf
conn = psycopg2.connect(**self.db_config)
try:
cursor = conn.cursor()
cursor.execute(
"SELECT * FROM orders WHERE id = %s",
(order_id,)
)
return cursor.fetchone()
finally:
conn.close() # Verbindung zerstört, nicht gepoolt
def get_all_orders(self, customer_id):
# Anfällig: Weitere neue Verbindung
conn = psycopg2.connect(**self.db_config)
try:
cursor = conn.cursor()
cursor.execute(
"SELECT * FROM orders WHERE customer_id = %s",
(customer_id,)
)
return cursor.fetchall()
finally:
conn.close()
Korrigierter Code
// Korrigiert: Verwendung von HikariCP Connection Pool
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
public class FixedUserRepository {
private static final HikariDataSource dataSource;
static {
// Korrigiert: Verbindungspool einmal konfigurieren
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/users");
config.setUsername("app_user");
config.setPassword("secret");
// Pool-Konfiguration
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setIdleTimeout(300000); // 5 Minuten
config.setConnectionTimeout(10000); // 10 Sekunden
config.setMaxLifetime(1800000); // 30 Minuten
// Verbindungsvalidierung
config.setConnectionTestQuery("SELECT 1");
config.setValidationTimeout(5000);
dataSource = new HikariDataSource(config);
}
// Korrigiert: Verbindung aus Pool holen, automatisch zurück via try-with-resources
public User findById(Long id) {
// Verbindung aus Pool geliehen, bei Schließen zurückgegeben
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM users WHERE id = ?")) {
stmt.setLong(1, id);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return mapUser(rs);
}
}
return null;
} catch (SQLException e) {
throw new RepositoryException("Benutzer nicht gefunden: " + id, e);
}
}
public List<User> findAll() {
// Korrigiert: Verwendet gepoolte Verbindung wieder
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
List<User> users = new ArrayList<>();
while (rs.next()) {
users.add(mapUser(rs));
}
return users;
} catch (SQLException e) {
throw new RepositoryException("Benutzer könnten nicht abgerufen werden", e);
}
}
// Pool herunterfahren wenn Anwendung terminiert
public static void shutdown() {
if (dataSource != null && !dataSource.isClosed()) {
dataSource.close();
}
}
}
# Korrigiert: Verwendung von SQLAlchemy mit Connection Pooling
from sqlalchemy import create_engine, text
from sqlalchemy.pool import QueuePool
from contextlib import contextmanager
class FixedOrderService:
def __init__(self):
# Korrigiert: Engine verwaltet Verbindungspool
self.engine = create_engine(
'postgresql://app_user:secret@localhost/orders',
poolclass=QueuePool,
pool_size=10, # Anzahl persistenter Verbindungen
max_overflow=20, # Extra-Verbindungen wenn Pool erschöpft
pool_timeout=30, # Sekunden auf Verbindung warten
pool_recycle=1800, # Verbindungen nach 30 Min recyceln
pool_pre_ping=True # Verbindung vor Verwendung verifizieren
)
@contextmanager
def get_connection(self):
"""Context Manager für Verbindungsbehandlung"""
conn = self.engine.connect()
try:
yield conn
finally:
conn.close() # Gibt an Pool zurück, zerstört nicht
def get_order(self, order_id):
# Korrigiert: Verbindung aus Pool
with self.get_connection() as conn:
result = conn.execute(
text("SELECT * FROM orders WHERE id = :id"),
{"id": order_id}
)
return result.fetchone()
def get_all_orders(self, customer_id):
# Korrigiert: Verwendet gepoolte Verbindung wieder
with self.get_connection() as conn:
result = conn.execute(
text("SELECT * FROM orders WHERE customer_id = :id"),
{"id": customer_id}
)
return result.fetchall()
def create_order(self, order_data):
# Korrigiert: Transaktion mit gepoolter Verbindung
with self.engine.begin() as conn: # Auto-Commit/Rollback
conn.execute(
text("INSERT INTO orders (customer_id, total) VALUES (:cid, :total)"),
{"cid": order_data['customer_id'], "total": order_data['total']}
)
// Korrigiert: Verwendung von Connection Pooling in C#
public class FixedCustomerRepository
{
// Korrigiert: Connection-String mit aktiviertem Pooling (Standard)
private readonly string _connectionString =
"Server=localhost;Database=customers;User Id=app;Password=secret;" +
"Min Pool Size=5;Max Pool Size=100;Connection Timeout=30;";
public Customer GetById(int id)
{
// Korrigiert: Verbindung verwendet automatisch Pool
// 'using'-Statement gibt Verbindung an Pool zurück
using (var conn = new SqlConnection(_connectionString))
{
conn.Open(); // Holt Verbindung aus Pool
using (var cmd = new SqlCommand(
"SELECT * FROM Customers WHERE Id = @id", conn))
{
cmd.Parameters.AddWithValue("@id", id);
using (var reader = cmd.ExecuteReader())
{
if (reader.Read())
{
return MapCustomer(reader);
}
}
}
} // Verbindung hier an Pool zurückgegeben
return null;
}
// Korrigiert: Async mit Pooling
public async Task<Customer> GetByIdAsync(int id)
{
await using var conn = new SqlConnection(_connectionString);
await conn.OpenAsync();
await using var cmd = new SqlCommand(
"SELECT * FROM Customers WHERE Id = @id", conn);
cmd.Parameters.AddWithValue("@id", id);
await using var reader = await cmd.ExecuteReaderAsync();
if (await reader.ReadAsync())
{
return MapCustomer(reader);
}
return null;
}
}
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-405: Asymmetric Resource Consumption (Eltern)
- CWE-400: Uncontrolled Resource Consumption (kann führen zu)
- CWE-410: Insufficient Resource Pool (verwandt)
Referenzen
-
MITRE Corporation. "CWE-1072: Data Resource Access without Use of Connection Pooling." https://cwe.mitre.org/data/definitions/1072.html
-
HikariCP. "Fast, Simple, Reliable Connection Pool." https://github.com/brettwooldridge/HikariCP
-
Microsoft. "SQL Server Connection Pooling." https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql-server-connection-pooling