Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
AfterLife — Laboratorio di rilevamento della persistenza della revoca: quando la reimpostazione della password riesce ma l'attaccante non se ne va mai. Riproduce il bug di revoca condizionale Strapi CVE-2026-22706, la sua correzione, un pacchetto di rilevamento a tre regole e la regola ingenua che non lo rileva. | Kitploit
Strumenti/GitHubGitHub/het-p301204/afterlife
Strumenti DifensiviAnalisi delle VulnerabilitàSicurezza WebAutenticazioneApprendimento e FormazioneRed TeamingRisposta agli IncidentiLab e Pratica
GitHubhet-p301204/afterlife

AfterLife

Vedi Repository
12h 41m faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →

Informazioni

Laboratorio di rilevamento della persistenza della revoca: quando la reimpostazione della password riesce ma l'attaccante non se ne va mai. Riproduce il bug di revoca condizionale Strapi CVE-2026-22706, la sua correzione, un pacchetto di rilevamento a tre regole e la regola ingenua che non lo rileva.

Condividi

AFTERLIFE

Laboratorio di Rilevamento della Persistenza della Revoca

Quando la reimpostazione della password riesce ma l'attaccante non se ne va mai.

Un laboratorio locale red/blue per una singola classe di vulnerabilità: la credenziale che sopravvive all'evento che avrebbe dovuto eliminarla. Include l'exploit, la causa principale, la correzione, un pacchetto di rilevamento a tre regole, un audit di stato per ciò che le regole strutturalmente non possono vedere, una console forense — e la regola di rilevamento che non funziona, mantenuta nel repository per essere dimostrata fallire.

CI tests python rules OWASP CWE

license

Avvio rapido · La scoperta · Perché il rilevamento ingenuo fallisce · Il pacchetto di regole · Console · La correzione · Matrice · Test · Documentazione


Lo stesso attacco contro entrambe le implementazioni. Una barra per credenziale, dall'emissione alla morte, raggruppate in lignaggi. La linea tratteggiata è il cambio di password. In modalità vulnerabile cinque barre la attraversano e continuano; in modalità corretta ogni barra nel lignaggio rubato si ferma lì.

Lo stesso attacco. Le stesse richieste. Una sola differenza. Generato da scripts/figures.py dallo stesso payload che la console disegna — rigenerato e confrontato in CI, così una figura non può divergere dal codice.


L'azione di contenimento che non contiene```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:~
Nessun errore. Nessuna anomalia. Nessuna autenticazione fallita da contare. La credenziale di accesso dell'attaccante ha *tre secondi di vita* ed è stata generata dal server, su richiesta, dopo il reset.

Ecco l'intera vulnerabilità:```python
def _revoke_for_security_change(self, user, kind, device_id):
    if device_id:
        revoke_credentials(user, device_id=device_id)   # ← the finding

Leggilo come farebbe un revisore. La logica di revoca è proprio lì. Chiama la funzione giusta con lo scope giusto. L'endpoint attorno ad essa aggiorna l'hash della password, restituisce 200 ed emette una nuova sessione — ogni comportamento osservabile di un corretto cambio password è presente.

E se il chiamante omette device_id, nulla viene revocato, e l'endpoint riporta comunque successo.

Questo non è ipotetico. È CVE-2026-22706 in Strapi ≤ 5.33.2, dove il passo di invalidazione del refresh-token era condizionato a un deviceId fornito dal chiamante. Punteggio 2.1, Low. Discusso di seguito.


Perché questo è importante

Il punteggio è basso perché l'attaccante aveva già accesso — questa è la condizione di ingresso, e questo bug non concede nulla di nuovo. Ciò che concede è la durata, e lo fa rompendo l'unico controllo che la vittima può azionare da sola.

  • Ogni runbook di account-takeover inizia con reimposta la password. Ogni prodotto dice all'utente la stessa cosa.
  • Quando fallisce silenziosamente, alla vittima viene detto che il problema è risolto e smette di cercare — il che disattiva il segnale di rilevamento che conta di più nel account takeover: l'utente che se ne accorge.
  • La finestra di persistenza è la durata della credenziale di refresh: 30 giorni per impostazione predefinita, rinnovabile indefinitamente tramite rotazione. test_persistence_lasts_as_long_as_the_refresh_credential percorre sette giorni di tempo simulato per dimostrarlo.
  • Non c'è una seconda azione che l'utente possa intraprendere. Un secondo cambio password fa esattamente quanto il primo.

Un bug con punteggio Low in un percorso di contenimento costa più di un bug con punteggio Low in un percorso di funzionalità, perché il costo viene pagato durante un incidente, quando nessuno sta leggendo gli advisory.


Il rilevatore ingenuo

La regola che scrivi per prima:```text IF credential.issued_at < credential_change.timestamp: ALERT

root@kitploit:~
Non è una regola stupida. È economica, richiede un solo campo, si legge come la definizione
del problema, ed è **corretta nel caso semplice** — un attaccante che usa un
token di *accesso* rubato dopo il reset viene individuato.
`test_the_naive_rule_catches_the_simple_case` verifica che funzioni.

Entrambe le regole pongono la stessa domanda sulla stessa credenziale. Leggono campi
diversi per rispondere, e i due campi sono in disaccordo:

![Due righe, una per campo. credential.issued_at legge 10:00:03, tre secondi dopo la modifica, e conclude che la credenziale sembra pulita. lineage.root_issued_at legge 09:00:00, un'ora prima della modifica, e genera un avviso.](https://raw.githubusercontent.com/het-p301204/afterlife/main/docs/figures/two-fields.svg)

**Tre secondi dopo, o un'ora prima — la stessa credenziale, nello stesso
istante.** La regola ingenua interroga un campo controllato dall'attaccante, e un
singolo `POST /refresh` lo azzera.

Eseguila sul log delle richieste, che è dove questa query viene effettivamente scritta,
perché i log delle richieste sono ciò che hai a disposizione:```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.

Sarebbe facile fermarsi qui, e disonesto, quindi il laboratorio esegue anche la versione più equa della regola ingenua — ampliata per osservare anche l'endpoint di refresh:```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:~
Questa è la differenza che conta alle 3 del mattino. La regola ingenua cattura
l'istante singolo in cui la catena attraversa il confine, poi perde la traccia per i restanti
29 giorni — e la credenziale che nomina è *già stata ruotata e revocata
dal server*. Revocarla non serve a nulla. `lin-001` è l'oggetto che
devi eliminare.

