Ungeschützte Übertragung von Anmeldedaten
Beschreibung
Ungeschützte Übertragung von Anmeldedaten tritt auf, wenn Software Authentifizierungs-Anmeldedaten (Passwörter, Tokens, API-Schlüssel, Session-Identifikatoren) über einen unverschlüsselten Kanal überträgt. Dies geschieht häufig, wenn Anwendungen HTTP statt HTTPS verwenden, Anmeldedaten über unverschlüsselte Protokolle senden (FTP, Telnet, SMTP ohne STARTTLS) oder Anmeldedaten in URLs einbetten. Angreifer, die Netzwerkverkehr beobachten können, können diese Anmeldedaten abfangen und sich als legitime Benutzer ausgeben.
Risiko
Im Klartext übertragene Anmeldedaten werden trivial durch Netzwerk-Sniffing, Man-in-the-Middle-Angriffe oder kompromittierte Netzwerkinfrastruktur abgefangen. Dies ist besonders gefährlich in öffentlichen WiFi-Netzwerken, Shared-Hosting-Umgebungen oder Netzwerken mit kompromittierten Geräten. Sobald Anmeldedaten erfasst werden, erhalten Angreifer vollen Zugriff auf Benutzerkonten. Unverschlüsselt übertragene Session-Tokens ermöglichen Session-Hijacking. Diese Schwachstelle ermöglicht Kontenübernahme im großen Stil, wenn sie mit Traffic-Interception-Fähigkeiten kombiniert wird.
Lösung
Verwenden Sie immer TLS/HTTPS für die Übertragung von Anmeldedaten. Erzwingen Sie HTTPS durch HSTS-Header und leiten Sie alle HTTP zu HTTPS um. Fügen Sie niemals Anmeldedaten in URLs ein (sie erscheinen in Logs und Browser-Verlauf). Verwenden Sie sichere, httpOnly-Cookies für Session-Tokens. Implementieren Sie Certificate Pinning für mobile Anwendungen. Verwenden Sie verschlüsselte Protokolle für alle Dienste (SFTP, SSH, SMTPS). Konfigurieren Sie Server so, dass sie Klartext-Authentifizierungsversuche ablehnen. Überwachen Sie auf Mixed Content, der die Sicherheit herabstufen könnte.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Anmeldedatendiebstahl Angreifer erfassen Klartext-Anmeldedaten und ermöglichen Kontozugriff. |
| Authentifizierung | Bereich: Session-Hijacking Abgefangene Session-Tokens ermöglichen sofortige Kontenübernahme. |
| Integrität | Bereich: Kontenkompromittierung Gestohlene Anmeldedaten ermöglichen unbefugte Aktionen als das Opfer. |
Beispielcode
Verwundbarer Code
# VERWUNDBAR: HTTP für Authentifizierung
import requests
def login_vulnerable(username, password):
# Anmeldedaten über HTTP gesendet!
response = requests.post(
'http://api.example.com/login', # Nicht HTTPS!
data={'username': username, 'password': password}
)
return response.json()
# VERWUNDBAR: Anmeldedaten in URL
def api_call_vulnerable(api_key):
# API-Schlüssel in URL offengelegt (geloggt, gecacht, im Browser-Verlauf)!
response = requests.get(
f'http://api.example.com/data?api_key={api_key}'
)
return response.json()
# VERWUNDBAR: Basic Auth über HTTP
def fetch_data_vulnerable(username, password):
response = requests.get(
'http://api.example.com/data',
auth=(username, password) # Basic Auth über HTTP!
)
return response.json()
# VERWUNDBAR: FTP für Dateiübertragung
from ftplib import FTP
def upload_vulnerable(host, username, password, file):
# Unverschlüsseltes FTP!
ftp = FTP(host)
ftp.login(username, password) # Klartext-Anmeldedaten!
ftp.storbinary(f'STOR {file}', open(file, 'rb'))
ftp.quit()
# VERWUNDBAR: Unverschlüsselte E-Mail mit Anmeldedaten
import smtplib
def send_email_vulnerable(smtp_host, username, password, message):
# Klartext SMTP!
server = smtplib.SMTP(smtp_host, 25)
server.login(username, password) # Anmeldedaten im Klartext!
server.send_message(message)
server.quit()
// VERWUNDBAR: Java HTTP-Client
public class VulnerableClient {
public String login(String username, String password) throws Exception {
// HTTP-Verbindung - Anmeldedaten offengelegt!
URL url = new URL("http://api.example.com/login");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
String data = "username=" + URLEncoder.encode(username, "UTF-8") +
"&password=" + URLEncoder.encode(password, "UTF-8");
try (OutputStream os = conn.getOutputStream()) {
os.write(data.getBytes(StandardCharsets.UTF_8));
}
return readResponse(conn);
}
// VERWUNDBAR: Anmeldedaten in URL
public String fetchData(String apiKey) throws Exception {
// API-Schlüssel in URL - in Logs sichtbar!
URL url = new URL("http://api.example.com/data?api_key=" + apiKey);
return readUrl(url);
}
}
// VERWUNDBAR: Zertifikatvalidierung ignorieren
public class InsecureSSLClient {
public void connect() throws Exception {
// SSL-Verifizierung deaktiviert - MitM möglich!
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return null; }
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
}
}
// VERWUNDBAR: Spring-Endpunkt ohne HTTPS
@RestController
public class VulnerableController {
// Keine Transport-Sicherheit erzwungen
@PostMapping("/login")
public ResponseEntity<String> login(
@RequestParam String username,
@RequestParam String password) {
// Akzeptiert Anmeldedaten über HTTP!
return ResponseEntity.ok(authenticate(username, password));
}
}
// VERWUNDBAR: HTTP API-Aufrufe
async function loginVulnerable(username, password) {
// HTTP - Anmeldedaten offengelegt!
const response = await fetch('http://api.example.com/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
return response.json();
}
// VERWUNDBAR: Anmeldedaten in URL
function fetchDataVulnerable(apiKey) {
// API-Schlüssel in URL!
return fetch(`http://api.example.com/data?api_key=${apiKey}`)
.then(res => res.json());
}
// VERWUNDBAR: WebSocket ohne TLS
function connectWebSocketVulnerable(token) {
// Unverschlüsselter WebSocket!
const ws = new WebSocket(`ws://api.example.com/ws?token=${token}`);
return ws;
}
// VERWUNDBAR: Node.js mit deaktivierter SSL-Verifizierung
const https = require('https');
const agent = new https.Agent({
rejectUnauthorized: false // Deaktiviert Zertifikatvalidierung!
});
async function fetchInsecure(url) {
return fetch(url, { agent });
}
// VERWUNDBAR: Express ohne HTTPS-Weiterleitung
const express = require('express');
const app = express();
app.post('/login', (req, res) => {
// Keine HTTPS-Erzwingung!
const { username, password } = req.body;
// ...
});
// VERWUNDBAR: Cookie ohne Secure-Flag setzen
app.post('/login', (req, res) => {
const token = generateToken(req.body.username);
// Cookie ohne Secure-Flag - wird über HTTP gesendet!
res.cookie('session', token, { httpOnly: true });
res.json({ success: true });
});
Lösungscode
# SICHER: HTTPS für alle Anmeldedatenübertragung
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.ssl_ import create_urllib3_context
import ssl
def login_safe(username, password):
"""Immer HTTPS für Anmeldedaten verwenden."""
response = requests.post(
'https://api.example.com/login', # HTTPS!
data={'username': username, 'password': password},
verify=True # SSL-Zertifikat verifizieren
)
return response.json()
# SICHER: API-Schlüssel im Header, nicht URL
def api_call_safe(api_key):
"""Anmeldedaten im Header, über HTTPS."""
response = requests.get(
'https://api.example.com/data',
headers={'Authorization': f'Bearer {api_key}'} # Im Header!
)
return response.json()
# SICHER: Basic Auth über HTTPS
def fetch_data_safe(username, password):
response = requests.get(
'https://api.example.com/data', # HTTPS!
auth=(username, password)
)
return response.json()
# SICHER: SFTP statt FTP
import paramiko
def upload_safe(host, username, key_path, file):
"""SFTP mit Schlüssel-Authentifizierung verwenden."""
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.RejectPolicy())
# Known Hosts laden
ssh.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
# Schlüssel-Authentifizierung verwenden
private_key = paramiko.RSAKey.from_private_key_file(key_path)
ssh.connect(host, username=username, pkey=private_key)
sftp = ssh.open_sftp()
sftp.put(file, f'/uploads/{os.path.basename(file)}')
sftp.close()
ssh.close()
# SICHER: TLS für E-Mail
import smtplib
import ssl
def send_email_safe(smtp_host, username, password, message):
"""TLS für SMTP verwenden."""
context = ssl.create_default_context()
# Option 1: SMTP mit STARTTLS
with smtplib.SMTP(smtp_host, 587) as server:
server.starttls(context=context)
server.login(username, password)
server.send_message(message)
# Option 2: SMTPS (implizites TLS)
# with smtplib.SMTP_SSL(smtp_host, 465, context=context) as server:
# server.login(username, password)
# server.send_message(message)
# SICHER: Minimale TLS-Version erzwingen
import ssl
import urllib3
class SecureHTTPAdapter(HTTPAdapter):
def init_poolmanager(self, *args, **kwargs):
context = create_urllib3_context(ssl_version=ssl.TLSVersion.TLSv1_2)
kwargs['ssl_context'] = context
return super().init_poolmanager(*args, **kwargs)
session = requests.Session()
session.mount('https://', SecureHTTPAdapter())
// SICHER: Java HTTPS-Client
public class SecureClient {
public String login(String username, String password) throws Exception {
// HTTPS-Verbindung
URL url = new URL("https://api.example.com/login");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
// Optional TLS-Version erzwingen
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(null, null, new SecureRandom());
conn.setSSLSocketFactory(sslContext.getSocketFactory());
conn.setRequestMethod("POST");
conn.setDoOutput(true);
String data = "username=" + URLEncoder.encode(username, "UTF-8") +
"&password=" + URLEncoder.encode(password, "UTF-8");
try (OutputStream os = conn.getOutputStream()) {
os.write(data.getBytes(StandardCharsets.UTF_8));
}
return readResponse(conn);
}
// SICHER: API-Schlüssel im Header
public String fetchData(String apiKey) throws Exception {
URL url = new URL("https://api.example.com/data");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
// API-Schlüssel im Authorization-Header
conn.setRequestProperty("Authorization", "Bearer " + apiKey);
return readResponse(conn);
}
}
// SICHER: Certificate Pinning
public class CertificatePinningClient {
public void connect(String host) throws Exception {
// Erwarteter Zertifikat-Pin
String expectedPin = "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
URL url = new URL("https://" + host + "/api");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.connect();
// Zertifikat verifizieren
Certificate[] certs = conn.getServerCertificates();
X509Certificate cert = (X509Certificate) certs[0];
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] publicKeyHash = digest.digest(cert.getPublicKey().getEncoded());
String actualPin = "sha256/" + Base64.getEncoder().encodeToString(publicKeyHash);
if (!actualPin.equals(expectedPin)) {
throw new SSLException("Zertifikat-Pin stimmt nicht überein!");
}
}
}
// SICHER: Spring Security mit HTTPS-Erzwingung
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// HTTPS für alle Requests erforderlich
.requiresChannel(channel -> channel
.anyRequest().requiresSecure()
)
// HSTS-Header
.headers(headers -> headers
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.maxAgeInSeconds(31536000)
)
);
return http.build();
}
}
// SICHER: Sichere Cookie-Konfiguration
@Configuration
public class CookieConfig {
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setUseSecureCookie(true); // Nur über HTTPS
serializer.setUseHttpOnlyCookie(true); // Nicht über JS zugänglich
serializer.setSameSite("Strict"); // CSRF-Schutz
return serializer;
}
}
// SICHER: HTTPS für alle Requests
async function loginSafe(username, password) {
const response = await fetch('https://api.example.com/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
return response.json();
}
// SICHER: API-Schlüssel im Header
function fetchDataSafe(apiKey) {
return fetch('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${apiKey}`
}
}).then(res => res.json());
}
// SICHER: Sicherer WebSocket
function connectWebSocketSafe(token) {
// WSS = WebSocket Secure
const ws = new WebSocket('wss://api.example.com/ws');
ws.onopen = () => {
// Token nach Verbindungsaufbau senden
ws.send(JSON.stringify({ type: 'auth', token }));
};
return ws;
}
// SICHER: Express mit HTTPS-Erzwingung
const express = require('express');
const helmet = require('helmet');
const app = express();
// HTTPS in Produktion erzwingen
app.use((req, res, next) => {
if (process.env.NODE_ENV === 'production' && !req.secure) {
return res.redirect(301, `https://${req.headers.host}${req.url}`);
}
next();
});
// HSTS-Header
app.use(helmet.hsts({
maxAge: 31536000,
includeSubDomains: true,
preload: true
}));
// SICHER: Sichere Cookies
app.post('/login', (req, res) => {
const token = generateToken(req.body.username);
res.cookie('session', token, {
httpOnly: true, // Nicht über JavaScript zugänglich
secure: true, // Nur über HTTPS gesendet
sameSite: 'strict', // CSRF-Schutz
maxAge: 3600000 // 1 Stunde
});
res.json({ success: true });
});
// SICHER: Node.js HTTPS-Server
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.cert'),
minVersion: 'TLSv1.2'
};
https.createServer(options, app).listen(443);
// HTTP zu HTTPS weiterleiten
const http = require('http');
http.createServer((req, res) => {
res.writeHead(301, { Location: `https://${req.headers.host}${req.url}` });
res.end();
}).listen(80);
// SICHER: Certificate Pinning in Node.js
const https = require('https');
const crypto = require('crypto');
const EXPECTED_PIN = 'sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
function fetchWithPinning(url) {
return new Promise((resolve, reject) => {
const req = https.get(url, (res) => {
const cert = res.socket.getPeerCertificate();
const publicKey = cert.pubkey;
const hash = crypto
.createHash('sha256')
.update(publicKey)
.digest('base64');
if (`sha256/${hash}` !== EXPECTED_PIN) {
reject(new Error('Zertifikat-Pin stimmt nicht überein'));
return;
}
// Antwort verarbeiten...
resolve(res);
});
req.on('error', reject);
});
}
Ausgenutzt in der Praxis
Firesheep (2010)
Tool demonstrierte massenhaftes Session-Hijacking in unverschlüsselten WiFi-Netzwerken und erzwang weitverbreitete HTTPS-Einführung.
SSL-Stripping-Angriffe
Angreifer stufen HTTPS zu HTTP herab, um Anmeldedaten in öffentlichen Netzwerken abzufangen.
Regierungsüberwachung
Massenüberwachungsprogramme nutzten unverschlüsselte Anmeldedatenübertragung für Geheimdienstsammlung aus.
Tools zum Testen/Ausnutzen
-
Wireshark — Klartext-Anmeldedaten erfassen.
-
Bettercap — Netzwerkangriffe einschließlich SSL-Stripping.
-
SSLStrip — HTTPS zu HTTP herabstufen.
-
testssl.sh — TLS-Konfiguration testen.
CVE-Beispiele
-
CVE-2020-8945 — Anmeldedaten-Offenlegung über unverschlüsselte Verbindung.
-
CVE-2019-3462 — APT-Paketmanager HTTP-Schwachstelle.
-
Zahlreiche anwendungsspezifische Anmeldedaten-Offenlegungsprobleme.
Referenzen
-
MITRE. "CWE-523: Unprotected Transport of Credentials." https://cwe.mitre.org/data/definitions/523.html
-
OWASP. "Transport Layer Protection Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html