Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
AfterLife — Lab zur Erkennung persistenter Revocation: wenn das Zurücksetzen des Passworts erfolgreich ist, der Angreifer aber nie geht. Reproduziert den bedingten Revocation-Bug Strapi CVE-2026-22706, seinen Fix, ein Erkennungspaket mit drei Regeln und die naive Regel, die ihn übersieht. | Kitploit
Tools/GitHubGitHub/het-p301204/afterlife
DefensivwerkzeugeSchwachstellenanalyseWebsicherheitAuthentifizierungLernen & BildungRed TeamingIncident ResponseLabs & Praxis
GitHubhet-p301204/afterlife

AfterLife

Lab zur Erkennung persistenter Revocation: wenn das Zurücksetzen des Passworts erfolgreich ist, der Angreifer aber nie geht. Reproduziert den bedingten Revocation-Bug Strapi CVE-2026-22706, seinen Fix, ein Erkennungspaket mit drei Regeln und die naive Regel, die ihn übersieht.

vor 12h 41mNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Repository anzeigen
Teilen

AFTERLIFE

Labor zur Erkennung von Persistenz nach Widerruf

Wenn das Zurücksetzen des Passworts gelingt, aber der Angreifer nie verschwindet.

Ein lokales Rot/Blau-Labor für eine einzige Schwachstellenklasse: die Anmeldedaten, die das Ereignis überleben, das sie eigentlich hätte vernichten sollen. Es liefert den Exploit, die Grundursache, den Fix, ein Erkennungspaket mit drei Regeln, ein Zustands-Audit für das, was die Regeln strukturell nicht sehen können, eine forensische Konsole — und die Erkennungsregel, die nicht funktioniert, im Repository belassen, um ihr Scheitern vorzuführen.

CI tests python rules OWASP CWE

license

Schnellstart · Der Befund · Warum naive Erkennung scheitert · Das Regelpaket · Konsole · Der Fix · Matrix · Tests · Dokumentation


Derselbe Angriff gegen beide Implementierungen. Ein Balken pro Anmeldedaten, von der Ausstellung bis zum Tod, gruppiert in Abstammungslinien. Die gestrichelte Linie ist die Passwortänderung. Im verwundbaren Modus überqueren fünf Balken sie und laufen weiter; im behobenen Modus endet jeder Balken der gestohlenen Abstammungslinie dort.

Derselbe Angriff. Dieselben Anfragen. Ein Unterschied. Generiert von scripts/figures.py aus derselben Payload, die die Konsole zeichnet — in CI neu generiert und diffed, damit eine Abbildung nicht vom Code abweichen kann.


Die Eindämmungsmaßnahme, die nicht eindämmt```text

09:00 alice logs in ┐ refresh credential rt-001 │ the attacker steals rt-001 ┘

10:00 alice changes her password ← the one thing a victim can do alone HTTP 200 · password changed · fresh session issued

10:00:03 attacker: POST /refresh rt-002 → HTTP 200 new access credential at-004, issued 10:00:03

10:01:00 attacker: GET /me at-004 → HTTP 200 {"username": "alice", "authenticated": true}

root@kitploit:~
Kein Fehler. Keine Anomalie. Keine fehlgeschlagene Authentifizierung zum Zählen. Die Zugangsdaten des Angreifers sind *drei Sekunden alt* und wurden vom Server auf Anfrage nach dem Reset erzeugt.

Hier ist die gesamte Schwachstelle:```python
def _revoke_for_security_change(self, user, kind, device_id):
    if device_id:
        revoke_credentials(user, device_id=device_id)   # ← the finding

Lesen Sie das so, wie es ein Reviewer tun würde. Die Revocation-Logik ist direkt dort. Sie ruft die richtige Funktion mit dem richtigen Scope auf. Der Endpunkt drumherum aktualisiert den Passwort-Hash, gibt 200 zurück und stellt eine frische Session aus — jedes beobachtbare Verhalten einer korrekten Passwortänderung ist vorhanden.

Und wenn der Aufrufer device_id weglässt, wird nichts widerrufen, und der Endpunkt meldet trotzdem Erfolg.

Das ist nicht hypothetisch. Es ist CVE-2026-22706 in Strapi ≤ 5.33.2, wo der Refresh-Token-Invalidierungsschritt von einem vom Aufrufer gelieferten deviceId abhängig war. Bewertet mit 2.1, Low. Diskutiert unten.


Warum das wichtig ist

Die Bewertung ist niedrig, weil der Angreifer bereits Zugriff hatte — das ist die Eintrittsbedingung, und dieser Bug gewährt nichts Neues. Was er gewährt, ist Dauer, und er tut dies, indem er die eine Kontrolle bricht, die das Opfer selbst bedienen kann.

  • Jedes Account-Takeover-Runbook beginnt mit Passwort zurücksetzen. Jedes Produkt sagt dem Nutzer dasselbe.
  • Wenn es stillschweigend fehlschlägt, wird dem Opfer gesagt, das Problem sei gelöst, und es hört auf zu suchen — was das Erkennungssignal abschaltet, das bei Account-Takeover am wichtigsten ist: dass der Nutzer es bemerkt.
  • Das Persistenzfenster ist die Lebensdauer des Refresh-Credentials: standardmäßig 30 Tage, durch Rotation unbegrenzt erneuerbar. test_persistence_lasts_as_long_as_the_refresh_credential durchläuft sieben Tage simulierte Zeit, um das zu zeigen.
  • Es gibt keine zweite Aktion, die der Nutzer ergreifen kann. Eine zweite Passwortänderung bewirkt genau so viel wie die erste.

Ein Low-bewerteter Bug in einem Containment-Pfad kostet mehr als ein Low-bewerteter Bug in einem Feature-Pfad, weil die Kosten während eines Vorfalls anfallen, wenn niemand Advisories liest.


Der naive Detektor

Die Regel, die man zuerst schreibt:```text IF credential.issued_at < credential_change.timestamp: ALERT

root@kitploit:~
Es ist keine dumme Regel. Sie ist billig, braucht ein Feld, liest sich wie die Definition
des Problems, und sie hat **recht im einfachen Fall** — ein Angreifer, der ein
gestohlenes *Access*-Token nach dem Reset verwendet, wird erwischt.
`test_the_naive_rule_catches_the_simple_case` bestätigt, dass sie funktioniert.

Beide Regeln stellen dieselbe Frage über dieselbe Credential. Sie lesen unterschiedliche
Felder, um sie zu beantworten, und die beiden Felder widersprechen sich:

![Zwei Zeilen, eine pro Feld. credential.issued_at liest 10:00:03, drei Sekunden nach der Änderung, und schlussfolgert, dass die Credential sauber aussieht. lineage.root_issued_at liest 09:00:00, eine Stunde vor der Änderung, und schlägt Alarm.](https://raw.githubusercontent.com/het-p301204/afterlife/main/docs/figures/two-fields.svg)

**Drei Sekunden danach, oder eine Stunde davor — dieselbe Credential, zum selben
Zeitpunkt.** Die naive Regel fragt ein Feld ab, das der Angreifer kontrolliert, und ein
`POST /refresh` setzt es zurück.

Führe sie über das Request-Log aus, wo diese Abfrage tatsächlich geschrieben wird,
denn Request-Logs sind das, was man hat:```text
NAIVE DETECTOR (NAIVE-001), over the request log

  Result: NO ALERT

  Six requests were served to the attacker after the reset. Every one of them
  carried at-004, minted at 10:00:03 -- three seconds *after* the password
  change. By its own timestamp it is the newest credential on the account.

Es wäre einfach, dort aufzuhören, und unehrlich, also führt das Labor auch die fairste Version der naiven Regel aus — erweitert, um auch den Refresh-Endpunkt zu überwachen:```text NAIVE DETECTOR, widened to include POST /refresh

1 alert at 2026-09-11T10:00:03.000Z: the credential presented to /refresh was rt-002, issued 2026-09-11T09:30:00.000Z. Then it goes blind. 6 events follow that hop and it flags none of them, because every credential from there on carries a post-reset timestamp. Its incident covers 1 accepted request; the lineage rule's covers all of them.

And look at what the alert names: NAIVE-001 revoke rt-002 -- rotated away and already dead at 10:00:03 AFTERLIFE-001 revoke lin-001 -- the live thing every future credential descends from

root@kitploit:~
Das ist der Unterschied, der um 3 Uhr morgens zählt. Die naive Regel erfasst
den einen Augenblick, in dem die Kette die Grenze überschreitet, und verliert
dann die Spur für die verbleibenden 29 Tage — und die Credential, die sie
benennt, wurde *bereits rotiert und vom Server widerrufen*. Sie zu widerrufen
bewirkt nichts. `lin-001` ist das Objekt, das du töten musst.

**Und der naiven Regel fehlte es nicht an Telemetrie.** Sie nutzt denselben
Event-Stream, denselben Lineage-Index, dieselbe Toleranz, dieselbe
Deduplizierung und denselben begrenzten Zustand. Sie überschreibt eine Methode:```python
class Correlator:                                  # AFTERLIFE-001
    def _age_reference(self, facts):
        return facts.root_issued_at

