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
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Ressourcenverbrauch - Blockierte Threads verbrauchen Ressourcen und erschöpfen Thread-Pools. |
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Hänger - Anwendung wird nicht reaktionsfähig beim Warten auf entfernte Ressource. |
| Andere | Bereich: Ä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
-
MITRE Corporation. "CWE-1088: Synchronous Access of Remote Resource without Timeout." https://cwe.mitre.org/data/definitions/1088.html
-
OWASP. "Testing for Denial of Service."
-
Circuit Breaker Pattern. "Release It!" von Michael Nygard.