Synchroner Zugriff auf entfernte Ressource ohne Timeout

Beschreibung

Synchroner Zugriff auf entfernte Ressource ohne Timeout tritt auf, wenn Code einen synchronen Aufruf an eine entfernte Ressource macht, ohne einen Timeout-Mechanismus zu implementieren, oder den Timeout auf unendlich setzt. Wenn ein entfernter Server nicht reagiert, langsam ist oder nicht erreichbar ist, blockiert der aufrufende Thread unbegrenzt auf eine Antwort wartend. Dies kann Anwendungshänger, Thread-Pool-Erschöpfung und Denial-of-Service-Bedingungen verursachen.

Risiko

Fehlende Timeouts bei Remote-Aufrufen haben direkte Sicherheitsimplikationen. Angreifer können Denial-of-Service verursachen, indem sie Remote-Dienste nicht reagieren lassen. Thread-Pool-Erschöpfung verhindert, dass legitime Anfragen verarbeitet werden. Verbindungspool-Erschöpfung blockiert alle Remote-Konnektivität. Kaskadierende Ausfälle können ganze Systeme lahmlegen, wenn ein Dienst hängt. Angreifer können langsame oder hängende Endpunkte ausnutzen, um Serverressourcen zu verbrauchen. Das vorhersehbare Blockierverhalten ermöglicht Ressourcenerschöpfungsangriffe.

Lösung

Setzen Sie immer angemessene Timeouts auf allen Remote-Ressourcenzugriffen. Konfigurieren Sie sowohl Verbindungs-Timeout (Zeit zum Verbindungsaufbau) als auch Lese-Timeout (Zeit zum Empfangen der Antwort). Setzen Sie vernünftige Timeout-Werte basierend auf erwarteten Antwortzeiten und SLAs. Implementieren Sie Circuit Breaker, um wiederholte Aufrufe an ausfallende Dienste zu verhindern. Verwenden Sie wo möglich asynchrone I/O mit Timeouts. Implementieren Sie Wiederholungslogik mit exponentiellem Backoff. Überwachen Sie Timeout-Events, um Dienstprobleme zu erkennen. Konfigurieren Sie Timeouts auf mehreren Ebenen (Anwendung, Framework, OS). Testen Sie Timeout-Verhalten explizit.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitBereich: Verfügbarkeit

DoS: Ressourcenverbrauch - Blockierte Threads verbrauchen Ressourcen und erschöpfen Thread-Pools.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Hänger - Anwendung wird nicht reaktionsfähig beim Warten auf entfernte Ressource.
AndereBereich: Ändere

Reduzierte Zuverlässigkeit - Kaskadierende Ausfälle treten auf, wenn Dienste auf andere Dienste wartend hängen.

Beispielcode

Anfälliger Code

// Anfällig: HTTP-Anfrage ohne Timeout
public class VulnerableHttpClient {

    public String fetchData(String url) throws Exception {
        // Anfällig: Kein Timeout konfiguriert
        URL urlObj = new URL(url);
        HttpURLConnection conn = (HttpURLConnection) urlObj.openConnection();

        // Anfällig: Kein Connect- oder Read-Timeout gesetzt!
        // Wenn Server hängt, blockiert dieser Thread für immer

        conn.setRequestMethod("GET");

        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(conn.getInputStream()))) {

            StringBuilder response = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            return response.toString();
        }
    }

    // Anfällig: Socket ohne Timeout
    public String sendTcpRequest(String host, int port, String message)
            throws Exception {
        // Anfällig: Kein Verbindungs-Timeout
        Socket socket = new Socket(host, port);

        // Anfällig: Kein Lese-Timeout
        // socket.setSoTimeout(0) ist Standard - unendlich!

        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
        BufferedReader in = new BufferedReader(
            new InputStreamReader(socket.getInputStream()));

        out.println(message);
        return in.readLine();  // Blockiert für immer wenn keine Antwort
    }
}
# Anfällig: HTTP- und Socket-Anfragen ohne Timeout
import requests
import socket
import urllib.request

class VulnerableClient:

    def fetch_http(self, url):
        # Anfällig: Kein Timeout-Parameter
        response = requests.get(url)  # Kann für immer hängen
        return response.text

    def fetch_urllib(self, url):
        # Anfällig: Kein Timeout in urlopen
        with urllib.request.urlopen(url) as response:  # Hängt wenn keine Antwort
            return response.read()

    def connect_socket(self, host, port):
        # Anfällig: Kein Timeout auf Socket
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # Standard-Timeout ist None - blockiert für immer
        sock.connect((host, port))  # Hängt wenn Host nicht reagiert
        return sock

    def fetch_database(self, query):
        # Anfällig: Datenbankverbindung ohne Timeout
        import psycopg2
        conn = psycopg2.connect(
            host="db.example.com",
            database="mydb",
            user="user",
            password="pass"
            # Kein connect_timeout angegeben!
        )
        cursor = conn.cursor()
        cursor.execute(query)  # Hängt wenn Abfrage ewig dauert
        return cursor.fetchall()