class NaiveCorrelator(Correlator):                 # NAIVE-001
    def _age_reference(self, facts):
        return facts.issued_at

root_issued_at steht im Index, den er bereits konsultiert. test_the_naive_rule_had_the_data_it_needed beweist es. Das Scheitern liegt im Vergleich, nicht im Logging.


Das Regelpaket

Drei Regeln über einem Log. Sie beantworten unterschiedliche Fragen und sie feuern in der Reihenfolge, in der sich ein Vorfall tatsächlich entfaltet.

RegelSchweregradbeantwortetfeuert
AFTERLIFE-002Refresh-Credential-WiederverwendungCRITICAL / MEDIUMwurde sie gestohlen?bei der Wiederverwendung
AFTERLIFE-003Unvollständiger Widerruf bei einer SicherheitsänderungHIGH / LOWlief die Eindämmung?bei der Änderung — kein Angreifer nötig
AFTERLIFE-001Nutzung der Credential-Lineage nach WiderrufHIGHwurde die veraltete Lineage genutzt?bei der ersten Akzeptanz
RULE PACK

10:00:00 HIGH AFTERLIFE-003 Incomplete revocation at a security change 10:00:03 HIGH AFTERLIFE-001 Post-revocation credential lineage use

root@kitploit:~
**Diese Reihenfolge ist das Nützlichste in diesem Repository.** AFTERLIFE-003
schlägt in dem Moment an, in dem die Änderung erfolgt, drei Sekunden bevor der Angreifer
irgendetwas berührt, weil die benötigten Beweise bereits vollständig sind: Das Log sagt, welche
Lineages beim Eintritt aktiv waren, und es sagt nicht, dass sie widerrufen wurden.

Es braucht kein Opfer und keine Ausnutzung. Es meldet den Defekt beim ersten
Passwort-Reset, den ein Benutzer durchführt — was es zu dem einen macht, das man im Staging
ausführt, wo es keinen Angreifer gibt, auf den man warten muss. AFTERLIFE-001 sagt einem, dass ein Einbruch
im Gange ist; AFTERLIFE-003 sagt einem, dass die eigene Eindämmungskontrolle defekt ist.