**E alla regola ingenua non sono mancati i dati di telemetria.** Utilizza lo stesso flusso
di eventi, lo stesso indice di lineage, la stessa tolleranza, la stessa deduplicazione e
lo stesso stato limitato. Sovrascrive un solo metodo:```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 si trova nell'indice che sta già consultando. test_the_naive_rule_had_the_data_it_needed lo dimostra. Il fallimento è nel confronto, non nella registrazione.


Il pacchetto di regole

Tre regole su un unico log. Rispondono a domande diverse e si attivano nell'ordine in cui un incidente si svolge realmente.

regolagravitàrisponde asi attiva
AFTERLIFE-002Riuso di credenziali dopo il refreshCRITICAL / MEDIUMè stato rubato?al riuso
AFTERLIFE-003Revoca incompleta in un cambio di sicurezzaHIGH / LOWil contenimento è stato eseguito?al cambio — nessun attaccante necessario
AFTERLIFE-001Uso della discendenza di credenziali post-revocaHIGHè stata usata la discendenza obsoleta?alla prima accettazione
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:~
**Quell'ordinamento è la cosa più utile in questo repository.** AFTERLIFE-003
scatta nell'istante in cui la modifica viene applicata, tre secondi prima che l'attaccante tocchi
qualsiasi cosa, perché le prove di cui ha bisogno sono già complete: il log dice quali
lineage erano attivi in ingresso, e non dice che sono stati revocati.

Non ha bisogno di una vittima né di uno sfruttamento. Segnalerà il difetto al primo
reset di password che qualsiasi utente esegue — il che lo rende quello da eseguire in staging,
dove non c'è un attaccante da attendere. AFTERLIFE-001 ti dice che è in corso una violazione;
AFTERLIFE-003 ti dice che il tuo controllo di contenimento è rotto.

![Due schede di avviso. AFTERLIFE-003 alle 10:00:00 riporta verdetto no_containment, watermark non registrato, scope none, e lin-001 sopravvissuto. AFTERLIFE-001 alle 10:00:03 riporta la credenziale rt-002 con un root emesso un'ora prima della modifica.](https://raw.githubusercontent.com/het-p301204/afterlife/main/docs/figures/findings-vulnerable.svg)

`application recorded watermark: no` è il campo che indirizza un addetto alla risposta
agli incidenti verso il *difetto* piuttosto che verso il sintomo.

### Il watermark che le regole usano non è quello che l'applicazione riporta

L'evento di cambio credenziale porta un campo `revocation_watermark` — il
valore `credentials_valid_after` che l'applicazione ha scritto. **Nell'implementazione
vulnerabile è `null`, perché l'applicazione non ne ha mai scritto uno.** Questo è
il bug.

Quindi una regola basata su quel campo sarebbe cieca proprio al caso che esiste per
intercettare. Le regole si ancorano al `timestamp` dell'evento stesso, che è vero indipendentemente dal fatto che
l'applicazione abbia fatto il suo lavoro, e riportano il campo mancante come prova.

### Proprietà```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

L'indipendenza dall'ordine non è "funziona più o meno": test_6b_every_permutation_of_the_critical_events_detects esegue tutti i 24 ordinamenti dei quattro eventi che contano e richiede esattamente un avviso da ciascuno. La deduplicazione è basata su (user_id, credential_change_event_id, lineage_id), con la soppressione di event_id davanti, così un file riprodotto non può gonfiare il conteggio.

Schede complete delle regole, telemetria richiesta e runbook di risposta: docs/detection.md.


La console

Una superficie di lettura per l'unica domanda attorno a cui ruota il laboratorio. Non una dashboard di conteggi di avvisi — un registro di mortalità. Una barra per credenziale, dall'emissione alla morte, raggruppata nella discendenza da cui deriva, con il cambio password disegnato come una linea oltre la quale tutto ciò che la precedeva doveva finire.

La console AFTERLIFE completa in modalità vulnerabile: testata, il verdetto in serif, una striscia contatore a cinque celle, il confronto a due campi, il grafico della linea vitale con cinque barre che attraversano la ghigliottina, ed entrambe le schede di avviso.

Un'inversione deliberata: il calore significa vivo, e dopo la linea il calore è sbagliato. Nella maggior parte delle UI di sicurezza il rosso significa che si è verificato un errore. Qui nulla va in errore — ogni richiesta nell'esecuzione vulnerabile restituisce 200. Quindi il colore segue la mortalità invece: freddo è una credenziale che è morta quando le è stato detto di farlo, caldo è una che respira ancora, e oltre la ghigliottina, essere ancora calda è l'intera scoperta. (BLACKOUT stabiliva la stessa convenzione, dove 200 era quello rosso.)

I sottili tratti verticali sono la genealogia: una credenziale figlia coniata dal suo genitore in quell'istante. rt-001 → rt-002 → rt-004 scende verso destra lungo tutta la finestra, e in modalità vulnerabile continua a scendere dopo il taglio — che è l'immagine di una discendenza che conia nuove credenziali dall'altra parte del proprio evento di estinzione.```bash python scripts/lab.py console