// Anfällig: .NET HTTP-Client ohne Timeout
public class VulnerableWebClient
{
    public async Task<string> FetchDataAsync(string url)
    {
        // Anfällig: HttpClient mit unendlichem Timeout
        using var client = new HttpClient();
        client.Timeout = Timeout.InfiniteTimeSpan;  // Explizit unendlich!

        var response = await client.GetAsync(url);
        return await response.Content.ReadAsStringAsync();
    }

    public string FetchDataSync(string url)
    {
        // Anfällig: WebClient hat standardmäßig keinen Timeout
        using var client = new WebClient();
        return client.DownloadString(url);  // Blockiert für immer
    }

    public string ConnectTcp(string host, int port)
    {
        // Anfällig: TcpClient ohne Timeout
        using var client = new TcpClient();
        client.Connect(host, port);  // Kein Timeout - hängt für immer

        using var stream = client.GetStream();
        using var reader = new StreamReader(stream);
        return reader.ReadLine();  // Kein Lese-Timeout
    }
}
// Anfällig: Node.js HTTP ohne Timeout
const http = require('http');
const net = require('net');

class VulnerableClient {

    fetchData(url) {
        return new Promise((resolve, reject) => {
            // Anfällig: Kein Timeout konfiguriert
            http.get(url, (res) => {
                let data = '';
                res.on('data', chunk => data += chunk);
                res.on('end', () => resolve(data));
            }).on('error', reject);
            // Anfrage kann für immer hängen!
        });
    }

    connectSocket(host, port) {
        return new Promise((resolve, reject) => {
            // Anfällig: Socket ohne Timeout
            const socket = net.connect({ host, port }, () => {
                resolve(socket);
            });
            // Kein Timeout gesetzt - Verbindungsversuch hängt für immer
        });
    }

    async fetchWithAxios(url) {
        const axios = require('axios');
        // Anfällig: Kein Timeout in Config
        const response = await axios.get(url);
        return response.data;
    }
}

Korrigierter Code

// Korrigiert: HTTP- und Socket-Anfragen mit ordnungsgemäßen Timeouts
public class FixedHttpClient {

    private static final int CONNECT_TIMEOUT_MS = 5000;  // 5 Sekunden
    private static final int READ_TIMEOUT_MS = 30000;    // 30 Sekunden

    public String fetchData(String url) throws Exception {
        URL urlObj = new URL(url);
        HttpURLConnection conn = (HttpURLConnection) urlObj.openConnection();

        // Korrigiert: Verbindungs- und Lese-Timeouts setzen
        conn.setConnectTimeout(CONNECT_TIMEOUT_MS);
        conn.setReadTimeout(READ_TIMEOUT_MS);

        conn.setRequestMethod("GET");

        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(conn.getInputStream()))) {

            StringBuilder response = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            return response.toString();
        }
    }

    // Korrigiert: Socket mit ordnungsgemäßen Timeouts
    public String sendTcpRequest(String host, int port, String message)
            throws Exception {

        // Korrigiert: Verbindung mit Timeout
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress(host, port), CONNECT_TIMEOUT_MS);

        // Korrigiert: Lese-Timeout
        socket.setSoTimeout(READ_TIMEOUT_MS);

        try {
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            BufferedReader in = new BufferedReader(
                new InputStreamReader(socket.getInputStream()));

            out.println(message);
            return in.readLine();  // Wirft SocketTimeoutException bei Timeout
        } finally {
            socket.close();
        }
    }

    // Korrigiert: OkHttp mit Timeouts verwenden
    public String fetchWithOkHttp(String url) throws Exception {
        OkHttpClient client = new OkHttpClient.Builder()
            .connectTimeout(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
            .readTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS)
            .writeTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS)
            .build();

        Request request = new Request.Builder()
            .url(url)
            .build();

        try (Response response = client.newCall(request).execute()) {
            return response.body().string();
        }
    }
}
# Korrigiert: Python Requests mit ordnungsgemäßen Timeouts
import requests
import socket
import urllib.request

class FixedClient:

    CONNECT_TIMEOUT = 5    # 5 Sekunden
    READ_TIMEOUT = 30      # 30 Sekunden
    TOTAL_TIMEOUT = (CONNECT_TIMEOUT, READ_TIMEOUT)

    def fetch_http(self, url):
        # Korrigiert: Tuple-Timeout (Connect, Read)
        response = requests.get(url, timeout=self.TOTAL_TIMEOUT)
        return response.text

    def fetch_urllib(self, url):
        # Korrigiert: Timeout-Parameter
        with urllib.request.urlopen(url, timeout=self.READ_TIMEOUT) as response:
            return response.read()

    def connect_socket(self, host, port):
        # Korrigiert: Socket mit Timeout
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(self.CONNECT_TIMEOUT)
        sock.connect((host, port))
        sock.settimeout(self.READ_TIMEOUT)  # Lese-Timeout
        return sock

    def fetch_database(self, query):
        import psycopg2
        # Korrigiert: Verbindungs-Timeout und Statement-Timeout
        conn = psycopg2.connect(
            host="db.example.com",
            database="mydb",
            user="user",
            password="pass",
            connect_timeout=self.CONNECT_TIMEOUT,
            options=f"-c statement_timeout={self.READ_TIMEOUT * 1000}"
        )
        cursor = conn.cursor()
        cursor.execute(query)
        return cursor.fetchall()

    # Korrigiert: Async mit Timeout
    async def fetch_async(self, url):
        import aiohttp
        import asyncio

        timeout = aiohttp.ClientTimeout(
            total=self.READ_TIMEOUT,
            connect=self.CONNECT_TIMEOUT
        )

        async with aiohttp.ClientSession(timeout=timeout) as session:
            async with session.get(url) as response:
                return await response.text()