![Zwei Alarmkarten. AFTERLIFE-003 um 10:00:00 meldet Urteil no_containment, Wasserzeichen nicht aufgezeichnet, Umfang none und lin-001 überlebend. AFTERLIFE-001 um 10:00:03 meldet Credential rt-002 mit einer Root, die eine Stunde vor der Änderung ausgestellt wurde.](https://raw.githubusercontent.com/het-p301204/afterlife/main/docs/figures/findings-vulnerable.svg)

`application recorded watermark: no` ist das Feld, das einen Incident
Responder auf den *Defekt* statt auf das Symptom hinweist.

### Das Wasserzeichen, das die Regeln verwenden, ist nicht das, das die Anwendung meldet

Das Credential-Change-Event trägt ein `revocation_watermark`-Feld — den
`credentials_valid_after`-Wert, den die Anwendung geschrieben hat. **In der verwundbaren
Implementierung ist er `null`, weil die Anwendung nie einen geschrieben hat.** Das ist
der Bug.

Eine Regel, die auf dieses Feld abzielt, wäre also blind für genau den Fall, den sie abfangen soll.
Die Regeln verankern sich am eigenen `timestamp` des Events, der wahr ist, unabhängig davon, ob
die Anwendung ihre Aufgabe erfüllt hat, und melden das fehlende Feld als Beweis.

### Eigenschaften```text
  deduplication        6 accepted requests on the stale lineage -> 1 alert
  event order          shuffled stream -> same alert  (1 alert)
  duplicate telemetry  log replayed twice -> 1 alert  (24 duplicate events discarded)
  false positives      the fixed implementation's log -> 0 alerts
  bounded state        caps at 256 activity/user, 2000 users, 20000 credentials

Die Unabhängigkeit der Reihenfolge ist kein „funktioniert größtenteils“: test_6b_every_permutation_of_the_critical_events_detects führt alle 24 Reihenfolgen der vier relevanten Ereignisse aus und verlangt genau einen Alarm von jedem. Die Deduplizierung basiert auf (user_id, credential_change_event_id, lineage_id), mit event_id-Unterdrückung davor, damit eine erneut eingespielte Datei die Zählung nicht aufblähen kann.

Vollständige Regelkarten, erforderliche Telemetrie und Response-Runbooks: docs/detection.md.


Die Konsole

Eine Leseoberfläche für die eine Frage, um die es im Lab geht. Kein Dashboard von Alarm- Zählungen — ein Sterberegister. Ein Balken pro Credential, von der Ausstellung bis zum Tod, gruppiert nach der Abstammungslinie, von der es abstammt, mit der Passwortänderung als Linie gezeichnet, an der alles davor enden sollte.

Die vollständige AFTERLIFE-Konsole im verwundbaren Modus: Kopfzeile, das Urteil in Serifenschrift, ein fünfzelliger Zählerstreifen, der Zwei-Feld-Vergleich, das Lebenslinien-Diagramm mit fünf Balken, die die Guillotine kreuzen, und beide Alarmkarten.

Eine bewusste Umkehrung: Wärme bedeutet lebendig, und nach der Linie ist Wärme falsch. In den meisten Sicherheits-UIs bedeutet Rot, dass ein Fehler aufgetreten ist. Hier tritt kein Fehler auf — jede Anfrage im verwundbaren Durchlauf liefert 200. Die Farbe folgt stattdessen der Sterblichkeit: Kalt ist ein Credential, das gestorben ist, als es dazu angewiesen wurde, warm ist eines, das noch atmet, und jenseits der Guillotine ist das Weiteratmen der ganze Befund. (BLACKOUT setzte dieselbe Konvention, wobei 200 das Rote war.)

Die dünnen vertikalen Tropfen sind Genealogie: ein Kind-Credential, das in diesem Augenblick aus seinem Elternteil erzeugt wird. rt-001 → rt-002 → rt-004 steigt über das gesamte Fenster nach unten und rechts, und im verwundbaren Modus steigt es nach dem Schnitt weiter — was das Bild einer Abstammungslinie ist, die neue Credentials jenseits ihres eigenen Aussterbeereignisses erzeugt.```bash python scripts/lab.py console

root@kitploit:~
schreibt **[`docs/console-preview.html`](https://github.com/het-p301204/afterlife/blob/main/docs/console-preview.html)** — eine
in sich geschlossene 74 KiB große Datei mit beiden Durchläufen fest eingebaut. Kein Server, kein Netzwerk, keine
Schriftarten zum Abrufen; öffne sie direkt aus dem Dateisystem. Oder führe die Live-Version aus:```bash
python -m console

Ziehen Sie den Scrubber über 10:00:00 hinaus zurück und wieder vorwärts: Die Befunde erscheinen, wenn das Protokoll sie verdient, AFTERLIFE-003 bei der Änderung und AFTERLIFE-001 drei Sekunden später. Die Designbegründung, einschließlich dessen, was gestrichen wurde, findet sich in docs/console-design.md.

Die Konsole ist konstruktionsbedingt schreibgeschützt. Sie führt abgeschlossene Szenarien erneut aus und zeichnet sie auf; sie kann die Implementierung nicht umschalten, die Uhr nicht verschieben oder etwas widerrufen. test_the_console_has_no_control_surface stellt sicher, dass die einzige Nicht-GET- Route /api/rebuild ist. Eine Leseoberfläche, die ändern kann, was sie liest, ist eine, der man nicht trauen kann.


Architektur

Sechs Pakete, eine Sicherheitsklasse, keine Infrastruktur.```text app/ the lab application config.py mode selection; defaults to fixed, deliberately store.py SQLite credential state + the security-change audit trail tokens.py minting and decoding; a JWT is a signed pointer to a row auth.py login / refresh / change-password + THE BUG + the audit main.py six endpoints

common/ clock.py a rewindable UTC clock, so an hour of history costs nothing events.py the event vocabulary, shared by app and detector telemetry.py JSONL emission with credential-name redaction

detector/ strictly downstream: reads a log file, decides nothing base.py the alert shape, replay suppression, bounded state lineage.py rebuilds a credential's ancestry from issuance events ledger.py which lineages are alive, per user rules.py AFTERLIFE-001 persistence reuse.py AFTERLIFE-002 theft containment.py AFTERLIFE-003 the defect itself naive.py NAIVE-001, kept in order to be demonstrated failing audit.py the state scan the rules structurally cannot do engine.py the pack: one shared index, one stream, ranked alerts tail.py byte-offset JSONL tailer

console/ the mortality register payload.py one run, shaped for drawing static/ ~1400 lines of vanilla HTML/CSS/JS, no build step

scripts/ lab.py the demonstration scenarios.py nine scenarios x both implementations report.py the incident report preview.py bake the offline console figures.py render the console to SVG for this README

tests/ 290 tests

root@kitploit:~
Die Anwendung läuft **in-process** unter der Demo, der Konsole und den Tests. Keine
Ports, kein Dev-Server, kein Docker. Die Uhr ist fixiert, sodass jeder Lauf
deterministisch ist und die committete Telemetrie, Konsole, Abbildungen und Berichte alle
byte-identisch zwischen Läufen sind — was CI mit `git diff --exit-code` prüft.

### Serverseitige Durchsetzung

Ein Credential ist eine **Zeile**, kein String. Das JWT, das der Client hält, trägt `cid` —
einen Lookup-Schlüssel — und die Zeile entscheidet, ob das Credential lebendig ist.

Der Ablauf wird gegen das `expires_at` der Zeile geprüft, nicht gegen den `exp`-Claim, den
der Inhaber präsentiert hat. Beide temporalen Claims werden an PyJWT mit `verify=False`
übergeben, was hier bedeutet *„der Server prüft dies selbst"*, und [`app/auth.py`](https://github.com/het-p301204/afterlife/blob/main/app/auth.py)
tut dies bei jeder Anfrage. Der Client hat nie ein Mitspracherecht darüber, ob sein eigenes
Credential noch gültig ist.

---

## Angriffszeitlinie```mermaid
flowchart TD
    A["Account compromised<br/><i>phishing · XSS · stolen backup</i>"] --> B["Attacker holds refresh credential rt-001<br/>lineage lin-001, root sess-001 @ 09:00"]
    B --> C["Legitimate password change @ 10:00<br/>HTTP 200 · password hash updated"]
    C --> D{"device_id supplied?"}
    D -->|"yes"| E["revoke_credentials(user, device_id)<br/>rt-001 revoked"]
    D -->|"no — the exploit"| F["nothing revoked<br/>no watermark written"]
    E --> G["Attacker refresh → 401<br/><b>contained</b>"]
    F --> AF3["<b>AFTERLIFE-003 · HIGH</b> @ 10:00:00<br/>containment did not run<br/><i>no attacker action required</i>"]
    F --> H["POST /refresh rt-002 → 200<br/>mints at-004 @ 10:00:03"]
    H --> I["GET /me at-004 → 200<br/>well-formed · correctly signed · <b>no anomaly</b>"]
    I --> J["at-004.issued_at > watermark<br/>NAIVE-001: no alert"]
    I --> K["lineage lin-001 root @ 09:00 < watermark<br/><b>AFTERLIFE-001 · HIGH</b>"]
    F --> L["attacker holds and never spends<br/>AFTERLIFE-001 silent — correctly<br/><b>state audit: dormant survivor</b>"]

    style F fill:#7f1d1d,color:#fff
    style H fill:#7f1d1d,color:#fff
    style I fill:#7f1d1d,color:#fff
    style J fill:#78350f,color:#fff
    style K fill:#14532d,color:#fff
    style AF3 fill:#14532d,color:#fff
    style L fill:#1e3a5f,color:#fff
    style E fill:#14532d,color:#fff
    style G fill:#14532d,color:#fff

Der Refresh-Chain-Zweig in der Mitte ist der springende Punkt. Der Angreifer verwendet keine alte Credential. Er verwendet eine alte Lineage, und die Lineage prägt bei Bedarf etwas Neues.


Token-Lineage

Eine Session ist die Wurzel einer Lineage. Alles, was darunter geprägt wird, erbt diese Wurzel, für immer.```text sess-001 session lineage lin-001 root sess-001 issued 09:00:00 │ ├── rt-001 refresh parent sess-001 root_issued_at 09:00:00 │ └── at-001 access parent rt-001 root_issued_at 09:00:00 │ ├── rt-002 refresh parent rt-001 root_issued_at 09:00:00 ← 09:30 rotation │ └── at-002 access parent rt-002 root_issued_at 09:00:00 │ └── rt-004 refresh parent rt-002 root_issued_at 09:00:00 ← 10:00:03 rotation └── at-004 access parent rt-004 root_issued_at 09:00:00 issued_at 10:00:03

root@kitploit:~
`at-004` ist drei Sekunden alt. Seine Abstammung ist eine Stunde alt. Beide
Tatsachen sind wahr, und nur eine davon ist in einem Request-Log sichtbar.

Zwei Invarianten halten dies zusammen, und beide sind getestet:

1. **Nur Authentifizierung erzeugt eine Abstammungslinie.** `Store.open_session` ist der
   einzige Ort, an dem eine `lineage_id` generiert wird, und es macht die Session zu ihrer eigenen
   `root_credential_id`.
2. **Minting kopiert die Wurzel nach unten.** `TokenService.mint` liest die Lineage-Felder
   vom Parent, anstatt sie neu zu berechnen, sodass eine Credential keine
   frischere Abstammung erlangen kann als der Login, von dem sie abstammt.

Ein vom Inhaber präsentiertes Token trägt bewusst **nicht** seine Lineage-Wurzel. Wenn es
das täte, könnte der Inhaber darüber lügen.

---

## Das State-Audit

AFTERLIFE-001 wird ausgelöst, wenn eine veraltete Lineage *verwendet* wird. Das ist der richtige Auslöser für
eine Detection-Regel, und es lässt eine Lücke: **eine veraltete Lineage, die niemand berührt hat, ist
für sie unsichtbar.** Ein Angreifer, der eine Credential stiehlt, beobachtet, wie das Zurücksetzen fehlschlägt,
und dann wartet, erzeugt keine Aktivität zum Korrelieren.

Also stellt das Lab auch die Frage, die eine Regel nicht stellen kann: nicht *was passiert ist*, sondern *was
noch am Leben ist*.```text
  scenario: dormant-survivor   (the attacker holds the credential and never spends it)

  AFTERLIFE-001   silent   — correct; nothing was accepted
  AFTERLIFE-003   HIGH     — the change revoked nothing
  state audit     1 stale lineage, 1 dormant

| --no-color | Deaktiviert farbige Ausgabe | | --verbose | Aktiviert ausführliche Ausgabe | | --quiet | Unterdrückt alle Ausgaben außer Fehlern | | --config | Gibt eine benutzerdefinierte Konfigurationsdatei an | | --timeout | Legt das Anforderungs-Timeout in Sekunden fest | | --retry | Anzahl der Wiederholungsversuche bei fehlgeschlagenen Anforderungen | | --proxy | Gibt einen Proxy für Anforderungen an | | --header | Fügt einen benutzerdefinierten Header zu Anforderungen hinzu | | --cookie | Legt ein Cookie für Anforderungen fest | | --user-agent | Legt den User-Agent-Header fest | | --output | Schreibt die Ausgabe in eine Datei | | --format | Legt das Ausgabeformat fest (json, yaml, text) | | --threads | Anzahl der zu verwendenden Threads | | --rate-limit | Begrenzt die Anforderungsrate | | --follow-redirects | Folgt HTTP-Weiterleitungen | | --insecure | Deaktiviert die SSL-Zertifikatsüberprüfung | | --debug | Aktiviert den Debug-Modus |```bash python scripts/lab.py audit

root@kitploit:~
| `--no-color` | Deaktiviert farbige Ausgabe |
| `--verbose` | Aktiviert ausführliche Ausgabe |
| `--quiet` | Unterdrückt alle Ausgaben außer Fehlern |
| `--config` | Gibt den Pfad zur Konfigurationsdatei an |
| `--output` | Gibt das Ausgabeformat an (json, yaml, table) |
| `--timeout` | Legt das Anforderungszeitlimit in Sekunden fest |
| `--retry` | Anzahl der Wiederholungsversuche bei fehlgeschlagenen Anfragen |
| `--proxy` | Gibt den zu verwendenden Proxy an |
| `--header` | Fügt einen benutzerdefinierten Header zu den Anfragen hinzu |
| `--cookie` | Fügt ein Cookie zu den Anfragen hinzu |
| `--user-agent` | Legt den User-Agent-Header fest |
| `--follow-redirects` | Folgt HTTP-Weiterleitungen |
| `--insecure` | Deaktiviert die TLS-Zertifikatsüberprüfung |
| `--rate-limit` | Legt die maximale Anforderungsrate fest |
| `--concurrency` | Legt die Anzahl gleichzeitiger Anforderungen fest |
| `--wordlist` | Gibt die zu verwendende Wortliste an |
| `--threads` | Legt die Anzahl der Threads fest |
| `--silent` | Unterdrückt alle Ausgaben außer Ergebnissen |
| `--debug` | Aktiviert den Debug-Modus |
| `--version` | Zeigt Versionsinformationen an |
| `--help` | Zeigt Hilfemeldungen an |```text
VULNERABLE
  server state   1 lineage(s) outlived the change   (0 dormant)
  telemetry      1 lineage(s)   -- the same question, asked of the log instead of the database
    lin-001 root 2026-09-11T09:00:00.000Z  7 credentials  in use

FIXED
  server state   clean
  telemetry      clean

Zwei Quellen, bewusst: Der Telemetrie-Audit sieht, was das Log beweisen kann, der Server-State-Audit sieht, was die Datenbank glaubt. Wo sie sich widersprechen, ist das Log kein getreues Abbild des Credential-Zustands, und jede darauf aufgebaute Erkennung ist schwächer, als sie aussieht — deshalb gibt der Befehl beides aus und sagt es, wenn sie voneinander abweichen.

Der Server-State-Audit benötigt die Tabelle security_changes, die beide Modi schreiben, denn das Festhalten, dass eine Änderung stattgefunden hat, ist eine eigene Verpflichtung, getrennt vom Handeln danach — und die verwundbare Implementierung erfüllt genau eine der beiden.


Szenario-Matrix

Neun Szenarien gegen beide Implementierungen. python scripts/scenarios.py prüft seine eigenen zwei Invarianten und beendet sich mit einem Fehlercode, wenn eine davon verletzt wird.

SzenarioModusEreignisseschlimmster Fallausgelöste Regelnstaledormant
legitimate-onlyvulnerable19LOWAFTERLIFE-00300
legitimate-onlyfixed19–keine00
stolen-refreshvulnerable22HIGHAFTERLIFE-001, AFTERLIFE-00310
stolen-refreshfixed23–keine00
stolen-refresh-with-device-idvulnerable19LOWAFTERLIFE-00300
stolen-refresh-with-device-idfixed19–keine00
multi-devicevulnerable29HIGHAFTERLIFE-001, AFTERLIFE-00320
multi-devicefixed29–keine00
refresh-reusevulnerable11MEDIUMAFTERLIFE-00200
refresh-reusefixed15MEDIUMAFTERLIFE-00200

Vier Zeilen tragen ein Argument:

  • legitimate-only enthält überhaupt keinen Angreifer. Der Fix produziert nichts; die verwundbare Implementierung produziert ein LOW. Das ist kein False Positive — es ist enumeration_only: Die Revocation hat diesmal funktioniert, durch Enumeration, ohne Watermark, die ein Credential abdeckt, das der Server vergessen hat. Das Pack trennt die beiden Implementierungen ohne anwesenden Angreifer.
  • dormant-survivor ist der blinde Fleck und seine Antwort in einer Zeile: AFTERLIFE-001 still, AFTERLIFE-003 HIGH, Audit dormant: 1.
  • multi-device ist der Fall, in dem der funktionierende Zweig der verwundbaren Implementierung dennoch scheitert — die Revocation ist auf das Laptop beschränkt, zwei andere Geräte bleiben unberührt.
  • expired-lineage ist die False-Positive-Kontrolle: Eine 40 Tage alte Session, deren Credentials von selbst abgelaufen sind, ist kein überlebender Persistenzpfad und wird auch nicht als solcher gemeldet.

Und die zentrale Behauptung der Matrix, geprüft durch das Skript und durch test_the_fix_never_produces_a_control_failure_finding: Der Fix erzeugt in keinem Szenario einen Control-Failure-Finding. AFTERLIFE-002 wird durchgelassen — es meldet einen Diebstahl, keinen Control-Failure, und eine korrekte Implementierung hat weiterhin Diebstähle zu melden.


False Positives

Eine Regel, die bei jedem Passwort-Reset alarmiert, wird innerhalb einer Woche stummgeschaltet, und eine stummgeschaltete Regel ist schlimmer als keine Regel — sie ist eine Regel, von der alle glauben, dass sie läuft.

FallErgebnisWarum
Passwortänderung, dann wird die Ersatz-Session genutztkein Alarmdie Wurzel der neuen Lineage liegt auf oder nach der Watermark
Reset, dann sofortiges Browsen (/me, /profile, /settings)kein Alarmeine frische Lineage, eine frische Wurzel
Telefon, Laptop und Tablet, alle korrekt rotiertkein Alarmjeder Login ist seine eigene Lineage
Ein Gerät, das sich nach der Änderung angemeldet hat und neben dem Angreifer browstkein Alarmfrische Wurzel — und die stale Lineage alarmiert weiterhin, allein
Ein stale Credential, das abgelehnt wurdekein Alarmresult: failure ist ausgeschlossen; es ist ein Beweis für die Verteidigung
Clock Skew bis zu 2s zwischen Komponentenkein Alarmdie dokumentierte Toleranz
Eine fehlgeschlagene Passwortänderungkein Alarmkein Anker
Aktivität vor der Änderungkein AlarmZeitordnungsprüfung
Ein rotiertes Credential (reason: rotated)zählt nicht als Todein verbrauchtes Credential, keine getötete Lineage
Eine 40 Tage alte Session, deren Credentials abgelaufen sindkein SurvivorAblauf wird pro Lineage verfolgt
Eine erste Passwortänderung bei einem brandneuen Kontokein Alarmes war nichts live, als es begann
Refresh-Token-Rotation aus einer stale LineageALARMfrischer Zeitstempel, stale Abstammung — das ist der Finding

Jede Zeile hat einen Test in tests/test_detector_afterlife001.py und tests/test_detector_rulepack.py.

Zur 2-Sekunden-Toleranz. Jeder Zeitstempel in diesem Lab stammt aus einem Prozess und einer Uhr, also ist die ehrliche Toleranz null. 2s ist das, was eine realistische Zwei-Host-Bereitstellung unter NTP benötigt, und es ist drei Größenordnungen kleiner als die Lücken, die dieser Angriff erzeugt — der ganze Zweck eines Refresh-Credentials ist es, langlebig zu sein. Die Toleranz ist ein Fenster, in dem eine tatsächlich stale Lineage ignoriert wird, deshalb wird sie bewusst klein gehalten: test_12b_a_gap_beyond_the_tolerance_does_alert legt fest, dass 2,1s weiterhin auslöst.


Der Fix```python

THE FIX. Two mechanisms, deliberately redundant.

self.store.set_watermark(user.user_id, moment) # the guarantee revoked = self.store.revoke_credentials(user.user_id, ...) # defence in depth

root@kitploit:~
Weder hängt von `device_id` ab. Es wird als Kontext erfasst und hat keinen Einfluss auf den
Blast Radius.

**Das Watermark ist die architektonische Garantie.** Ein Zeitstempel pro Benutzer,
`credentials_valid_after`, wird bei jeder Anfrage sowohl gegen die
Ausstellung des Credentials selbst *als auch* gegen dessen Lineage-Root verglichen:```python
watermark = user.credentials_valid_after
if credential.issued_at      < watermark:  reject  # the obvious case
if credential.root_issued_at < watermark:  reject  # the refresh chain

Die zweite Gegenüberstellung ist diejenige, deren korrekte Umsetzung etwas kostet, und diejenige, die eine naive Implementierung auslässt – bei der Durchsetzung genauso wie bei der Erkennung.

Explizite Widerrufung ist Verteidigung in der Tiefe, und Beweismittel. revoked_at und revocation_reason sind das, was ein Incident Responder liest, und die auth.token.revoked-Events sind das, was beweist, dass eine Eindämmung stattgefunden hat.

test_the_watermark_alone_rejects_a_stale_credential setzt das Watermark ohne irgendetwas zu widerrufen und verlangt, dass die veraltete Credential abgelehnt wird – was feststellt, welche der beiden tragend ist, und welche die Eigenschaft ist, die auch für Credentials gilt, von denen der Server vergessen hat, dass er sie ausgestellt hat.

Derselbe Angriff, gegen den Fix

Die Konsole im Fixed-Modus: das Urteil lautet „Nothing issued before the change works after it", die Zähler sind alle grün, jeder Balken in lin-001 endet an der Guillotine, und das Findings-Panel meldet keine Findings bei fünf bestandenen Checks.

Keine einzige Anfrage hat sich geändert. Eine Funktion schon.

Nicht nur Passwörter

Der Auslöser ist nicht „das Passwort hat sich geändert". Er ist „etwas hat sich geändert, das zuvor ausgestellte Credentials unglaubwürdig macht". Alle vier Routen teilen _revoke_for_security_change, sodass der Fix und der Bug identisch gelten für:

Änderungwarum Credentials unglaubwürdig werden
Passwortänderung / -resetdas Geheimnis, mit dem die Session etabliert wurde, ist weg
MFA-Registrierung oder -Änderungdie Faktoren, mit denen die Session etabliert wurde, sind nicht die Faktoren des Kontos
Rollenänderung / Privilegieneskalationdie Credential wurde unter einer anderen Autorisierung geprägt
Kontowiederherstellungkonstruktionsbedingt könnte das Konto einen Moment zuvor in jemandes anderer Hand gewesen sein
Loginwiderruft nicht andere – eine neue Abstammungslinie, keine Aussage, dass die alten nicht vertrauenswürdig sind

Getestet für jede Art, in beiden Modi, in tests/test_privilege_changes.py – einschließlich der Login-Nicht-Anforderung, dokumentiert, damit der nächste Leser sie nicht „repariert".


Architektonische Kompromisse

Vier Wege, eine Credential aufhören zu lassen zu funktionieren. AFTERLIFE implementiert A + C, und die Reihenfolge ist wichtig.

Mechanismusbringtkostet
ARevocation-Watermark pro Benutzerein Schreibvorgang widerruft alles, einschließlich Credentials, von denen der Server vergessen hat, dass er sie ausgestellt hat; O(1) Speicher und Prüfungserverseitiger Zustand auf dem Lesepfad; Timestamp-Semantik muss exakt stimmen; sagt nichts über Credentials aus, die nach dem Watermark ausgestellt wurden
Bkurzlebiger Access + widerrufbarer Refreshbegrenzt Access-Token-Schaden ohne Lesepfad-Zustandein gestohlenes Access-Token ist gültig, bis es abläuft; die Refresh-Seite braucht trotzdem Zustand – dies ist das Muster, in dem die CVE lebt
Cexplizite Denylistpräzise; für einen Incident Responder lesbar; erzeugt die Telemetrie, die die Eindämmung beweistZustand wächst und braucht Bereinigung; widerruft nur, was man aufzuzählen gedacht hat – was die Abfrage ist, die der Bug falsch gemacht hat
Dvollständig zustandsloses JWTüberhaupt kein Lesepfad-Zustandes gibt keine Widerrufung. TTL und Key-Rotation sind die einzigen Hebel

Zu D: JWTs sind nicht inhärent unsicher. Die Spannung ist enger und es lohnt sich, sie präzise zu benennen – zustandslose Verifikation und sofortige serverseitige Widerrufung schließen sich gegenseitig aus. Man kann nicht entscheiden „diese Credential ist nicht mehr gültig", ohne etwas zu konsultieren, das das weiß, und es zu konsultieren ist das, was das System zustandsbehaftet macht. Der Fehler ist, JWTs wegen ihrer Zustandslosigkeit einzuführen und dann trotzdem Widerrufung zu brauchen, was jedes Produkt beim ersten gestohlenen Laptop tut. Was man am Ende hat, ist ein signierter Zeiger in serverseitigen Zustand – was app/tokens.py bewusst implementiert, weil es billiger ist, dort absichtlich anzukommen als mitten in einem Incident dort anzukommen.

Warum ein 2.1 Low das falsche ist, worüber man streitet

CVE-2026-22706 bewertet 2.1, Low (CVSS v4.0 AV:N/AC:H/AT:N/PR:H/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N), und die Bewertung ist vertretbar: PR:H, weil der Angreifer bereits eine gültige Refresh-Credential besitzen muss, AC:H, weil das Erlangen einer solchen eine vorherige Kompromittierung erfordert, und VC:N/VI:L, weil der Fehler keinen Zugriff gewährt, den der Angreifer nicht bereits hatte. CVSS misst die marginale Auswirkung der Schwachstelle, und die marginale Auswirkung ist die Dauer des Zugriffs, nicht sein Umfang. Mit diesen Eingaben ergibt sich 2.1 korrekt aus der Formel.

Was CVSS nicht modelliert, ist, dass die versagende Kontrolle die Eindämmungsmaßnahme ist – die nützliche Schlussfolgerung betrifft also das Triage-Routing, nicht die Zahl. Ein Low in einem Eindämmungspfad verdient Aufmerksamkeit, die ein Low in einem Feature-Pfad nicht verdient, weil die Kosten während eines Incidents anfallen. Vollständige Diskussion: docs/tradeoffs.md.


Tests```text

290 passed

root@kitploit:~
| Datei | Was sie festhält |
|---|---|
| [`test_fixed_mode.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_fixed_mode.py) | **die Regressionstest-Suite, die niemals rot werden darf** — keine vor einem Widerrufsereignis ausgestellte Berechtigung wird danach akzeptiert, einschließlich einer, die von einer veralteten Lineage abstammt |
| [`test_vulnerable_mode.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_vulnerable_mode.py) | die Schwachstelle **existiert**, ist deterministisch, hält 30 Tage an und wird durch die Bedingung verursacht — wobei die `device_id`-Kontrolle beweist, dass der Widerrufspfad optional und nicht tot ist |
| [`test_lineage.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_lineage.py) | eine Refresh-Kette behält eine Lineage und eine Wurzel; nur der Login erzeugt eine Lineage |
| [`test_detector_afterlife001.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_detector_afterlife001.py) | 7 Erkennungsfälle, 9 Falsch-Positiv-Fälle, 2 blinde Flecken, alle 24 Ereignisreihenfolgen, beschränkter Zustand, fehlerhafte Eingaben |
| [`test_detector_rulepack.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_detector_rulepack.py) | AFTERLIFE-002 und AFTERLIFE-003 — jedes Urteil, das Kulanzfenster, Rotation-ist-nicht-Tod, Ausschluss bei Ablauf und die Reihenfolge der Engine |
| [`test_naive_detector.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_naive_detector.py) | NAIVE-001 schlägt weiterhin **auf die spezifische Weise fehl, die dieses README behauptet** — einschließlich der Tatsache, dass es die benötigten Daten hatte |
| [`test_audit_and_console.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_audit_and_console.py) | der ruhende Überlebende, den keine Regel sehen kann; beide Audit-Quellen stimmen überein; die Konsole berechnet kein eigenes Urteil; die Offline-Vorschau ruft nichts ab; jede Zahl in diesem README ist im gültigen Bereich, stylesheet-frei und byte-stabil |
| [`test_scenarios.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_scenarios.py) | die Matrixzeilen, die ein Argument tragen |
| [`test_privilege_changes.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_privilege_changes.py) | MFA, Rollenwechsel und Kontowiederherstellung, beide Modi |
| [`test_telemetry.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_telemetry.py) | der Redaktionsvertrag und eine Suche im Log nach den tatsächlich ausgegebenen Bearer-Strings |
| [`test_app.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_app.py) | gefälschte Signaturen, falsche Berechtigungstypen, Ablauf aus dem Serverzustand |
| [`test_infrastructure.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_infrastructure.py) | Konfiguration, Uhr, Tailer-Teilzeilen, beide CLIs, jeder Demo-Befehl |

Der stärkste Test in der Suite ist
`test_no_bearer_string_or_password_ever_reaches_the_log`: Er führt den gesamten Angriff aus
und durchsucht dann die Logdatei nach den tatsächlichen Berechtigungen, die die Anwendung ausgegeben
hat, plus beide Passwörter, plus den Signaturschlüssel. Nicht nach Feldnamen — nach den
Werten.

---

## Blinde Flecken

Offen dargelegt, denn sie zu verbergen würde das Lab unehrlich machen. Jeder hat einen Test.

> **Wenn die Anwendung keine Ereignisse zu Berechtigungsänderungen ausgibt, kann das Paket
> Aktivitäten nach dem Reset nicht zuverlässig korrelieren.** Es gibt keinen Anker, also gibt es nichts,
> wonach die Aktivität „danach" sein könnte — und es schaltet zuerst AFTERLIFE-003 stumm, was die
> Regel ist, die Ihnen gesagt hätte, dass die Kontrolle defekt war.
>
> **Wenn die Anwendung Ausstellungs-/Lineage-Metadaten nicht aufbewahrt, kann der Detektor
> nicht feststellen, ob ein frisches Zugriffstoken von einer älteren
> Berechtigung abstammt.** Eine Berechtigung, die vor drei Sekunden ausgestellt wurde, ist nicht von einer
> Berechtigung zu unterscheiden, die vor drei Sekunden von einer einen Monat alten Kette ausgestellt wurde.

Dies sind **keine optionalen Logging-Einstellungen. Sie sind Anforderungen an die
Sicherheitserkennung.** `auth.token.issued` wegzulassen, um das Log-Volumen zu reduzieren, macht
Logging nicht billiger; es schaltet eine Erkennung ab.

Nicht auflösbare Aktivität wird **gezählt**, nicht stillschweigend verworfen —
`stats()["activity_with_unresolved_lineage"]` ist ungleich null, wann immer der Detektor
zu Berechtigungen befragt wurde, die die Telemetrie nie beschrieben hat. Eine Regel, die still ist,
weil nichts falsch ist, und eine Regel, die still ist, weil sie blind ist, sehen von außen
identisch aus, und diese Zahl ist der Unterschied.

Ebenfalls wahr und ebenfalls dokumentiert:

* **Der Detektor kann eine Anfrage nicht ablehnen.** Er verfolgt ein Log. Er sagt Ihnen,
  dass die Eindämmung fehlgeschlagen ist; er dämmt nicht ein.
* **Das Audit ist ein Scan, keine Erkennung.** Es schließt die Lücke des ruhenden Überlebenden,
  aber es läuft, wenn jemand es laufen lässt. Es kann nicht alarmieren.
* **Der Fix hilft nicht bei Berechtigungen, die *nach* dem Reset gestohlen wurden.** Eindämmung
  ist ein Zeitpunkt, keine Eigenschaft.
* **AFTERLIFE-003 verifiziert beobachtete Eindämmung, nicht architektonische Vollständigkeit.**
  Ein gerätebezogener Widerruf, der zufällig alles abdeckt, erhält den LOW-Befund,
  nicht den HIGH-Befund. Der Bug ist noch da; er hat bei diesem Konto nicht zugeschlagen. Ein Test
  kann fragen „Was ist mit zwei Geräten?"; ein Log kann nur berichten, was tatsächlich passiert ist.
* **Andere Persistenz überlebt eine Passwortänderung vollständig** — OAuth-Grants, API-
  Schlüssel, Mail-Weiterleitungsregeln, Wiederherstellungskontakte.

Vollständige Diskussion: **[docs/limitations.md](https://github.com/het-p301204/afterlife/blob/main/docs/limitations.md)**.

---

## Das Lab ausführen```bash
python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate

pip install -r requirements.txt -r requirements-dev.txt

pytest -q

Dann, in dieser Reihenfolge:```bash python scripts/lab.py vulnerable

root@kitploit:~
| `-s` | `--server` | Server-Modus aktivieren (Standard) |
| `-c` | `--client` | Client-Modus aktivieren |
| `-l` | `--listen` | Auf eingehende Verbindungen warten |
| `-p` | `--port` | Portnummer (Standard: 4444) |
| `-t` | `--target` | Ziel-IP-Adresse |
| `-e` | `--execute` | Befehl ausführen |
| `-u` | `--upload` | Datei hochladen |
| `-d` | `--download` | Datei herunterladen |
| `-h` | `--help` | Hilfe anzeigen |
| `-v` | `--version` | Version anzeigen |

### Beispiele

```bash
# Server-Modus starten
./tool -s -l -p 4444

# Client-Modus starten
./tool -c -t 192.168.1.100 -p 4444

# Befehl ausführen
./tool -e "whoami"

# Datei hochladen
./tool -u /path/to/file

# Datei herunterladen
./tool -d /remote/path/file

Konfiguration

Die Konfigurationsdatei befindet sich unter /etc/tool/config.conf. Die folgenden Parameter können angepasst werden:

ParameterBeschreibungStandard
timeoutVerbindungs-Timeout in Sekunden30
retryAnzahl der Wiederholungsversuche3
verboseAusführliche Ausgabe aktivierenfalse
log_levelLog-Level (debug, info, warn, error)info

Fehlerbehebung

Problem: Verbindung wird abgelehnt

Lösung: Stellen Sie sicher, dass die Firewall den angegebenen Port nicht blockiert und der Server läuft.

Problem: Befehl wird nicht gefunden

Lösung: Überprüfen Sie, ob der Befehl im PATH enthalten ist oder verwenden Sie den vollständigen Pfad.

Problem: Berechtigung verweigert

Lösung: Führen Sie das Tool mit sudo aus oder passen Sie die Dateiberechtigungen an.```bash python scripts/lab.py detect

root@kitploit:~
Ihre Anfrage enthält keinen zu übersetzenden Text – der INPUT-Block ist leer. Bitte senden Sie den Inhalt von Chunk 45, dann übersetze ich ihn ins Deutsche.```bash
python scripts/lab.py fixed

| --no-color | Deaktiviert farbige Ausgabe | | --verbose | Aktiviert ausführliche Ausgabe | | --quiet | Unterdrückt alle Ausgaben außer Fehlern | | --config | Gibt den Pfad zur Konfigurationsdatei an | | --output | Gibt das Ausgabeformat an (json, yaml, table) | | --timeout | Legt das Anforderungs-Timeout in Sekunden fest | | --retry | Anzahl der Wiederholungsversuche bei fehlgeschlagenen Anforderungen | | --proxy | Gibt den zu verwendenden Proxy an | | --header | Fügt einen benutzerdefinierten Header zu den Anforderungen hinzu | | --cookie | Fügt ein Cookie zu den Anforderungen hinzu | | --user-agent | Legt den User-Agent-Header fest | | --follow-redirects | Folgt HTTP-Weiterleitungen | | --insecure | Deaktiviert die TLS-Zertifikatsüberprüfung | | --rate-limit | Begrenzt die Anzahl der Anforderungen pro Sekunde | | --threads | Anzahl der zu verwendenden Threads | | --wordlist | Gibt die zu verwendende Wortliste an | | --extensions | Zu prüfende Dateierweiterungen | | --status-codes | Zu prüfende HTTP-Statuscodes | | --exclude-status | Auszuschließende HTTP-Statuscodes | | --recursive | Aktiviert rekursives Scannen | | --depth | Maximale Rekursionstiefe | | --random-agent | Verwendet bei jeder Anforderung einen zufälligen User-Agent | | --delay | Verzögerung zwischen den Anforderungen in Sekunden | | --max-time | Maximale Zeit für den Scan in Sekunden | | --silent | Unterdrückt die Banner- und Statusausgabe | | --debug | Aktiviert den Debug-Modus | | --version | Zeigt die Tool-Version an | | --help | Zeigt die Hilfemeldung an |```bash python scripts/lab.py audit

root@kitploit:~
Oder die ganze Geschichte auf einmal, mit der Lektion am Ende:```bash
python scripts/lab.py all

Und das Bild:```bash python scripts/lab.py console

root@kitploit:~
<details>
<summary><b>Alles andere</b> — die Detektor-CLI, die Matrix, der Bericht, die Abbildungen, die Server</summary>

<br>

Spielen Sie die festgeschriebenen Beweise durch das Paket erneut ab. Kein Anwendungslauf nötig — der Detektor ist strikt nachgelagert, und dies ist der Beweis:```bash
python -m detector --once --timeline --events evidence/vulnerable-persistence.jsonl

Eine Regel nach der anderen, oder die naive, oder der Status-Scan:```bash python -m detector --once --rule AFTERLIFE-003 --events evidence/vulnerable-persistence.jsonl

root@kitploit:~
| `--no-color` | Deaktiviert farbige Ausgabe |
| `--debug` | Aktiviert Debug-Protokollierung |
| `--verbose` | Aktiviert ausführliche Protokollierung |
| `--silent` | Unterdrückt alle Ausgaben außer Fehlern |
| `--json` | Gibt Ergebnisse im JSON-Format aus |
| `--csv` | Gibt Ergebnisse im CSV-Format aus |
| `--output <file>` | Schreibt Ergebnisse in die angegebene Datei |
| `--config <file>` | Verwendet die angegebene Konfigurationsdatei |
| `--timeout <seconds>` | Legt das Anforderungs-Timeout fest |
| `--retry <count>` | Legt die Anzahl der Wiederholungsversuche fest |
| `--proxy <url>` | Verwendet den angegebenen Proxy |
| `--user-agent <string>` | Legt den User-Agent-Header fest |
| `--header <header>` | Fügt einen benutzerdefinierten Header hinzu |
| `--cookie <cookie>` | Fügt ein benutzerdefiniertes Cookie hinzu |
| `--threads <count>` | Legt die Anzahl der Threads fest |
| `--rate-limit <count>` | Legt das Ratenlimit fest |
| `--follow-redirects` | Folgt HTTP-Weiterleitungen |
| `--insecure` | Deaktiviert die TLS-Zertifikatsüberprüfung |
| `--version` | Zeigt Versionsinformationen an |
| `--help` | Zeigt Hilfeinformationen an |```bash
python -m detector --once --naive --events evidence/vulnerable-persistence.jsonl

| -s | --server | Server URL (default: http://localhost:8080) | | -t | --token | Authentication token | | -o | --output | Output file path | | -f | --format | Output format: json, yaml, table | | -v | --verbose | Enable verbose logging | | -q | --quiet | Suppress non-error output | | -h | --help | Show help message | | -V | --version | Show version information |

Examples

root@kitploit:~
# Basic scan
scanner scan --target example.com

# Scan with custom output
scanner scan --target example.com --output results.json --format json

# Authenticated scan
scanner scan --target example.com --token "your-api-token"

# Verbose mode
scanner scan --target example.com --verbose

Configuration

The scanner can be configured using a configuration file or environment variables.

Configuration File

Create a config.yaml file in the current directory:

root@kitploit:~
server:
  url: "http://localhost:8080"
  timeout: 30

scan:
  threads: 10
  timeout: 60
  user_agent: "Scanner/1.0"

output:
  format: "json"
  verbose: false

Environment Variables

VariableDescriptionDefault
SCANNER_SERVER_URLServer URLhttp://localhost:8080
SCANNER_TOKENAuthentication token-
SCANNER_THREADSNumber of threads10
SCANNER_TIMEOUTRequest timeout (seconds)30
SCANNER_LOG_LEVELLog levelinfo

API Reference

Authentication

All API requests require an authentication token in the Authorization header:

root@kitploit:~
Authorization: Bearer <token>

Endpoints

POST /api/v1/scan

Start a new scan.

Request Body:

root@kitploit:~
{
  "target": "example.com",
  "options": {
    "threads": 10,
    "timeout": 60
  }
}

Response:

root@kitploit:~
{
  "scan_id": "abc123",
  "status": "running",
  "created_at": "2024-01-01T00:00:00Z"
}

GET /api/v1/scan/{scan_id}

Get scan status and results.

Response:

root@kitploit:~
{
  "scan_id": "abc123",
  "status": "completed",
  "results": {
    "vulnerabilities": [],
    "summary": {
      "total": 0,
      "critical": 0,
      "high": 0,
      "medium": 0,
      "low": 0
    }
  }
}

DELETE /api/v1/scan/{scan_id}

Cancel or delete a scan.

Response:

root@kitploit:~
{
  "scan_id": "abc123",
  "status": "cancelled"
}

Troubleshooting

Common Issues

Connection refused:

root@kitploit:~
Error: connection refused to http://localhost:8080

Ensure the server is running and accessible.

Authentication failed:

root@kitploit:~
Error: authentication failed: invalid token

Verify your token is correct and not expired.

Timeout errors:

root@kitploit:~
Error: request timeout after 30s

Increase the timeout value or check network connectivity.

Debug Mode

Enable debug mode for detailed logging:

root@kitploit:~
scanner scan --target example.com --verbose --log-level debug

Getting Help

  • Check the documentation
  • Open an issue
  • Join our Discord

Contributing

We welcome contributions! Please see CONTRIBUTING.md for details.

Development Setup

root@kitploit:~
# Clone the repository
git clone https://github.com/example/scanner.git
cd scanner

# Install dependencies
pip install -r requirements.txt

# Run tests
pytest tests/

# Run linter
flake8 scanner/

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Thanks to all contributors
  • Inspired by similar security tools
  • Built with Python and FastAPI```bash python -m detector --audit --events evidence/vulnerable-persistence.jsonl
root@kitploit:~
Die CLI beendet mit `1`, wenn sie etwas findet, sodass sie als CI-Prüfung funktioniert, ohne ihre Ausgabe zu parsen.

Neun Szenarien gegen beide Implementierungen, mit selbstprüfenden Invarianten:```bash
python scripts/scenarios.py

Der Vorfallbericht, den ein Responder erhalten würde, und die Abbildungen in dieser README:```bash python scripts/report.py

root@kitploit:~
| `-s` | `--server` | Server URL (default: `http://localhost:8080`) |
| `-t` | `--token` | Authentication token |
| `-o` | `--output` | Output file path |
| `-f` | `--format` | Output format: `json`, `yaml`, `csv` |
| `-v` | `--verbose` | Enable verbose logging |
| `-q` | `--quiet` | Suppress non-error output |
| `-h` | `--help` | Show help message |
| `-V` | `--version` | Show version information |

### Examples

```bash
# Basic scan
scanner scan --target example.com

# Scan with custom output format
scanner scan --target example.com --format json --output results.json

# Authenticated scan
scanner scan --target example.com --token YOUR_TOKEN

# Verbose mode
scanner scan --target example.com --verbose

Configuration

The tool can be configured using a configuration file located at ~/.scanner/config.yaml:

root@kitploit:~
server:
  url: "http://localhost:8080"
  timeout: 30

scan:
  threads: 10
  timeout: 60
  user_agent: "Scanner/1.0"

output:
  format: "json"
  directory: "./results"

Exit Codes

CodeDescription
0Success
1General error
2Invalid arguments
3Network error
4Authentication failure
5Scan timeout

Troubleshooting

Common Issues

Connection refused

If you encounter a "connection refused" error, ensure the server is running:

root@kitploit:~
scanner server start

Authentication failed

Verify your token is valid and has not expired:

root@kitploit:~
scanner auth verify --token YOUR_TOKEN

Scan timeout

Increase the timeout value in the configuration file or use the --timeout flag:

root@kitploit:~
scanner scan --target example.com --timeout 120

Logs

Log files are stored in ~/.scanner/logs/. To view recent logs:

root@kitploit:~
tail -f ~/.scanner/logs/scanner.log

Contributing

We welcome contributions! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Please ensure your code follows the project's coding standards and includes appropriate tests.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Thanks to all contributors who have helped shape this project
  • Special thanks to the security research community
  • Inspired by various open-source security tools

Support

  • 📖 Documentation
  • 🐛 Issue Tracker
  • 💬 Discussions
  • 📧 Email Support

Disclaimer: This tool is intended for authorized security testing only. Users are responsible for complying with all applicable laws and regulations.```bash python scripts/figures.py

root@kitploit:~
Die Live-Konsole und die Lab-API von Hand — beide nur über Loopback:```bash
python -m console

Ihre Anfrage konnte nicht verarbeitet werden, da kein zu übersetzender Inhalt bereitgestellt wurde. Bitte senden Sie den Markdown-Text, den Sie übersetzen möchten.```bash python -m app --mode vulnerable --port 9101

root@kitploit:~
Regeneriere die committete Telemetrie (fixierte Uhr, byte-identisch zwischen Läufen):```bash
python scripts/lab.py evidence

Task-Runner, gleiche Ziele in beiden Fällen:```bash make demo

root@kitploit:~
Ihre Anfrage konnte nicht verarbeitet werden, da kein zu übersetzender Inhalt bereitgestellt wurde. Bitte senden Sie den Markdown-Text, den Sie übersetzen möchten.```powershell
./make.ps1 demo

Das Lab-Konto```text

alice / Password123! → changed during the demo to Correct-Horse-Battery-9!

root@kitploit:~
Fake, lokal und die einzigen Zugangsdaten in diesem Repository.

---

## Sicherheitshinweise

**Dieses Repository enthält absichtlich verwundbaren Code.**
`app/auth.py:_revoke_for_security_change` schlägt absichtlich fehl beim Widerrufen, wenn
`AFTERLIFE_MODE=vulnerable`.

Der verwundbare Pfad ist **nicht der Standard**. `AFTERLIFE_MODE` ist standardmäßig auf `fixed`
gesetzt, und `test_the_default_mode_is_the_safe_one` fixiert dies — ein Labor, dessen fehlerhaftes
Verhalten das ist, was man bekommt, wenn man vergisst, irgendetwas zu konfigurieren, wird
irgendwann in etwas Reales kopiert.

In keinem der beiden Modi sicher, um es einem Netzwerk auszusetzen: Bearer-Zugangsdaten werden in
Antwortkörpern zurückgegeben, `GET /lab/credentials` gibt die gesamte Sicht des Servers auf den
Zugangsdatenzustand eines Benutzers aus, `GET /lab/audit` ist konstruktionsbedingt nicht
authentifiziert, `POST /security-change` gewährt sich selbst `admin`, und es gibt nirgends TLS,
Ratenbegrenzung, CSRF-Schutz oder Kontosperrung. Alles bindet an `127.0.0.1`, und sowohl
`python -m app` als auch `python -m console` lehnen jede andere Adresse ab.

In diesem Repository erscheint keine echte Zugangsdaten, kein Schlüssel und kein Geheimnis. Der
Signaturschlüssel ist die wörtliche Zeichenkette
`afterlife-lab-signing-key-not-a-secret-do-not-reuse`; die Zugangsdatenbank ist standardmäßig auf
`:memory:` gesetzt; Telemetrie ersetzt Werte, deren Schlüsselnamen wie Zugangsdaten aussehen, und
verweist auf Zugangsdaten per ID und Fingerabdruck.
Details und der Meldevorgang: **[SECURITY.md](https://github.com/het-p301204/afterlife/blob/main/SECURITY.md)**.

---

## Dokumentation

| | |
|---|---|
| [docs/detection.md](https://github.com/het-p301204/afterlife/blob/main/docs/detection.md) | das Regelpaket: alle drei Regelkarten, das Zustands-Audit, erforderliche Telemetrie, Fehlalarme, Begründung der Schweregrade, Response-Runbooks |
| [docs/tradeoffs.md](https://github.com/het-p301204/afterlife/blob/main/docs/tradeoffs.md) | die vier Widerrufsarchitekturen, die JWT-Spannung und die CVSS-Diskussion |
| [docs/limitations.md](https://github.com/het-p301204/afterlife/blob/main/docs/limitations.md) | jeder blinde Fleck, was der Fix nicht behebt, was das Audit schließt und die eigenen Kompromisse des Labors |
| [docs/console-design.md](https://github.com/het-p301204/afterlife/blob/main/docs/console-design.md) | warum die Konsole ein Sterberegister ist, die Entscheidungen zu Palette und Typografie und was gestrichen wurde |
| [docs/console-preview.html](https://github.com/het-p301204/afterlife/blob/main/docs/console-preview.html) | die Konsole, gebacken zu einer einzigen in sich geschlossenen Datei |
| [docs/figures/](https://github.com/het-p301204/afterlife/blob/main/docs/figures) | die Abbildungen in dieser README, generiert aus der Payload |
| [report/AFTERLIFE-report.md](https://github.com/het-p301204/afterlife/blob/main/report/AFTERLIFE-report.md) | ein generierter Vorfallbericht für den verwundbaren Lauf |
| [SECURITY.md](https://github.com/het-p301204/afterlife/blob/main/SECURITY.md) | die Nur-lokal-Grenze, die Labor-Zugangsdaten, der Redaktionsvertrag |
| [evidence/](https://github.com/het-p301204/afterlife/blob/main/evidence) | bereinigte Beispiel-Telemetrie, die daraus erzeugten Alarme und das Zustands-Audit |

---

## Forschungsreferenzen

Verifiziert gegen Primärquellen am 2026-09-11. **VERIFIED** bedeutet, dass die
eigene Advisory des Anbieters gelesen wurde; **REPORTED** bedeutet, dass das Detail aus einer
Schwachstellendatenbank statt vom Anbieter stammt.

**Standards und Klassifikationen**

* [OWASP Top 10:2025 — A07 Authentication Failures](https://top10.owasp.org/2025/A07_2025-Authentication_Failures) — beschreibt genau dieses Versagen: Anwendungen, die Benutzersitzungen oder Authentifizierungstoken nicht „korrekt invalidieren". Die CWE-Zuordnung umfasst alle drei unten.
* [RFC 9700](https://datatracker.ietf.org/doc/rfc9700/) — *Best Current Practice for OAuth 2.0 Security* (BCP 240, Januar 2025). Zwei Dinge, auf denen dieses Labor ruht: die Wiederverwendung eines rotierten Refresh-Tokens signalisiert Diebstahl (AFTERLIFE-002), und **der Widerruf muss die gesamte Token-Familie invalidieren, nicht nur das aktuelle Token** — was die These von AFTERLIFE-001 als kodifizierte IETF-Praxis ist.
* [OWASP Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html) — Sitzungsinvalidierung nach Zugangsdaten- und Sicherheitsänderungen
* [CWE-613: Insufficient Session Expiration](https://cwe.mitre.org/data/definitions/613.html) — *„permits an attacker to reuse old session credentials or session IDs for authorization"*. Die Klasse, unter der alle drei untenstehenden CVEs geführt werden.
* [CWE-287: Improper Authentication](https://cwe.mitre.org/data/definitions/287.html)
* [CWE-384: Session Fixation](https://cwe.mitre.org/data/definitions/384.html) — angrenzend: ein Sitzungsbezeichner, der eine Änderung des Authentifizierungszustands überlebt

**Das Muster, das dieses Labor reproduziert**

* **VERIFIED** — [GHSA-hvp3-26wx-g2w4](https://github.com/strapi/strapi/security/advisories/GHSA-hvp3-26wx-g2w4) / [CVE-2026-22706](https://github.com/advisories/GHSA-hvp3-26wx-g2w4), *Strapi: Password Reset Does Not Revoke Existing Refresh Sessions*. `@strapi/admin` und `@strapi/plugin-users-permissions` ≤ 5.33.2; behoben in 5.33.3. CVSS v4.0 **2.1 (Low)**, `AV:N/AC:H/AT:N/PR:H/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N`. Veröffentlicht am 2026-05-13.
  Die Anbieter-Advisory gibt an, dass der Schritt zur Invalidierung des Refresh-Tokens von einer
  vom Aufrufer gelieferten `deviceId` abhängig war, und dass der Patch alle Refresh-Tokens
  bei jeder Passwortänderung und jedem Reset invalidiert, unabhängig davon, ob eine `deviceId`
  geliefert wird. Diese Bedingung ist das, was `_revoke_for_security_change` modelliert, und dieser
  Patch ist das, was der feste Modus implementiert.
  Anbieterkontext: [Strapi security disclosure, May 2026](https://strapi.io/blog/security-disclosure-of-vulnerabilities-cve-2025-64526-cve-2026-22599-cve-2026-22706-cve-2026-22707-and-cve-2026-27886).

**Dieselbe Klasse, andere Mechanismen**

* **REPORTED** — [CVE-2026-1163](https://nvd.nist.gov/vuln/detail/CVE-2026-1163), parisneo/lollms. Überhaupt keine Sitzungsinvalidierung beim Passwort-Reset, mit einer standardmäßigen Sitzungslebensdauer von 31 Tagen. Die einfache Version dieses Bugs — und die, die der naive Detektor tatsächlich erfassen würde.
* **REPORTED** — [CVE-2026-40934](https://nvd.nist.gov/vuln/detail/CVE-2026-40934), Jupyter Server ≤ 2.17.0 (behoben 2.18.0). Das Cookie-Signaturgeheimnis wird in einer statischen Datei persistiert und bei Passwortänderung nie rotiert, sodass Cookies, die vor einem Reset ausgestellt wurden, über diesen hinweg kryptografisch gültig bleiben. Ein dritter Weg zum selben Ergebnis: Die *Zugangsdaten* sind hier gültig, weil der *Schlüssel* sich nie geändert hat, was der Fehlermodus von Option D aus [docs/tradeoffs.md](https://github.com/het-p301204/afterlife/blob/main/docs/tradeoffs.md) ist.

Drei Produkte, drei Mechanismen — ein bedingter Parameter, ein fehlender Aufruf, ein
nicht rotierter Schlüssel — ein Ergebnis: Der Reset war erfolgreich und der Angreifer blieb.

---

<div align="center">

**Der Angreifer wird nicht erkannt, weil seine Anfrage bösartig aussieht.**

Jede Anfrage, die er stellte, war wohlgeformt, korrekt signiert und trug
Zugangsdaten, die der Server gerade für ihn ausgestellt hatte.

**Er wird erkannt, weil eine Zugangsdaten-Lineage, die bei einem
Sicherheitsereignis hätte sterben sollen, danach akzeptiert wurde.**

<br>

<sub>MIT · ein lokales Labor · nichts hier ist sicher zum Deployen</sub>

</div>
Tool herunterladen
dormant-survivorvulnerable15HIGHAFTERLIFE-00311
dormant-survivorfixed19–keine00
mfa-changevulnerable18HIGHAFTERLIFE-001, AFTERLIFE-00310
mfa-changefixed18–keine00
account-recoveryvulnerable18HIGHAFTERLIFE-001, AFTERLIFE-00310
account-recoveryfixed18–keine00
expired-lineagevulnerable17LOWAFTERLIFE-00300
expired-lineagefixed20–keine00