root@kitploit:~
scrive **[`docs/console-preview.html`](https://github.com/het-p301204/afterlife/blob/main/docs/console-preview.html)** — un
file autonomo di 74 KiB con entrambe le esecuzioni incorporate. Nessun server, nessuna rete, nessun
font da scaricare; aprilo dal filesystem. Oppure esegui la versione live:```bash
python -m console

Trascina il cursore indietro oltre 10:00:00 e in avanti attraverso di esso: i risultati compaiono quando il log se li guadagna, AFTERLIFE-003 al cambiamento e AFTERLIFE-001 tre secondi dopo. Il ragionamento di progettazione, incluso ciò che è stato tagliato, è in docs/console-design.md.

La console è read-only per costruzione. Riesegue scenari conclusi e li disegna; non può cambiare l'implementazione, spostare l'orologio o revocare nulla. test_the_console_has_no_control_surface verifica che l'unica rotta non-GET sia /api/rebuild. Una superficie di lettura che può cambiare ciò che sta leggendo è una di cui non ci si può fidare.


Architettura

Sei pacchetti, una classe di sicurezza, nessuna infrastruttura.```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:~
L'applicazione viene eseguita **in-process** sotto la demo, la console e i test. Nessuna
porta, nessun dev server, nessun Docker. L'orologio è fissato, quindi ogni esecuzione è
deterministica e la telemetria, la console, le figure e i report committati sono tutti
byte-identici tra le esecuzioni — cosa che la CI verifica con `git diff --exit-code`.

### Applicazione lato server

Una credenziale è una **riga**, non una stringa. Il JWT che il client possiede contiene `cid` —
una chiave di ricerca — ed è la riga a decidere se la credenziale è viva.

La scadenza viene verificata rispetto a `expires_at` della riga, non rispetto al claim `exp`
presentato dal possessore. Entrambi i claim temporali vengono passati a PyJWT con `verify=False`,
che qui significa *"il server lo verifica da sé"*, e [`app/auth.py`](https://github.com/het-p301204/afterlife/blob/main/app/auth.py)
lo fa, su ogni richiesta. Il client non ha mai voce in capitolo sulla validità della propria
credenziale.

---

## Cronologia dell'attacco```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

Il ramo della catena di refresh nel mezzo è il punto centrale. L'attaccante non usa una vecchia credenziale. Usa una vecchia discendenza, e la discendenza conia qualcosa di nuovo su richiesta.


Discendenza dei token

Una sessione è la radice di una discendenza. Tutto ciò che viene coniato sotto di essa eredita quella radice, per sempre.```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` ha tre secondi. La sua ascendenza ha un'ora. Entrambi i fatti sono veri,
e solo uno di essi è visibile in un log delle richieste.

Due invarianti tengono insieme tutto questo, ed entrambe sono testate:

1. **Solo l'autenticazione crea una discendenza.** `Store.open_session` è l'unico
   posto in cui viene generato un `lineage_id`, e rende la sessione il proprio
   `root_credential_id`.
2. **Il minting copia la radice verso il basso.** `TokenService.mint` legge i campi della discendenza
   dal genitore invece di ricalcolarli, così una credenziale non può acquisire
   un'ascendenza più fresca del login da cui discende.

Un token presentato dal detentore deliberatamente **non** porta con sé la propria radice di discendenza. Se lo
facesse, il detentore potrebbe mentire al riguardo.

---

## L'audit dello stato

AFTERLIFE-001 scatta quando una discendenza obsoleta viene *usata*. Questo è il trigger giusto per
una regola di rilevamento, e lascia un buco: **una discendenza obsoleta che nessuno ha toccato è
invisibile ad essa.** Un attaccante che ruba una credenziale, osserva il fallimento del reset,
e poi aspetta, non produce alcuna attività da correlare.

Quindi il laboratorio pone anche la domanda che una regola non può porre: non *cosa è successo*, ma *cosa è
ancora vivo*.```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 | Disabilita l'output colorato | | --debug | Abilita l'output di debug | | --verbose | Abilita l'output dettagliato | | --silent | Sopprime tutto l'output non essenziale | | --json | Emette i risultati in formato JSON | | --csv | Emette i risultati in formato CSV | | --html | Emette i risultati in formato HTML | | --markdown | Emette i risultati in formato Markdown | | --output FILE | Scrive l'output su FILE | | --config FILE | Usa FILE di configurazione | | --profile NAME | Usa il profilo di configurazione NAME | | --timeout SECONDS | Imposta il timeout a SECONDS | | --retries N | Riprova le operazioni fallite N volte | | --concurrency N | Esegue N operazioni in parallelo | | --rate-limit N | Limita a N richieste al secondo | | --proxy URL | Usa il proxy URL | | --user-agent STRING | Imposta lo User-Agent a STRING | | --header HEADER | Aggiunge un header HTTP personalizzato | | --cookie COOKIE | Aggiunge un cookie | | --insecure | Disabilita la verifica del certificato TLS | | --follow-redirects | Segue i redirect HTTP | | --max-redirects N | Limita a N redirect | | --help | Mostra il messaggio di aiuto | | --version | Mostra le informazioni sulla versione |

Esempi

root@kitploit:~
# Esegue una scansione di base
tool scan example.com

# Esegue una scansione con output dettagliato
tool scan example.com --verbose

# Esegue una scansione e salva i risultati in JSON
tool scan example.com --json --output results.json

# Esegue una scansione con un file di configurazione personalizzato
tool scan example.com --config custom.yaml

# Esegue una scansione con un profilo specifico
tool scan example.com --profile aggressive

# Esegue una scansione con un timeout personalizzato
tool scan example.com --timeout 60

# Esegue una scansione con proxy
tool scan example.com --proxy http://127.0.0.1:8080

# Esegue una scansione con header personalizzati
tool scan example.com --header "Authorization: Bearer TOKEN"

# Esegue una scansione con un rate limit
tool scan example.com --rate-limit 10

# Esegue una scansione con concorrenza
tool scan example.com --concurrency 20

File di configurazione

Il tool supporta file di configurazione in formato YAML, JSON e TOML.

root@kitploit:~
# config.yaml
target: example.com
timeout: 30
retries: 3
concurrency: 10
rate_limit: 5
proxy: http://127.0.0.1:8080
user_agent: "Mozilla/5.0"
headers:
  Authorization: "Bearer TOKEN"
  X-Custom-Header: "value"
cookies:
  session: "abc123"
insecure: false
follow_redirects: true
max_redirects: 5
output: results.json
format: json
verbose: true
debug: false
silent: false

Profili

I profili consentono di salvare insiemi di opzioni predefinite.

root@kitploit:~
# profiles.yaml
default:
  timeout: 30
  retries: 3
  concurrency: 10

aggressive:
  timeout: 10
  retries: 1
  concurrency: 50
  rate_limit: 100

stealth:
  timeout: 60
  retries: 5
  concurrency: 1
  rate_limit: 1
  user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"

Variabili d'ambiente

VariabileDescrizione
TOOL_CONFIGPercorso del file di configurazione
TOOL_PROFILENome del profilo da usare
TOOL_TIMEOUTTimeout in secondi
TOOL_RETRIESNumero di tentativi
TOOL_CONCURRENCYLivello di concorrenza
TOOL_RATE_LIMITLimite di richieste al secondo
TOOL_PROXYURL del proxy
TOOL_USER_AGENTStringa User-Agent
TOOL_OUTPUTFile di output
TOOL_FORMATFormato di output
TOOL_VERBOSEAbilita l'output dettagliato
TOOL_DEBUGAbilita l'output di debug
TOOL_SILENTSopprime l'output non essenziale
TOOL_NO_COLORDisabilita l'output colorato

Codici di uscita

CodiceDescrizione
0Successo
1Errore generico
2Errore di utilizzo
3Errore di configurazione
4Errore di rete
5Errore di autenticazione
6Errore di autorizzazione
7Risorsa non trovata
8Timeout
9Interrotto dall'utente
10Errore interno

Esempi di output

Output JSON

root@kitploit:~
{
  "target": "example.com",
  "timestamp": "2024-01-01T00:00:00Z",
  "duration": 1.234,
  "results": [
    {
      "type": "vulnerability",
      "severity": "high",
      "title": "Example Vulnerability",
      "description": "Description of the vulnerability",
      "evidence": "Evidence of the vulnerability",
      "remediation": "How to fix the vulnerability"
    }
  ]
}

Output CSV

root@kitploit:~
type,severity,title,description,evidence,remediation
vulnerability,high,Example Vulnerability,Description of the vulnerability,Evidence of the vulnerability,How to fix the vulnerability

Output HTML

root@kitploit:~
<!DOCTYPE html>
<html>
<head>
  <title>Scan Results</title>
</head>
<body>
  <h1>Scan Results</h1>
  <table>
    <thead>
      <tr>
        <th>Type</th>
        <th>Severity</th>
        <th>Title</th>
        <th>Description</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>vulnerability</td>
        <td>high</td>
        <td>Example Vulnerability</td>
        <td>Description of the vulnerability</td>
      </tr>
    </tbody>
  </table>
</body>
</html>

Output Markdown

root@kitploit:~
# Scan Results

| Type | Severity | Title | Description |
|------|----------|-------|-------------|
| vulnerability | high | Example Vulnerability | Description of the vulnerability |

Integrazione

Docker

root@kitploit:~
# Esegue il tool in Docker
docker run --rm -v $(pwd):/data tool scan example.com

# Esegue il tool in Docker con un file di configurazione
docker run --rm -v $(pwd):/data tool scan example.com --config /data/config.yaml

Docker Compose

root@kitploit:~
version: '3'
services:
  tool:
    image: tool:latest
    volumes:
      - ./data:/data
    command: scan example.com --config /data/config.yaml

GitHub Actions

root@kitploit:~
name: Security Scan
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run security scan
        run: |
          docker run --rm -v $(pwd):/data tool scan example.com --config /data/config.yaml

GitLab CI

root@kitploit:~
stages:
  - scan

security-scan:
  stage: scan
  image: tool:latest
  script:
    - tool scan example.com --config config.yaml
  artifacts:
    paths:
      - results.json

Jenkins

root@kitploit:~
pipeline {
    agent any
    stages {
        stage('Security Scan') {
            steps {
                sh 'docker run --rm -v $(pwd):/data tool scan example.com --config /data/config.yaml'
            }
        }
    }
}

Risoluzione dei problemi

Problemi comuni

ProblemaSoluzione
Connessione rifiutataVerifica che il target sia raggiungibile e che la porta sia aperta
TimeoutAumenta il valore di --timeout
Errori di certificatoUsa --insecure per disabilitare la verifica del certificato
Rate limit superatoRiduci --rate-limit o aumenta --timeout
Memoria esauritaRiduci --concurrency
Autorizzazione negataVerifica di avere i permessi necessari
Comando non trovatoVerifica che il tool sia installato e nel PATH
File di configurazione non trovatoVerifica il percorso del file di configurazione
Profilo non trovatoVerifica il nome del profilo
Variabile d'ambiente non impostataVerifica che la variabile d'ambiente sia impostata

Debug

root@kitploit:~
# Abilita l'output di debug
tool scan example.com --debug

# Abilita l'output dettagliato
tool scan example.com --verbose

# Abilita entrambi
tool scan example.com --debug --verbose

Log

root@kitploit:~
# Scrive i log su un file
tool scan example.com --log-file tool.log

# Imposta il livello di log
tool scan example.com --log-level debug

Contribuire

I contributi sono benvenuti! Consulta le linee guida per i contributi.

  1. Fai il fork del repository
  2. Crea un branch per la funzionalità (git checkout -b feature/amazing-feature)
  3. Effettua il commit delle modifiche (git commit -m 'Add some amazing feature')
  4. Esegui il push sul branch (git push origin feature/amazing-feature)
  5. Apri una Pull Request

Licenza

Questo progetto è distribuito sotto la licenza MIT. Consulta il file LICENSE per i dettagli.

Ringraziamenti

  • Contributor 1
  • Contributor 2
  • Contributor 3

Contatti

  • Autore: Your Name
  • Email: [email protected]
  • Sito web: https://example.com

Badge

License Version Build Coverage Downloads Stars Forks Issues PRs Contributors Last Commit Language Platform Docker PyPI npm Go Rust Security Maintained Open Source Made with Love


Nota: Questo è un README di esempio per scopi dimostrativi. Sostituisci i segnaposto con le informazioni reali del tuo progetto.```bash python scripts/lab.py audit

root@kitploit:~
I'm sorry, but I don't see any content to translate in your message. You mentioned "INPUT:" but no actual Markdown content followed it.

Please provide the chunk 31 content you'd like me to translate from English to Italian, and I'll return only the translated Markdown text following all the rules you've specified.```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

Due fonti, deliberatamente: l'audit della telemetria vede ciò che il log può provare, l'audit dello stato del server vede ciò che il database ritiene. Quando sono in disaccordo, il log non è un registro fedele dello stato delle credenziali, e ogni rilevamento costruito su di esso è più debole di quanto sembri — perciò il comando stampa entrambi e lo segnala se differiscono.

L'audit dello stato del server necessita della tabella security_changes, che entrambe le modalità scrivono, perché registrare che un cambiamento è avvenuto è un obbligo separato dall'agire su di esso — e l'implementazione vulnerabile ne soddisfa esattamente uno dei due.


Matrice degli scenari

Nove scenari contro entrambe le implementazioni. python scripts/scenarios.py verifica le proprie due invarianti ed esce con codice diverso da zero se una delle due fallisce.

scenariomodalitàeventipeggioreregole attivatestaledormant
legitimate-onlyvulnerable19LOWAFTERLIFE-00300
legitimate-onlyfixed19–none00
stolen-refreshvulnerable22HIGHAFTERLIFE-001, AFTERLIFE-00310
stolen-refreshfixed23–none00
stolen-refresh-with-device-idvulnerable19LOWAFTERLIFE-00300
stolen-refresh-with-device-idfixed19–none00
multi-devicevulnerable29HIGHAFTERLIFE-001, AFTERLIFE-00320
multi-devicefixed29–none00
refresh-reusevulnerable11MEDIUMAFTERLIFE-00200
refresh-reusefixed15MEDIUMAFTERLIFE-00200

Quattro righe portano un argomento:

  • legitimate-only non contiene alcun attaccante. La correzione non produce nulla; l'implementazione vulnerabile produce un LOW. Non è un falso positivo — è enumeration_only: la revoca ha funzionato questa volta, enumerando, senza alcun watermark a coprire una credenziale che il server ha dimenticato. Il pacchetto separa le due implementazioni senza alcun attaccante presente.
  • dormant-survivor è il punto cieco e la sua risposta in una riga: AFTERLIFE-001 silenzioso, AFTERLIFE-003 HIGH, audit dormant: 1.
  • multi-device è dove il ramo funzionante dell'implementazione vulnerabile fallisce comunque — revoca limitata al laptop, due altri dispositivi intatti.
  • expired-lineage è il controllo dei falsi positivi: una sessione vecchia di 40 giorni le cui credenziali sono scadute da sole non è un percorso di persistenza sopravvissuto, e non è segnalata come tale.

E l'affermazione centrale della matrice, verificata dallo script e da test_the_fix_never_produces_a_control_failure_finding: la correzione non solleva alcun rilevamento di fallimento di controllo in nessuno scenario. AFTERLIFE-002 è consentito — segnala un furto, non un fallimento di controllo, e un'implementazione corretta ha comunque furti da segnalare.


Falsi positivi

Una regola che allerta a ogni reset di password viene silenziata entro una settimana, e una regola silenziata è peggio di nessuna regola — è una regola che tutti credono sia in esecuzione.

casoesitoperché
Cambio password, poi la sessione sostitutiva viene usatanessun avvisola radice della nuova discendenza è al watermark o successiva
Reset poi navigazione immediata (/me, /profile, /settings)nessun avvisouna discendenza fresca, una radice fresca
Telefono, laptop e tablet, tutti correttamente ruotatinessun avvisoogni login è la propria discendenza
Un dispositivo che ha effettuato l'accesso dopo il cambiamento, navigando insieme all'attaccantenessun avvisoradice fresca — e la discendenza stale allerta comunque, da sola
Una credenziale stale che è stata rifiutatanessun avvisoresult: failure è escluso; è una prova per la difesa
Sfasamento dell'orologio fino a 2s tra i componentinessun avvisola tolleranza documentata
Un cambio password fallitonessun avvisonon è un'ancora
Attività prima del cambiamentonessun avvisocontrollo dell'ordinamento temporale
Una credenziale ruotata (reason: rotated)non conteggiata come morteuna credenziale consumata, non una discendenza uccisa
Una sessione vecchia di 40 giorni le cui credenziali sono scadutenon un sopravvissutoscadenza tracciata per discendenza
Un primo cambio password su un account nuovo di zeccanessun avvisonulla era attivo in ingresso
Rotazione del refresh-token da una discendenza staleAVVISOtimestamp fresco, ascendenza stale — questo è il rilevamento

Ogni riga ha un test in tests/test_detector_afterlife001.py e tests/test_detector_rulepack.py.

Sulla tolleranza di 2 secondi. Ogni timestamp in questo laboratorio proviene da un unico processo e un unico orologio, quindi la tolleranza onesta è zero. 2s è ciò di cui necessita un deployment realistico su due host sotto NTP, ed è tre ordini di grandezza inferiore ai gap che questo attacco produce — lo scopo stesso di una credenziale di refresh è essere di lunga durata. La tolleranza è una finestra in cui una discendenza genuinamente stale è ignorata, quindi è mantenuta piccola di proposito: test_12b_a_gap_beyond_the_tolerance_does_alert verifica che 2.1s scatti comunque.


La correzione```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:~
Nessuno dei due dipende da `device_id`. Viene registrato come contesto e non ha alcuna influenza sul raggio d'impatto.

**Il watermark è la garanzia architetturale.** Un timestamp per utente,
`credentials_valid_after`, confrontato su ogni richiesta sia con l'emissione
della credenziale stessa *sia* con la sua radice di discendenza:```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

Il secondo confronto è quello che costa qualcosa per essere fatto bene, e quello che un'implementazione ingenua omette — nell'enforcement esattamente come nel rilevamento.

La revoca esplicita è difesa in profondità, e prova. revoked_at e revocation_reason sono ciò che un incident responder legge, e gli eventi auth.token.revoked sono ciò che prova che il contenimento è avvenuto.

test_the_watermark_alone_rejects_a_stale_credential imposta il watermark senza revocare nulla e richiede che la credenziale obsoleta venga rifiutata — il che stabilisce quale dei due è portante, e quale è la proprietà che vale ancora per le credenziali che il server ha dimenticato di aver emesso.

Lo stesso attacco, contro la correzione

La console in modalità corretta: il verdetto recita "Nothing issued before the change works after it", i contatori sono tutti verdi, ogni barra in lin-001 termina alla ghigliottina, e il pannello dei risultati non riporta alcun risultato con cinque controlli superati.

Non è cambiata una sola richiesta. È cambiata una funzione.

Non solo password

Il trigger non è "la password è cambiata". È "qualcosa è cambiato che rende inaffidabili le credenziali emesse in precedenza". Tutte e quattro le rotte condividono _revoke_for_security_change, quindi la correzione e il bug si applicano in modo identico a:

modificaperché le credenziali diventano inaffidabili
cambio / reset passwordil segreto con cui è stata stabilita la sessione non esiste più
registrazione o modifica MFAi fattori con cui è stata stabilita la sessione non sono i fattori dell'account
cambio di ruolo / escalation di privilegila credenziale è stata coniata sotto un'autorizzazione diversa
recupero accountper costruzione, l'account potrebbe essere stato nelle mani di qualcun altro un attimo fa
loginnon revoca gli altri — una nuova discendenza, non un'affermazione che le vecchie siano inaffidabili

Testato per ogni tipo, in entrambe le modalità, in tests/test_privilege_changes.py — incluso il non-requisito del login, documentato affinché il prossimo lettore non lo "corregga".


Compromessi architetturali

Quattro modi per far smettere di funzionare una credenziale. AFTERLIFE implementa A + C, e l'ordine conta.

meccanismocosa si guadagnacosa costa
Awatermark di revoca per utenteuna scrittura revoca tutto, incluse le credenziali che il server ha dimenticato; archiviazione e controllo O(1)stato lato server sul percorso di lettura; la semantica dei timestamp deve essere esattamente corretta; non dice nulla sulle credenziali emesse dopo il watermark
Baccesso a breve durata + refresh revocabilelimita il danno dell'access token senza stato sul percorso di letturaun access token rubato è valido fino alla scadenza; il lato refresh necessita comunque di stato — questo è il pattern in cui vive la CVE
Cdenylist esplicitaprecisa; leggibile per un incident responder; produce la telemetria che prova il contenimentolo stato cresce e necessita pulizia; revoca solo ciò che ti sei ricordato di enumerare — che è la query che il bug ha sbagliato
DJWT completamente statelessnessuno stato sul percorso di letturanon esiste revoca. TTL e rotazione delle chiavi sono le uniche leve

Su D: i JWT non sono intrinsecamente insicuri. La tensione è più ristretta e vale la pena enunciarla con precisione — la verifica stateless e la revoca immediata lato server sono mutuamente esclusive. Non puoi decidere "questa credenziale non è più valida" senza consultare qualcosa che lo sappia, e consultarlo è ciò che rende il sistema stateful. L'errore è adottare i JWT per la loro statelessness e poi aver comunque bisogno della revoca, cosa che ogni prodotto fa la prima volta che un laptop viene rubato. Quello con cui si finisce è un puntatore firmato verso lo stato lato server — che è ciò che app/tokens.py implementa deliberatamente, perché arrivarci di proposito costa meno che arrivarci nel mezzo di un incidente.

Perché un 2.1 Low è la cosa sbagliata di cui discutere

CVE-2026-22706 ha un punteggio di 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), e il punteggio è difendibile: PR:H perché l'attaccante deve già possedere una credenziale di refresh valida, AC:H perché ottenerne una richiede una compromissione precedente, e VC:N/VI:L perché la falla non concede alcun accesso che l'attaccante non avesse già. CVSS misura l'impatto marginale della vulnerabilità, e l'impatto marginale è la durata dell'accesso, non la sua portata. Dati questi input, 2.1 deriva correttamente dalla formula.

Ciò che CVSS non modella è che il controllo che fallisce è l'azione di contenimento — quindi la conclusione utile riguarda l'instradamento del triage, non il numero. Un Low in un percorso di contenimento merita attenzione che un Low in un percorso di funzionalità non merita, perché il costo si paga durante un incidente. Discussione completa: docs/tradeoffs.md.


Test```text

290 passed

root@kitploit:~
| file | cosa fissa |
|---|---|
| [`test_fixed_mode.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_fixed_mode.py) | **la suite di regressione che non deve mai diventare rossa** — nessuna credenziale emessa prima di un evento di revoca viene accettata dopo, inclusa una discendente da una lineage obsoleta |
| [`test_vulnerable_mode.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_vulnerable_mode.py) | la vulnerabilità **esiste**, è deterministica, dura 30 giorni ed è causata dal condizionale — con il controllo `device_id` che dimostra che il percorso di revoca è opzionale anziché morto |
| [`test_lineage.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_lineage.py) | una catena di refresh mantiene una lineage e una radice; solo il login crea una lineage |
| [`test_detector_afterlife001.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_detector_afterlife001.py) | 7 casi di rilevamento, 9 casi di falsi positivi, 2 punti ciechi, tutti i 24 ordinamenti di eventi, stato limitato, input malformato |
| [`test_detector_rulepack.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_detector_rulepack.py) | AFTERLIFE-002 e AFTERLIFE-003 — ogni verdetto, la finestra di tolleranza, rotation-is-not-death, l'esclusione della scadenza e l'ordinamento del motore |
| [`test_naive_detector.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_naive_detector.py) | NAIVE-001 continua a fallire **nel modo specifico che questo README dichiara** — incluso il fatto che aveva i dati di cui aveva bisogno |
| [`test_audit_and_console.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_audit_and_console.py) | il sopravvissuto dormiente che nessuna regola riesce a vedere; entrambe le fonti di audit concordanti; la console che non calcola alcun verdetto proprio; l'anteprima offline che non recupera nulla; ogni cifra in questo README entro i limiti, senza stylesheet e byte-stabile |
| [`test_scenarios.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_scenarios.py) | le righe della matrice che portano un argomento |
| [`test_privilege_changes.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_privilege_changes.py) | MFA, cambio di ruolo e recupero account, entrambe le modalità |
| [`test_telemetry.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_telemetry.py) | il contratto di redazione e un grep del log per le stringhe bearer letterali emesse |
| [`test_app.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_app.py) | firme falsificate, tipi di credenziale errati, scadenza dallo stato del server |
| [`test_infrastructure.py`](https://github.com/het-p301204/afterlife/blob/main/tests/test_infrastructure.py) | configurazione, orologio, righe parziali del tailer, entrambe le CLI, ogni comando demo |

Il test più forte della suite è
`test_no_bearer_string_or_password_ever_reaches_the_log`: esegue l'intero attacco
e poi cerca nel file di log le credenziali effettive che l'applicazione ha
distribuito, più entrambe le password, più la chiave di firma. Non per i nomi dei
campi — per i valori.

---

## Punti ciechi

Dichiarati apertamente, perché nasconderli renderebbe il laboratorio disonesto. Ognuno ha un test.

> **Se l'applicazione non emette eventi di cambio credenziale, il pacchetto non può
> correlare in modo affidabile l'attività successiva al reset.** Non c'è un'ancora,
> quindi non c'è nulla rispetto a cui l'attività possa essere "successiva" — e
> silenzia prima AFTERLIFE-003, che è la regola che ti avrebbe detto che il
> controllo era rotto.
>
> **Se l'applicazione non conserva i metadati di emissione/lineage, il rilevatore
> non può determinare se un access token fresco discende da una credenziale più
> vecchia.** Una credenziale coniata tre secondi fa è indistinguibile da una
> credenziale coniata tre secondi fa da una catena vecchia di un mese.

Queste **non sono preferenze di logging opzionali. Sono requisiti di rilevamento
di sicurezza.** Eliminare `auth.token.issued` per ridurre il volume dei log non
rende il logging più economico; disattiva un rilevamento.

L'attività irrisolvibile viene **contata**, non scartata silenziosamente —
`stats()["activity_with_unresolved_lineage"]` è diverso da zero ogni volta che al
rilevatore sono state chieste credenziali che la telemetria non ha mai descritto.
Una regola silenziosa perché non c'è nulla di sbagliato e una regola silenziosa
perché è cieca appaiono identiche dall'esterno, e quel numero è la differenza.

Altrettanto vero, e altrettanto documentato:

* **Il rilevatore non può rifiutare una richiesta.** Segue un log. Ti dice che il
  contenimento è fallito; non contiene.
* **L'audit è una scansione, non un rilevamento.** Colma il divario del
  sopravvissuto dormiente, ma viene eseguito quando qualcuno lo esegue. Non può
  inviare pagine.
* **La correzione non aiuta con le credenziali rubate *dopo* il reset.** Il
  contenimento è un punto nel tempo, non una proprietà.
* **AFTERLIFE-003 verifica il contenimento osservato, non la completezza
  architetturale.** Una revoca con ambito dispositivo che copre per caso tutto
  ottiene il risultato LOW, non quello HIGH. Il bug è ancora lì; non ha morso su
  quell'account. Un test può chiedere "e con due dispositivi?"; un log può solo
  riportare ciò che è effettivamente accaduto.
* **Altre persistenza sopravvive del tutto a un cambio di password** — grant
  OAuth, chiavi API, regole di inoltro mail, contatti di recupero.

Discussione completa: **[docs/limitations.md](https://github.com/het-p301204/afterlife/blob/main/docs/limitations.md)**.

---

## Eseguire il laboratorio```bash
python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate

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

pytest -q

Poi, in ordine:```bash python scripts/lab.py vulnerable

root@kitploit:~
I cannot translate this content because no text was provided in your message. The INPUT section is empty.

Please send the actual Markdown content for chunk 43, and I will translate it from English to Italian following all the rules you specified.```bash
python scripts/lab.py detect

I cannot translate this content. The input you've provided is empty—there is no Markdown text, code, or any other content following the "INPUT:" label.

Please paste the actual chunk 45 content you'd like translated from English to Italian, and I'll return only the translated Markdown with all structure, code, paths, and technical identifiers preserved exactly as required.```bash python scripts/lab.py fixed

root@kitploit:~
I cannot translate this content because no source text was provided. The INPUT section is empty.

Please send the actual Markdown content for chunk 47 of 79, and I will translate it from English to Italian following all the rules you specified.```bash
python scripts/lab.py audit

O l'intera storia in una volta, con la lezione alla fine:```bash python scripts/lab.py all

root@kitploit:~
E l'immagine:```bash
python scripts/lab.py console
Tutto il resto — la CLI del rilevatore, la matrice, il report, le figure, i server

Riproduci le prove committate attraverso il pacchetto. Non è necessaria alcuna esecuzione dell'applicazione — il rilevatore è strettamente a valle, e questa è la prova:```bash python -m detector --once --timeline --events evidence/vulnerable-persistence.jsonl

root@kitploit:~
Una regola alla volta, o quella ingenua, o la scansione dello stato:```bash
python -m detector --once --rule AFTERLIFE-003 --events evidence/vulnerable-persistence.jsonl

I cannot translate this content because no text was provided. The INPUT section is empty.

Please send the actual Markdown chunk you want translated, and I will return only the Italian translation with all structure preserved.```bash python -m detector --once --naive --events evidence/vulnerable-persistence.jsonl

root@kitploit:~
| `-s` | `--server` | `SERVER` | `http://localhost:8080` | URL del server |
| `-t` | `--token` | `TOKEN` | | Token di autenticazione |
| `-o` | `--output` | `FILE` | `stdout` | File di output |
| `-f` | `--format` | `FORMAT` | `json` | Formato di output |
| `-v` | `--verbose` | | | Output dettagliato |
| `-q` | `--quiet` | | | Modalità silenziosa |
| `-h` | `--help` | | | Mostra il messaggio di aiuto |
| `-V` | `--version` | | | Mostra la versione |

### Esempi

```bash
# Scansione di base
tool scan --target example.com

# Scansione avanzata con output
tool scan --target example.com --format json --output results.json

# Utilizzo di un file di configurazione
tool scan --config config.yaml

# Modalità dettagliata
tool scan --target example.com --verbose

Configurazione

File di configurazione

root@kitploit:~
# config.yaml
server:
  url: "http://localhost:8080"
  timeout: 30

scan:
  threads: 10
  timeout: 5
  user_agent: "Mozilla/5.0"

output:
  format: "json"
  verbose: false

Variabili d'ambiente

root@kitploit:~
export TOOL_SERVER_URL="http://localhost:8080"
export TOOL_TOKEN="your-token-here"
export TOOL_TIMEOUT="30"

Sviluppo

Prerequisiti

  • Python 3.8+
  • pip
  • git

Configurazione

root@kitploit:~
# Clona il repository
git clone https://github.com/example/tool.git
cd tool

# Crea un ambiente virtuale
python -m venv venv
source venv/bin/activate  # Linux/macOS
# oppure
venv\Scripts\activate  # Windows

# Installa le dipendenze
pip install -r requirements.txt

# Installa in modalità sviluppo
pip install -e .

Test

root@kitploit:~
# Esegui tutti i test
pytest

# Esegui con copertura
pytest --cov=tool --cov-report=html

# Esegui test specifici
pytest tests/test_scanner.py -v

Compilazione

root@kitploit:~
# Crea il pacchetto
python -m build

# Crea l'immagine Docker
docker build -t tool:latest .

# Esegui il container
docker run -it --rm tool:latest --help

Contribuire

  1. Fai il fork del repository
  2. Crea un branch per la funzionalità (git checkout -b feature/amazing-feature)
  3. Esegui il commit delle modifiche (git commit -m 'Add amazing feature')
  4. Esegui il push sul branch (git push origin feature/amazing-feature)
  5. Apri una Pull Request

Licenza

Questo progetto è distribuito sotto la licenza MIT - vedi il file LICENSE per i dettagli.

Ringraziamenti

  • Contributor 1
  • Contributor 2

Supporto

  • 📧 Email: [email protected]
  • 💬 Discord: Join our server
  • 🐛 Issues: GitHub Issues

Nota: Questo strumento è destinato esclusivamente a scopi di test di sicurezza autorizzati e ricerca. Gli utenti sono responsabili del rispetto di tutte le leggi applicabili.```bash python -m detector --audit --events evidence/vulnerable-persistence.jsonl

root@kitploit:~
La CLI esce con `1` quando trova qualcosa, quindi funziona come controllo CI senza
analizzare il suo output.

Nove scenari contro entrambe le implementazioni, con invarianti auto-verificanti:```bash
python scripts/scenarios.py

Il rapporto sull'incidente che verrebbe consegnato a un responder, e le figure in questo README:```bash python scripts/report.py

root@kitploit:~
I cannot translate this content because no source text was provided. The INPUT section is empty.

Please send the actual Markdown chunk (chunk 65 of 79) that you want translated from English to Italian, and I will return only the translated text with all Markdown structure, code, paths, URLs, and identifiers preserved exactly as required.```bash
python scripts/figures.py

La console live e l'API del laboratorio manualmente — entrambe solo su loopback:```bash python -m console

root@kitploit:~
I cannot translate this content because the input is empty. The message contains only the translation instructions and metadata, but no actual Markdown content from the README chunk to translate.

Please provide the source text (chunk 69 of 79) that you want translated from English to Italian, and I will return only the translated Markdown content following all the rules you specified.```bash
python -m app --mode vulnerable --port 9101

Rigenera la telemetria committata (clock fissato, identica byte per byte tra le esecuzioni):```bash python scripts/lab.py evidence

root@kitploit:~
Task runner, stessi target in entrambi i casi:```bash
make demo

I'm sorry, but I don't see any content to translate in your message. You mentioned "INPUT:" but no actual Markdown text was provided after it.

Please paste the chunk 75/79 content you'd like me to translate from English to Italian, and I'll return only the translated Markdown, preserving all structure, code, paths, URLs, and technical identifiers exactly as they appear.```powershell ./make.ps1 demo

root@kitploit:~
</details>

### L'account del laboratorio```text
alice / Password123!      →  changed during the demo to  Correct-Horse-Battery-9!

Falso, locale e le uniche credenziali in questo repository.


Note di sicurezza

Questo repository contiene codice intenzionalmente vulnerabile. app/auth.py:_revoke_for_security_change non riesce a revocare di proposito quando AFTERLIFE_MODE=vulnerable.

Il percorso vulnerabile non è quello predefinito. AFTERLIFE_MODE è impostato per default su fixed, e test_the_default_mode_is_the_safe_one lo fissa — un laboratorio il cui comportamento danneggiato è ciò che si ottiene dimenticando di configurare qualsiasi cosa finirà prima o poi per essere copiato in qualcosa di reale.

Non è sicuro esporre a una rete in nessuna delle due modalità: le credenziali bearer vengono restituite nei corpi delle risposte, GET /lab/credentials riversa l'intera visione del server dello stato delle credenziali di un utente, GET /lab/audit non è autenticato per design, POST /security-change si concederà da solo admin, e non c'è TLS, rate limiting, protezione CSRF o blocco dell'account da nessuna parte. Tutto si lega a 127.0.0.1, e sia python -m app che python -m console rifiutano qualsiasi altro indirizzo.

Nessuna credenziale, chiave o segreto reale appare in questo repository. La chiave di firma è la stringa letterale afterlife-lab-signing-key-not-a-secret-do-not-reuse; il database delle credenziali è impostato per default su :memory:; la telemetria sostituisce i valori i cui nomi di chiave sembrano credenziali e fa riferimento alle credenziali tramite id e fingerprint. Dettagli e il processo di segnalazione: SECURITY.md.


Documentazione


Riferimenti di ricerca

Verificati rispetto a fonti primarie l'11-09-2026. VERIFIED significa che l'advisory del fornitore stesso è stato letto; REPORTED significa che il dettaglio proviene da un database di vulnerabilità piuttosto che dal fornitore.

Standard e classificazioni

  • OWASP Top 10:2025 — A07 Authentication Failures — descrive esattamente questo fallimento: applicazioni che non "invalidano correttamente le sessioni utente o i token di autenticazione". La sua mappatura CWE include tutte e tre quelle sotto.
  • RFC 9700 — Best Current Practice for OAuth 2.0 Security (BCP 240, gennaio 2025). Due cose su cui si basa questo laboratorio: il riutilizzo di un refresh token ruotato segnala furto (AFTERLIFE-002), e la revoca deve invalidare l'intera famiglia di token, non solo il token corrente — che è la tesi di AFTERLIFE-001 codificata come pratica IETF.
  • OWASP Session Management Cheat Sheet — invalidazione della sessione dopo cambi di credenziali e di sicurezza
  • CWE-613: Insufficient Session Expiration — "permette a un attaccante di riutilizzare vecchie credenziali di sessione o ID di sessione per l'autorizzazione". La classe sotto cui sono archiviate tutte e tre le CVE seguenti.
  • CWE-287: Improper Authentication
  • CWE-384: Session Fixation — adiacente: un identificatore di sessione che sopravvive a un cambiamento di stato dell'autenticazione

Il pattern che questo laboratorio riproduce

  • VERIFIED — GHSA-hvp3-26wx-g2w4 / CVE-2026-22706, Strapi: Password Reset Does Not Revoke Existing Refresh Sessions. @strapi/admin e @strapi/plugin-users-permissions ≤ 5.33.2; corretto 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. Pubblicato il 13-05-2026. L'advisory del fornitore afferma che il passaggio di invalidazione del refresh-token era condizionato a un deviceId fornito dal chiamante, e che la patch invalida tutti i refresh token a ogni cambio e reset di password indipendentemente dal fatto che venga fornito un deviceId. Quel condizionale è ciò che _revoke_for_security_change modella, e quella patch è ciò che implementa la modalità corretta. Contesto del fornitore: Strapi security disclosure, May 2026.

La stessa classe, meccanismi diversi

  • REPORTED — CVE-2026-1163, parisneo/lollms. Nessuna invalidazione della sessione al reset della password del tutto, con una durata predefinita della sessione di 31 giorni. La versione semplice di questo bug — e quella che il rilevatore ingenuo catturerebbe effettivamente.
  • REPORTED — CVE-2026-40934, Jupyter Server ≤ 2.17.0 (corretto 2.18.0). Il segreto di firma dei cookie è persistito in un file statico e non viene mai ruotato al cambio di password, quindi i cookie emessi prima di un reset rimangono crittograficamente validi attraverso di esso. Una terza via verso lo stesso esito: la credenziale qui è valida perché la chiave non è mai cambiata, che è la modalità di fallimento dell'Opzione D da docs/tradeoffs.md.

Tre prodotti, tre meccanismi — un parametro condizionale, una chiamata mancante, una chiave non ruotata — un esito: il reset è riuscito e l'attaccante è rimasto.


L'attaccante non viene rilevato perché la sua richiesta sembra malevola.

Ogni richiesta che ha fatto era ben formata, firmata correttamente e portava una credenziale che il server aveva appena coniato per lui.

Vengono rilevati perché una discendenza di credenziali che sarebbe dovuta morire a un evento di sicurezza è stata accettata dopo di esso.


MIT · un laboratorio locale · nulla qui è sicuro da distribuire

Scarica lo strumento
dormant-survivorvulnerable15HIGHAFTERLIFE-00311
dormant-survivorfixed19–none00
mfa-changevulnerable18HIGHAFTERLIFE-001, AFTERLIFE-00310
mfa-changefixed18–none00
account-recoveryvulnerable18HIGHAFTERLIFE-001, AFTERLIFE-00310
account-recoveryfixed18–none00
expired-lineagevulnerable17LOWAFTERLIFE-00300
expired-lineagefixed20–none00
docs/detection.mdil pacchetto di regole: tutte e tre le schede delle regole, l'audit dello stato, la telemetria richiesta, i falsi positivi, la motivazione della severità, i runbook di risposta
docs/tradeoffs.mdle quattro architetture di revoca, la tensione con JWT e la discussione su CVSS
docs/limitations.mdogni punto cieco, cosa non risolve la correzione, cosa chiude l'audit e i compromessi del laboratorio stesso
docs/console-design.mdperché la console è un registro di mortalità, le decisioni su palette e tipografia e cosa è stato tagliato
docs/console-preview.htmlla console, compilata in un unico file autonomo
docs/figures/le figure in questo README, generate dal payload
report/AFTERLIFE-report.mdun report di incidente generato per l'esecuzione vulnerabile
SECURITY.mdil confine solo-locale, le credenziali del laboratorio, il contratto di redazione
evidence/telemetria campione sanificata, gli avvisi che produce e l'audit dello stato