// Korrigiert: .NET HTTP-Client mit ordnungsgemäßen Timeouts
public class FixedWebClient
{
    private static readonly TimeSpan ConnectTimeout = TimeSpan.FromSeconds(5);
    private static readonly TimeSpan ReadTimeout = TimeSpan.FromSeconds(30);

    // Korrigiert: Wiederverwendbarer HttpClient mit Timeout
    private static readonly HttpClient _httpClient = new HttpClient
    {
        Timeout = ReadTimeout
    };

    public async Task<string> FetchDataAsync(string url,
        CancellationToken cancellationToken = default)
    {
        // Korrigiert: HttpClient mit Timeout und Abbruch
        using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        cts.CancelAfter(ReadTimeout);

        var response = await _httpClient.GetAsync(url, cts.Token);
        return await response.Content.ReadAsStringAsync();
    }

    public async Task<string> ConnectTcpAsync(string host, int port)
    {
        using var client = new TcpClient();

        // Korrigiert: Verbindung mit Timeout via Task.WhenAny
        var connectTask = client.ConnectAsync(host, port);
        var timeoutTask = Task.Delay(ConnectTimeout);

        var completedTask = await Task.WhenAny(connectTask, timeoutTask);
        if (completedTask == timeoutTask)
        {
            throw new TimeoutException($"Verbindung zu {host}:{port} Timeout");
        }

        await connectTask;  // Ausnahmen weiterleiten

        // Korrigiert: Lesen mit Timeout
        using var stream = client.GetStream();
        stream.ReadTimeout = (int)ReadTimeout.TotalMilliseconds;

        using var reader = new StreamReader(stream);
        return await reader.ReadLineAsync();
    }
}
// Korrigiert: Node.js HTTP mit Timeouts
const http = require('http');
const https = require('https');
const net = require('net');
const axios = require('axios');

class FixedClient {

    constructor() {
        this.connectTimeout = 5000;   // 5 Sekunden
        this.readTimeout = 30000;     // 30 Sekunden
    }

    fetchData(url) {
        return new Promise((resolve, reject) => {
            const request = http.get(url, (res) => {
                let data = '';
                res.on('data', chunk => data += chunk);
                res.on('end', () => resolve(data));
            });

            // Korrigiert: Verbindungs-Timeout
            request.setTimeout(this.connectTimeout, () => {
                request.destroy();
                reject(new Error('Verbindungs-Timeout'));
            });

            request.on('error', reject);
        });
    }

    connectSocket(host, port) {
        return new Promise((resolve, reject) => {
            const socket = net.connect({ host, port });

            // Korrigiert: Verbindungs-Timeout
            socket.setTimeout(this.connectTimeout);

            socket.on('connect', () => {
                // Korrigiert: Auf Lese-Timeout wechseln
                socket.setTimeout(this.readTimeout);
                resolve(socket);
            });

            socket.on('timeout', () => {
                socket.destroy();
                reject(new Error('Socket-Timeout'));
            });

            socket.on('error', reject);
        });
    }

    async fetchWithAxios(url) {
        // Korrigiert: Axios mit Timeout
        const response = await axios.get(url, {
            timeout: this.readTimeout,
            // Optional: AbortController für Abbruch
            signal: AbortSignal.timeout(this.readTimeout)
        });
        return response.data;
    }
}

CVE-Beispiele

Fehlende Timeout-Schwachstellen haben Denial-of-Service-Bedingungen in vielen Anwendungen verursacht, obwohl spezifische CVEs typischerweise die resultierende Auswirkung beschreiben anstatt spezifisch fehlende Timeouts zu zitieren.


Verwandte CWEs

  • CWE-821: Incorrect Synchronization (Eltern)
  • CWE-400: Uncontrolled Resource Consumption (kann führen zu)
  • CWE-835: Loop with Unreachable Exit Condition (ähnliches Konzept)

Referenzen

  1. MITRE Corporation. "CWE-1088: Synchronous Access of Remote Resource without Timeout." https://cwe.mitre.org/data/definitions/1088.html

  2. OWASP. "Testing for Denial of Service."

  3. Circuit Breaker Pattern. "Release It!" von Michael Nygard.