Hängender Datenbank-Cursor (Cursor-Injection)

Beschreibung

Hängender Datenbank-Cursor tritt auf, wenn ein Datenbank-Cursor nach seiner beabsichtigten Verwendung offen gelassen wird, oder wenn Cursor-Referenzen von Angreifern manipuliert werden können. Datenbank-Cursor sind Pointer auf Ergebnismengen, die einen Zustand beibehalten. Wenn Cursor-Namen vorhersehbar oder benutzerkontrolliert sind, können Angreifer möglicherweise auf Cursor zugreifen oder diese manipulieren, die von anderen Sitzungen erstellt wurden, was potenziell unbefugten Datenzugriff oder Störung von Datenbankoperationen ermöglicht.

Risiko

Vorhersehbare Cursor-Namen ermöglichen Angreifern den Zugriff auf Abfrageergebnisse anderer Benutzer. Hängende Cursor verbrauchen Datenbankressourcen und ermöglichen Denial of Service. In einigen Datenbanken kann Cursor-Injection zu Datenlecks über Sitzungen hinweg führen. Speichererschöpfung durch ungeschlossene Cursor beeinträchtigt die Datenbankleistung. Race Conditions mit Cursorn können Daten aus gleichzeitigen Transaktionen exponieren.

Lösung

Schließen Sie Datenbank-Cursor immer nach der Verwendung. Verwenden Sie eindeutige, unvorhersehbare Cursor-Namen, wenn Namen sichtbar sind. Implementieren Sie ordnungsgemäßes Cursor-Lifecycle-Management. Verwenden Sie parametrisierte Cursor-Namen, wenn dynamisch. Stellen Sie sicher, dass Cursor nach Möglichkeit transaktionsbezogen sind. Implementieren Sie Ressourcenlimits für die Cursor-Anzahl. Verwenden Sie Connection-Pooling mit ordnungsgemäßer Cursor-Bereinigung.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Datenleck

Zugriff auf Cursor anderer Sitzungen exponiert deren Daten.
VerfügbarkeitBereich: Ressourcenerschöpfung

Ungeschlossene Cursor verbrauchen Datenbankressourcen.
IntegritätBereich: Datenmanipulation

Modifikation des Cursor-Zustands beeinflusst Abfrageergebnisse.

Beispielcode + Lösungscode

Verwundbarer Code

// VERWUNDBAR: Cursor wird nie geschlossen
public class VulnerableDAO {

    public List<User> getAllUsers() throws SQLException {
        Connection conn = dataSource.getConnection();
        Statement stmt = conn.createStatement();

        // Cursor geöffnet aber nie geschlossen
        ResultSet rs = stmt.executeQuery("SELECT * FROM users");

        List<User> users = new ArrayList<>();
        while (rs.next()) {
            users.add(mapUser(rs));
        }

        // Fehlt: rs.close(), stmt.close(), conn.close()
        return users;
    }
}

// VERWUNDBAR: Cursor bleibt bei Ausnahme offen
public List<User> getUsersVulnerable() throws SQLException {
    Connection conn = dataSource.getConnection();
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("SELECT * FROM users");

    List<User> users = new ArrayList<>();
    while (rs.next()) {
        // Wenn hier Ausnahme, bleibt Cursor offen
        users.add(processUser(rs));  // Kann werfen
    }

    rs.close();
    stmt.close();
    conn.close();
    return users;
}

// VERWUNDBAR: Vorhersehbarer Cursor-Name (Stored Procedure)
public void processDataVulnerable(String userId) throws SQLException {
    CallableStatement cs = conn.prepareCall(
        "DECLARE cursor_" + userId + " CURSOR FOR " +
        "SELECT * FROM user_data WHERE user_id = ?"
    );

    // Angreifer kann Cursor-Namen erraten: cursor_12345
    // Kann in anderer Sitzung zugreifen
}
-- VERWUNDBAR: PL/SQL mit vorhersehbarem Cursor
CREATE OR REPLACE PROCEDURE get_user_data(p_user_id IN NUMBER) AS
    -- Vorhersehbarer Cursor-Name
    CURSOR user_cursor IS
        SELECT * FROM sensitive_data WHERE user_id = p_user_id;
