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

AuswirkungDetails
VertraulichkeitBereich: Anmeldedatendiebstahl

Angreifer erfassen Klartext-Anmeldedaten und ermöglichen Kontozugriff.
AuthentifizierungBereich: Session-Hijacking

Abgefangene Session-Tokens ermöglichen sofortige Kontenübernahme.
IntegritätBereich: 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

  1. MITRE. "CWE-523: Unprotected Transport of Credentials." https://cwe.mitre.org/data/definitions/523.html

  2. OWASP. "Transport Layer Protection Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html