Einleitung: Wenn KI auf Cybersicherheit trifft
Wenn du das hier liest, fragst du dich vielleicht: Können wir einem Computer wirklich das Hacken beibringen? Natürlich nicht im böswilligen Sinne, sondern so, wie ethische Hacker arbeiten, um Systeme sicherer zu machen. Genau das versucht dieses Projekt, und ich will ehrlich sein – es ist faszinierend und am Anfang auch etwas überwältigend.
Das Projekt, das wir uns ansehen, verwendet etwas, das sich Proximal Policy Optimization (PPO) nennt, um Sprachmodelle darauf zu trainieren, Penetrationstests an einer verwundbaren Webanwendung namens OWASP Juice Shop durchzuführen (Link: https://owasp.org/www-project-juice-shop/). Keine Sorge, falls diese Begriffe kompliziert klingen – wir gehen alles Schritt für Schritt gemeinsam durch.
Was passiert hier eigentlich?
Das große Ganze (einfach erklärt)
Stell dir vor, du bringst einem Schüler bei, ein Cybersicherheitsexperte zu werden. Du würdest:
- Ihm verwundbare Systeme zeigen
- Erklären, welche Angriffe er versuchen soll
- Ihm Feedback geben, wenn er Erfolg hat oder scheitert
- Ihn üben lassen, bis er besser wird
Genau das macht dieses Projekt im Grunde auch, nur eben mit einer KI:
🤖 KI-Modell (Qwen) ← Schüler
🕸️ Juice Shop ← Übungslabor
🎯 PPO-Algorithmus ← Lehrmethode
📊 Belohnungen ← Noten/Feedback

Das Kernkonzept von Proximal Policy Optimization im Cybersicherheits-Training
Der Datensatz: Echte Angriffe, echte Ergebnisse
Das Herzstück dieses Systems ist ein Datensatz mit 240 echten Penetrationstest-Versuchen gegen den Juice Shop. Jeder Eintrag sieht (vereinfacht) so aus:
{
"state": {
"user_id": 51,
"auth": "Yes",
"headers": {"Authorization": "Bearer token..."}
},
"action": {
"description": "SQL-Injection – Benutzerauflistung",
"difficulty": 2
},
"reward": 16,
"success": true
}
Was mich beim ersten Blick auf die Daten beeindruckt hat, ist, wie menschlich sie sich anfühlen. Jeder Datensatz steht für einen echten Moment, in dem jemand versucht hat, eine Schwachstelle zu finden – manchmal erfolgreich, manchmal nicht. Der Datensatz hat eine Erfolgsquote von 2,9 % mit einer durchschnittlichen Belohnung von 18,7 Punkten pro Versuch. Das spiegelt die Realität von Penetrationstests wider, bei denen die meisten Versuche scheitern, die Erfolge aber umso wertvoller sind.
Die technische Architektur: Eine Aufschlüsselung
1. Der Agent (JuiceShopAgent)
Das sind quasi die „Hände“ unseres Systems – dieser Teil interagiert tatsächlich mit der verwundbaren Webanwendung:
- Registriert neue Benutzer für jede Testsitzung
- Führt über 25 verschiedene Angriffsarten aus (SQL-Injection, XSS, Directory Traversal usw.)
- Erfasst den Zustand der Anwendung vor und nach jedem Angriff
- Berechnet Belohnungen basierend auf Erfolg und Schwere der Schwachstelle
Der Agent kann anspruchsvolle Angriffe durchführen wie:
UNION SELECT
SQL-Injections auf Such-Endpunkte- Admin-Login-Umgehungen mit
admin@juice-sh.op'--
- Directory Traversal mit kodierten Pfaden wie
%25252e%25252e%25252f
- Ausnutzung von Geschäftslogik (Bestellungen mit negativer Menge)
2. Das Gehirn (PPO-Training)
Hier wird es richtig interessant. Das System verwendet Proximal Policy Optimization, eine Art des bestärkenden Lernens (Reinforcement Learning). Stell es dir wie eine behutsame Methode vor, der KI etwas beizubringen:
Traditionelles Training: „Hier ist die richtige Antwort, lerne sie auswendig.“
PPO-Training: „Probiere Dinge aus, erhalte Feedback und verbessere dich schrittweise.“
Der PPO-Algorithmus ist besonders gut, weil er:
- Keine drastischen Änderungen vornimmt, die den Lernprozess stören könnten
- Ein Gleichgewicht zwischen Erkundung und Nutzung findet (neue Dinge ausprobieren vs. das anwenden, was funktioniert)
- Wertfunktionen nutzt, um den langfristigen Erfolg vorherzusagen

Wie der PPO-Agent von Belohnungen in der Cybersicherheitsumgebung lernt
3. Das Modell (Qwen-Sprachmodelle)
Das Projekt unterstützt mehrere Qwen-Modelle:
- Qwen2.5-1.5B-Instruct: 6-8 GB VRAM, gut zum Experimentieren
- Qwen2.5-3B-Instruct: 10-12 GB VRAM, bessere Qualität
- Qwen2.5-7B-Instruct: 16-20 GB VRAM, höchste Qualität
Jedes Modell kann auf zwei Arten trainiert werden:
- Volles Fine-Tuning: Aktualisiert alle Parameter des Modells (bessere Genauigkeit, mehr Speicherbedarf)
- LoRA (Low-Rank Adaptation): Aktualisiert nur kleine Adapter-Schichten (schneller, weniger Speicherbedarf)
Der Lernprozess: Wie es wirklich funktioniert
Reward Engineering: Das Herzstück des Lernens
Wir haben ein einfaches Belohnungssystem implementiert:
def calculate_smart_reward(challenges_before, challenges_after,
status_code, response_text, difficulty):
# 'calculate_smart_reward' berechnet eine intelligente Belohnung
reward = 0
# Große Belohnungen für das tatsächliche Lösen von Herausforderungen
new_solved = challenges_after - challenges_before
if new_solved:
reward = len(new_solved) * difficulty * 20
# Kleinere Belohnungen für vielversprechende Versuche
if status_code == 200: reward += 10
if 'admin' in response_text.lower(): reward += 8
if 'sql' in response_text.lower(): reward += 6
return reward
Das bedeutet, die KI wird belohnt für:
- Das tatsächliche Lösen von Herausforderungen (große Belohnungen)
- Das Erhalten interessanter Antworten (mittlere Belohnungen)
- Das Durchführen vernünftiger Versuche (kleine Belohnungen)
Trainingsvarianten: Verschiedene Ansätze
Das Projekt umfasst mehrere Trainingsstrategien:
train_ppo.py: Der Standardansatz
- Standard-PPO mit guten Standardeinstellungen
- Funktioniert für die meisten Anwendungsfälle
- 5 Epochen, ausgewogene Parameter
train_stable.py: Der vorsichtige Ansatz
- Konservative Lernraten
- Zusätzliche Stabilitätsprüfungen
- Gradient-Clipping und Volatilitätsüberwachung
- Am besten für ein konsistentes, zuverlässiges Training
train_long.py: Der gründliche Ansatz
- 10+ Epochen mit Anpassung der Lernrate
- Frühes Stoppen bei Erreichen des Ziels
- Umfassendes Speichern von Checkpoints
- Am besten für Modelle in Produktionsqualität
Die Ergebnisse: Was wird tatsächlich gelernt?
Nach dem Training zeigen die Modelle interessante Verhaltensänderungen:
Vor dem Training (Allgemeine KI-Antwort):
Anfrage: „Nächste Aktion für den Penetrationstest?“
Antwort: „Ich kann Ihnen mit allgemeinen Informationen zur Cybersicherheit helfen ...“
Nach dem Training (Gezieltes Penetration-Testing):
Anfrage: „Benutzer 51, Auth: Ja, Vorher: SQL-Injection. Nächste Aktion?“
Antwort: „Versuche einen XSS-Angriff auf den Suchparameter oder prüfe Admin-Endpunkte.“
Das Modell lernt:
- Muster von Schwachstellen im Anwendungszustand zu erkennen
- Spezifische technische Angriffe anstelle von allgemeinen Ratschlägen vorzuschlagen
- Angriffe logisch zu verketten (z. B. nach einer SQL-Injection eine Rechteausweitung versuchen)
- Sich auf hochwertige Ziele zu konzentrieren (Admin-Endpunkte, sensible Daten)
Herausforderungen und Grenzen
Was gut funktioniert
- Konsistentes Lernen: Die Modelle verbessern sich zuverlässig über die Trainingsepochen hinweg
- Technische Genauigkeit: Die gelernten Angriffe sind valide Penetrationstest-Techniken
- Kontextbewusstsein: Die Modelle berücksichtigen den Zustand der Anwendung bei ihren Vorschlägen
Was noch schwierig ist
- Niedrige Erfolgsquote: Selbst trainierte Modelle lösen Herausforderungen nicht oft
- Rechenaufwand: Volles Fine-Tuning erfordert erhebliche GPU-Ressourcen
- Generalisierung: Die Modelle sind auf den Juice Shop spezialisiert und lassen sich möglicherweise nicht auf andere Anwendungen übertragen
Warum das wichtig ist
Für die Cybersicherheit
Dieser Ansatz könnte eines Tages helfen:
- Penetrationstests für gängige Schwachstellenmuster zu automatisieren
- Sicherheitsexperten mit KI-gestütztem Lernen zu schulen
- Eine kontinuierliche Sicherheitsbewertung von Webanwendungen durchzuführen
Für die KI-Forschung
Das Projekt demonstriert:
- Praktisches bestärkendes Lernen für reale Sicherheitsaufgaben
- Die Integration von Sprachmodellen in interaktive Umgebungen
- Reward Engineering für komplexe Bereiche mit seltenen Belohnungen
Wie eine KI lernt zu hacken (Die Details der Implementierung)
Ein Blick auf die Algorithmen und den Code, die dieses System zum Laufen bringen
Einleitung: Ein Blick unter die Haube
Lass uns gemeinsam die wichtigsten Bausteine durchgehen.
1. Die Datengenerierungs-Engine: JuiceShopAgent
Die Grundlage: Einrichtung der Testumgebung
Die JuiceShopAgent
-Klasse ist das Arbeitstier, das die Penetrationstests tatsächlich durchführt. So wird sie eingerichtet:
class JuiceShopAgent:
def __init__(self):
self.session = requests.Session()
self.session.timeout = 15
self.current_user_id = None
self.basket_id = None
self.admin_email = "admin@juice-sh.op"
Das mag einfach aussehen, aber hier steckt System dahinter. Jede Testsitzung erhält:
- Eine eigene HTTP-Sitzung (für Cookies und Zustandsverwaltung)
- Vernünftige Zeitüberschreitungen (15 Sekunden – genug für Antworten, aber nicht zu lang bei Hängern)
- Nachverfolgung des Benutzerkontexts (user_id und basket_id für zustandsabhängige Angriffe)
Intelligente Benutzerregistrierung: Frische Testkontexte schaffen
Ein cleverer Aspekt ist, wie für jeden Test neue Benutzer erstellt werden:
def register_and_login(self) -> Tuple[str, str]:
"""Neuen Benutzer registrieren und anmelden"""
email = f"user{uuid.uuid4().hex[:8]}@juice-sh.op"
password = f"Pass{random.randint(1000, 9999)}!"
try:
# Mit zufälliger Sicherheitsfrage registrieren
register_data = {
"email": email,
"password": password,
"passwordRepeat": password,
"securityQuestion": {
"id": random.randint(1, 12),
"answer": f"answer{random.randint(100, 999)}"
}
}
res = self.session.post(f"{BASE_URL}/api/Users", json=register_data)
if res.status_code == 201:
# Anmelden und Authentifizierungstoken erhalten
login_res = self.session.post(
f"{BASE_URL}/rest/user/login",
json={"email": email, "password": password}
)
if login_res.status_code == 200:
token = login_res.json()['authentication']['token']
self.session.headers.update({"Authorization": f"Bearer {token}"})
return email, password
Was ich an diesem Ansatz schätze:
- Einzigartige Identitäten: Jeder Testlauf erhält einen frischen Benutzerkontext
- Realistische Anmeldedaten: Passwörter folgen gängigen Mustern
- Korrekter Authentifizierungsablauf: Anmelden → Token erhalten → Header aktualisieren
- Fehlerbehandlung: Sauberes Scheitern, wenn die Registrierung nicht funktioniert
Das Angriffsarsenal: Bewährte Exploits für Schwachstellen
Das Herzstück des Systems ist get_proven_attacks()
, das eine Liste von Angriffen zurückgibt, die tatsächlich gegen den Juice Shop funktionieren:
def get_proven_attacks(self) -> List[Tuple[str, callable, int]]:
"""Gibt Angriffe zurück, die nachweislich mit dem aktuellen Juice Shop funktionieren"""
attacks = [
# SQL-Injection-Angriffe
("SQL Injection - Such-Umgehung",
lambda: self.session.get(f"{BASE_URL}/rest/products/search?q=qwert%27))%20UNION%20SELECT%20id,%20email,%20password,%20%274%27,%20%275%27,%20%276%27,%20%277%27,%20%278%27,%20%279%27%20FROM%20Users--"), 2),
("SQL Injection - Login-Umgehung Admin",
lambda: self._admin_login_bypass(), 3),
# Dateizugriffs-Angriffe
("Zugriff auf vertrauliches Dokument",
lambda: self.session.get(f"{BASE_URL}/ftp/acquisitions.md"), 1),
("Poison Null Byte-Angriff",
lambda: self.session.get(f"{BASE_URL}/ftp/eastere.gg%2500.md"), 3),
# Fehler in der Geschäftslogik
("Bestellung mit negativer Menge",
lambda: self._negative_quantity_working(), 3),
]
return attacks
Jeder Angriff ist strukturiert als:
- Beschreibung: Ein für Menschen lesbarer Name
- Funktion: Eine Lambda-Funktion oder Methode, die den Angriff ausführt
- Schwierigkeitsgrad: Eine ganzzahlige Bewertung (1=leicht, 3=schwer)
Das intelligente Belohnungssystem: Lernen durch Feedback
Hier wird das System wirklich intelligent. Die Belohnungsberechnung sagt nicht nur „Erfolg“ oder „Misserfolg“, sondern gibt differenziertes Feedback:
def calculate_smart_reward(challenges_before: set, challenges_after: set,
status_code: int, response_text: str, difficulty: int,
url: str) -> int:
"""Intelligente Belohnungsberechnung basierend auf tatsächlichen Schwachstellen"""
new_solved = challenges_after - challenges_before
reward = 0
# Grundbelohnung für das Lösen von Herausforderungen (GROSSE Belohnungen)
if new_solved:
reward = len(new_solved) * difficulty * 20 # Bis zu 60 Punkte!
logger.info(f"🎉 HERAUSFORDERUNG GELÖST: {list(new_solved)} - Belohnung: {reward}")
return reward
# Statusbasierte Belohnungen (mittlere Belohnungen)
if status_code == 200:
reward += 10
# Inhaltsanalyse auf potenzielle Schwachstellen
if response_text:
content_lower = response_text.lower()
# Indikatoren für SQL-Injection
if any(indicator in content_lower for indicator in
['email', 'password', 'users', 'admin', 'syntax error']):
reward += 15
# Indikatoren für Dateizugriff
if any(indicator in content_lower for indicator in
['markdown', 'acquisitions', 'legal', 'confidential']):
reward += 12
# Auch fehlgeschlagene Versuche können informativ sein
elif status_code == 401: reward += 3 # Authentifizierung erforderlich – interessant!
elif status_code == 403: reward += 5 # Verboten – wir haben etwas gefunden
elif status_code >= 500: reward += 6 # Serverfehler geben Informationen preis
return max(reward, 2) # Immer eine kleine Belohnung für den Versuch geben
Diese Belohnungsstruktur lehrt die KI:
- Große Erfolge verdienen große Belohnungen (Herausforderungen lösen = 20-60 Punkte)
- Interessante Misserfolge sind wertvoll (Fehlermeldungen erhalten = 6-15 Punkte)
- Schon Versuche zählen (mindestens 2 Punkte für jede Aktion)
Das Schöne liegt in der Inhaltsanalyse – das System erkennt, wenn der Antworttext für Schwachstellen relevante Schlüsselwörter enthält, auch wenn die Herausforderung nicht vollständig gelöst wurde.
2. Die Trainings-Engine: PPO-Implementierung
Datensatzvorbereitung: Von Rohdaten zu Trainingsbeispielen
Der Trainingsprozess beginnt damit, die rohen Penetrationstest-Daten in ein KI-freundliches Format umzuwandeln:
def build_dataset(tokenizer, data_path, split="train"):
"""Erstellt einen Datensatz für das Training"""
ds = load_dataset("json", data_files=data_path, split=split)
def create_prompt(sample):
# Prompt für das Chat-Modell verbessern
system_prompt = "Du bist ein erfahrener Cybersicherheits-Penetrationstester. Analysiere den aktuellen Zustand einer Webanwendung und schlage die nächste taktische Aktion vor, um Schwachstellen zu finden."
state_info = json.dumps(sample['state'], indent=2)
prompt = f"<|im_start|>system\n{system_prompt}<|im_end|>\n"
prompt += f"<|im_start|>user\n"
prompt += f"Aktueller Zustand der Webanwendung:\n```json\n{state_info}\n```\n\n"
prompt += f"Was sollte die nächste Aktion des Penetrationstests sein? Gib einen spezifischen, umsetzbaren Schritt an.<|im_end|>\n"
prompt += f"<|im_start|>assistant\n"
return prompt
def tokenize(sample):
sample["query"] = create_prompt(sample)
encoded = tokenizer(
sample["query"],
padding="max_length",
truncation=True,
max_length=512,
return_tensors="pt"
)
sample["input_ids"] = encoded["input_ids"].squeeze()
return sample
ds = ds.map(tokenize, batched=False)
ds.set_format(type="torch")
return ds
Das Prompt-Engineering ist hier entscheidend:
- Klare Rollendefinition: „Du bist ein erfahrener Cybersicherheits-Penetrationstester“
- Kontextbereitstellung: JSON-Zustand der Webanwendung
- Spezifische Anweisung: „Gib einen spezifischen, umsetzbaren Schritt an“
- Korrekte Formatierung: Verwendung des Qwen-Chat-Templates mit
<|im_start|>
-Tokens
PPO-Konfiguration: Die Lernparameter
In der PPO-Konfiguration geschieht die Magie – diese Parameter steuern, wie die KI lernt:
ppo_config = PPOConfig(
model_name=args.model_name,
learning_rate=1e-6, # Konservative Lernrate
batch_size=8, # 8 Beispiele auf einmal verarbeiten
mini_batch_size=2, # PPO-Updates auf 2 Beispiele gleichzeitig
gradient_accumulation_steps=4, # Effektive Batch-Größe = 8
# PPO-Hyperparameter
ppo_epochs=6, # 6 Optimierungsschritte pro Batch
gamma=0.99, # Abzinsungsfaktor für zukünftige Belohnungen
lam=0.95, # GAE-Lambda für die Vorteilberechnung
cliprange=0.1, # Policy-Updates beschneiden (konservativ!)
cliprange_value=0.1, # Wertfunktion-Updates beschneiden
vf_coef=0.2, # Gewichtung des Verlusts der Wertfunktion
max_grad_norm=1.0, # Gradient-Clipping
target_kl=0.05, # KL-Divergenz-Ziel (sehr konservativ)
whiten_rewards=True, # Belohnungen normalisieren
)
Ich möchte einige wichtige Entscheidungen hervorheben:
- Konservatives Clipping (0.1): Verhindert, dass sich das Modell zu drastisch ändert
- Niedrige Lernrate (1e-6): Langsames, stetiges Lernen
- Reward Whitening: Normalisiert Belohnungen, damit das Modell nicht durch deren Größenordnung verwirrt wird
Die Trainingsschleife: Wo das Lernen stattfindet
Die zentrale Trainingsschleife ist der Ort, an dem die KI tatsächlich lernt:
for epoch in range(args.epochs):
for batch in tqdm(ppo_trainer.dataloader, desc=f"Epoche {epoch + 1}"):
query_tensors = batch["input_ids"]
# Batch-Tensor in eine Liste umwandeln (PPO-Anforderung)
if isinstance(query_tensors, torch.Tensor) and query_tensors.dim() == 2:
query_tensors = [query_tensors[i] for i in range(query_tensors.size(0))]
# Antworten vom aktuellen Modell generieren
response_tensors = ppo_trainer.generate(
query_tensors,
return_prompt=False,
**generation_kwargs
)
# Belohnungen aus dem ursprünglichen Datensatz holen
rewards = []
for i in range(len(query_tensors)):
dataset_idx = (batch_count % len(dataset))
reward_value = dataset[dataset_idx]["reward"]
rewards.append(float(reward_value))
reward_tensors = [torch.tensor(r, dtype=torch.float32) for r in rewards]
# PPO-Optimierungsschritt
stats = ppo_trainer.step(query_tensors, response_tensors, reward_tensors)
# Fortschritt protokollieren
batch_mean_reward = sum(rewards) / len(rewards)
value_loss = stats.get('ppo/loss/value', 0)
policy_loss = stats.get('ppo/loss/policy', 0)
Die Abfolge ist:
- Anfragen aus dem Datensatz erhalten
- Antworten mit dem aktuellen Modell generieren
- Belohnungen basierend auf den Antworten berechnen
- PPO-Update durchführen, um das Modell zu verbessern
- Statistiken protokollieren, um den Fortschritt zu verfolgen
Generierungsparameter: Die Kreativität der KI steuern
Die Generierungseinstellungen sind sorgfältig auf Penetrationstests abgestimmt:
generation_kwargs = {
"min_length": -1,
"top_k": 40, # Die 40 wahrscheinlichsten nächsten Tokens berücksichtigen
"top_p": 0.85, # Schwellenwert für Nucleus-Sampling
"do_sample": True, # Sampling aktivieren (nicht gierig)
"temperature": 0.6, # Niedriger = fokussiertere Antworten
"pad_token_id": tokenizer.eos_token_id,
"eos_token_id": tokenizer.eos_token_id,
"max_new_tokens": 128, # Vernünftige Antwortlänge
"repetition_penalty": 1.05, # Leichte Strafe für Wiederholungen
}
Diese Einstellungen balancieren:
- Kreativität (Sampling aktiviert, vernünftige Temperatur)
- Fokus (niedrigere Temperatur, Top-k-Filterung)
- Qualität (Wiederholungsstrafe, Längenbegrenzung)
5. Wichtige Erkenntnisse aus dem Code und bewährte Praktiken
Philosophie der Fehlerbehandlung
Im gesamten Code findet sich ein konsistentes Muster zur Fehlerbehandlung:
try:
result = risky_operation()
if result.status_code == 200:
return process_success(result)
except Exception as e:
logger.debug(f"Operation fehlgeschlagen: {e}")
# Sinnvollen Standardwert zurückgeben, statt abzustürzen
mock_response = requests.Response()
mock_response.status_code = 500
return mock_response
Dieser Ansatz:
- Protokolliert Probleme, ohne die Ausführung zu stoppen
- Liefert Schein-Antworten, um das Training am Laufen zu halten
- Verhält sich bei Ausfällen von Komponenten kontrolliert
Speicherverwaltung
Der Code geht sorgfältig mit dem GPU-Speicher um:
# Geeignete Datentypen verwenden
model = AutoModelForCausalLMWithValueHead.from_pretrained(
model_name,
torch_dtype=torch.bfloat16, # Halbe Genauigkeit spart Speicher
device_map="auto", # Automatische Verteilung auf GPU/CPU
)
# Gradient-Checkpointing aktivieren
ppo_config = PPOConfig(
gradient_checkpointing=True, # Tausche Rechenleistung gegen Speicher
# ...
)
Ende von Teil 1