BEGIN
    OPEN user_cursor;
    -- Cursor bleibt offen wenn Ausnahme auftritt
    FETCH user_cursor INTO v_record;

    -- Fehlt: CLOSE user_cursor im Exception-Handler
END;
/

-- VERWUNDBAR: Dynamischer Cursor mit Benutzereingabe
CREATE OR REPLACE PROCEDURE dynamic_query(p_cursor_name VARCHAR2) AS
    v_cursor INTEGER;
BEGIN
    -- Angreifer kontrolliert Cursor-Name
    v_cursor := DBMS_SQL.OPEN_CURSOR;
    -- ...
    -- Cursor wird möglicherweise nicht geschlossen
END;
/
# VERWUNDBAR: Python mit ungeschlossenen Cursorn
import psycopg2

def get_users_vulnerable():
    conn = psycopg2.connect(database_url)
    cursor = conn.cursor()  # Cursor geöffnet

    cursor.execute("SELECT * FROM users")
    users = cursor.fetchall()

    # Cursor nie geschlossen!
    # Verbindung nie geschlossen!
    return users

# VERWUNDBAR: Benannter Cursor mit vorhersehbarem Namen
def process_data_vulnerable(user_id):
    conn = psycopg2.connect(database_url)

    # Vorhersehbarer Cursor-Name
    cursor = conn.cursor(name=f"cursor_{user_id}")
    cursor.execute("SELECT * FROM sensitive_data")

    # Bei Ausnahme bleibt Cursor offen
    for row in cursor:
        process(row)

# VERWUNDBAR: Cursor-Akkumulation
class VulnerableBatchProcessor:
    def __init__(self):
        self.conn = psycopg2.connect(database_url)
        self.cursors = []

    def process_batch(self, batch_id):
        # Erstellt jeden Aufruf neuen Cursor, schließt nie
        cursor = self.conn.cursor(name=f"batch_{batch_id}")
        cursor.execute(f"SELECT * FROM batch_data WHERE id = {batch_id}")
        self.cursors.append(cursor)  # Akkumuliert für immer

Lösungscode

// SICHER: Ordnungsgemäßer Cursor-Lifecycle mit try-with-resources
public class SafeDAO {

    public List<User> getAllUsers() throws SQLException {
        List<User> users = new ArrayList<>();

        try (Connection conn = dataSource.getConnection();
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {

            while (rs.next()) {
                users.add(mapUser(rs));
            }
        }  // Alle Ressourcen automatisch geschlossen

        return users;
    }
}

// SICHER: Manuelle Ressourcenverwaltung mit finally
public List<User> getUsersSafe() throws SQLException {
    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;

    try {
        conn = dataSource.getConnection();
        stmt = conn.createStatement();
        rs = stmt.executeQuery("SELECT * FROM users");

        List<User> users = new ArrayList<>();
        while (rs.next()) {
            users.add(processUser(rs));
        }
        return users;

    } finally {
        // In umgekehrter Reihenfolge schließen, jede Ausnahme behandeln
        if (rs != null) try { rs.close(); } catch (SQLException e) { log(e); }
        if (stmt != null) try { stmt.close(); } catch (SQLException e) { log(e); }
        if (conn != null) try { conn.close(); } catch (SQLException e) { log(e); }
    }
}

// SICHER: Eindeutige Cursor-Namen
public void processDataSafe(String userId) throws SQLException {
    // Unvorhersehbaren Cursor-Namen generieren
    String cursorName = "cursor_" + UUID.randomUUID().toString().replace("-", "");

    try (CallableStatement cs = conn.prepareCall(
            "DECLARE " + cursorName + " CURSOR FOR " +
            "SELECT * FROM user_data WHERE user_id = ?")) {

        cs.setString(1, userId);
        cs.execute();
        // Ergebnisse verarbeiten
    }
    // Cursor automatisch geschlossen
}
-- SICHER: PL/SQL mit ordnungsgemäßem Cursor-Management
CREATE OR REPLACE PROCEDURE get_user_data(p_user_id IN NUMBER) AS
    CURSOR user_cursor IS
        SELECT * FROM sensitive_data WHERE user_id = p_user_id;
    v_record user_cursor%ROWTYPE;
BEGIN
    OPEN user_cursor;

    BEGIN
        LOOP
            FETCH user_cursor INTO v_record;
            EXIT WHEN user_cursor%NOTFOUND;
            process_record(v_record);
        END LOOP;
    EXCEPTION
        WHEN OTHERS THEN
            -- Cursor immer bei Fehler schließen
            IF user_cursor%ISOPEN THEN
                CLOSE user_cursor;
            END IF;
            RAISE;
    END;

    CLOSE user_cursor;
END;
/

-- SICHER: Verwendung von implizitem Cursor (automatisch geschlossen)
CREATE OR REPLACE PROCEDURE get_user_data_safe(p_user_id IN NUMBER) AS
BEGIN
    FOR rec IN (SELECT * FROM sensitive_data WHERE user_id = p_user_id)
    LOOP
        process_record(rec);
    END LOOP;
    -- Impliziter Cursor automatisch geschlossen
END;
/
# SICHER: Python mit Context-Managern
import psycopg2
from contextlib import contextmanager

@contextmanager
def get_cursor(conn):
    cursor = conn.cursor()
    try:
        yield cursor
    finally:
        cursor.close()

def get_users_safe():
    with psycopg2.connect(database_url) as conn:
        with conn.cursor() as cursor:
            cursor.execute("SELECT * FROM users")
            return cursor.fetchall()
    # Beide Cursor und Verbindung automatisch geschlossen

# SICHER: Benannter Cursor mit eindeutigem Namen und ordnungsgemäßer Bereinigung
import uuid

def process_data_safe(user_id):
    # Unvorhersehbarer Cursor-Name
    cursor_name = f"cursor_{uuid.uuid4().hex}"

    with psycopg2.connect(database_url) as conn:
        with conn.cursor(name=cursor_name) as cursor:
            cursor.execute(
                "SELECT * FROM sensitive_data WHERE user_id = %s",
                (user_id,)
            )
            for row in cursor:
                process(row)
    # Cursor beim Verlassen geschlossen

# SICHER: Batch-Prozessor mit Cursor-Bereinigung
class SafeBatchProcessor:
    def __init__(self):
        self.conn = psycopg2.connect(database_url)

    def process_batch(self, batch_id):
        cursor_name = f"batch_{uuid.uuid4().hex}"

        with self.conn.cursor(name=cursor_name) as cursor:
            cursor.execute(
                "SELECT * FROM batch_data WHERE id = %s",
                (batch_id,)
            )
            for row in cursor:
                self.process_row(row)
        # Cursor automatisch geschlossen

    def close(self):
        self.conn.close()

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.close()

# Verwendung
with SafeBatchProcessor() as processor:
    processor.process_batch(123)

Ausgenutzt in der Praxis

Datenbank-Ressourcenerschöpfung

Anwendungen mit Cursor-Lecks verursachten Datenbankausfälle unter Last.

Sitzungsübergreifender Datenzugriff

Vorhersehbare Cursor-Namen ermöglichten Zugriff auf Abfrageergebnisse anderer Sitzungen.

Speichererschöpfungs-Angriffe

Angreifer lösten Cursor-Erstellung ohne Schließung aus, um Speicher zu erschöpfen.


Tools zum Testen/Ausnutzen

  • Datenbank-Monitoring-Tools für Cursor-Lecks.
  • Lasttests um Cursor-Erschöpfung aufzudecken.
  • Benutzerdefinierte Skripte zum Testen der Cursor-Namen-Vorhersehbarkeit.

CVE-Beispiele

  • CVEs durch Datenbank-Cursor-Ressourcenerschöpfung.
  • Datenlecks durch Cursor-Manipulation.

Referenzen

  1. MITRE. "CWE-619: Dangling Database Cursor (Cursor Injection)." https://cwe.mitre.org/data/definitions/619.html
  2. Datenbank-Anbieter-Dokumentation zum Cursor-Management.