
Triage dell'esposizione di credenziali e dati sensibili per condivisioni file
Triage dell'esposizione di credenziali e dati sensibili per le condivisioni di file.
Quando compare una condivisione aperta, la domanda non è mai "questo repo ha una chiave divulgata". È "cosa è stato esposto, e cosa devo revocare prima della chiusura dell'attività." sift è costruito per questa domanda: alto recall, una coda di revisione rapida e un ciclo di feedback per cui qualsiasi cosa individui a occhio diventa una regola che trova le altre duecento copie.

Python 3.11+, solo libreria standard. Niente pip install, niente internet, niente passaggi di build. Funziona su un laptop IR bloccato, che è dove ti serve.```bash sift survey \fileserver\openshare # how big is this thing sift copy \fileserver\openshare C:\IR\case-4471 # take a throttled copy sift scan C:\IR\case-4471 # scan it, opens the triage UI
Anche la scansione in loco funziona. Provatela prima su una condivisione di credenziali fittizie:```bash
sift demo C:\temp\demoshare
sift.cmd è un launcher che funziona da qualsiasi directory. Per digitare sift invece
del percorso completo, aggiungi C:\Dev\sift al PATH:```bash
setx PATH "%PATH%;C:\Dev\sift"
Per una macchina senza alcun Python, `python build_portable.py` genera
`dist/sift-secrets-<version>-portable-win64.zip`: il runtime incorporabile ufficiale di python.org più questo albero sorgente, da decomprimere ed eseguire tramite lo `sift.cmd` incluso. ~11 MB, nessuna installazione, nessun diritto di amministratore, e niente in esso è compilato o ricompattato — vedi la docstring in `build_portable.py` per capire perché questo batte un .exe congelato su un laptop IR bloccato.
---
## Perché non semplicemente gitleaks o trufflehog
Entrambi sono buoni strumenti che risolvono un problema diverso.
Sono strumenti di **precisione** pensati per la CI, dove un falso positivo costa a uno sviluppatore il pomeriggio, quindi scattano principalmente su elementi dalla forma simile a una chiave API nota di un vendor. trufflehog va oltre e preferisce segreti che può *verificare* chiamando l'API del vendor, un segnale davvero eccellente che le regex non possono riprodurre.
Il triage delle condivisioni ribalta l'economia. Un essere umano legge già ogni riscontro, quindi un falso positivo costa tre secondi. Ciò che ti costa è un **falso negativo**.
Le chiavi API dei vendor trapelano eccome nelle condivisioni - un backup della web root, uno script di deploy, la cartella di progetto di qualcuno copiata sull'unità dipartimentale, e c'è un `.env` con una chiave Stripe attiva dentro. Meritano di essere individuati, e sift li individua. Ma sono anche la parte che gitleaks e trufflehog già gestiscono bene. Il vuoto è tutto il resto, e su una condivisione file è la maggior parte:
| Cosa perdono gli scanner CI | Perché lo ignorano |
|---|---|
| `web.config` con una stringa di connessione SQL | Non è un formato di chiave noto, nessun vendor da verificare |
| `Map-Drives.ps1` con `net use ... /user:` | Solo un comando shell con una parola dopo |
| `New Hire Setup Guide.docx` | File Office, letto come binario, saltato del tutto |
| `unattend.xml`, GPP `Groups.xml` | Artefatti di distribuzione Windows per cui nessuno ha scritto un rilevatore |
| `confCons.xml`, `.rdg`, WinSCP.ini | Password memorizzate reversibili, ma non un "formato segreto" |
| `passwords.xlsx` | È uno ZIP. Gli scanner di testo semplice vedono binario e vanno avanti |
| `.kdbx`, `.pfx`, `id_rsa` | Byte opachi - il *nome del file* è il riscontro |
| Un `.bak` con una stringa di connessione dentro | Binario, quindi mai letto |
sift copre questi casi, include le proprie regole per chiavi vendor e **importa pacchetti di regole e riscontri di altri strumenti** - gitleaks TOML, Kingfisher/Titus YAML e trufflehog JSON - così non devi scegliere tra gli strumenti.
Il precedente più vicino è [Snaffler](https://github.com/SnaffCon/Snaffler), che è eccellente nella metà relativa a nomi file e classificazione e che è la diretta ispirazione per le regole sui nomi file. Ciò che non ha - e che si rivela essere il vero collo di bottiglia quando hai 400 riscontri - è un ciclo di revisione.
---
## Il ciclo
1. **Scansiona** la condivisione.
2. **Lavora la coda.** Ogni riscontro mostra le righe circostanti con la corrispondenza evidenziata. I tasti freccia ampliano il contesto; un clic apre l'intero file in VS Code su quella riga, o in Notepad.
3. **Individua un falso negativo.** Ti capiterà. Evidenzialo nell'anteprima e premi `r`.
4. **sift propone pattern** e ti dice in tempo reale quante volte ciascuno corrisponderebbe su tutto ciò che è già stato letto.
5. **Salva.** La riscansione in cache richiede circa un secondo, e i nuovi riscontri appaiono nella coda con le tue decisioni di triage esistenti intatte.
Il passaggio 5 è ciò che rende utile tutto il resto. I riscontri sono indicizzati su `(path, rule, line, value-hash)`, quindi una riscansione reinserisce le stesse righe e si porta dietro stato, note e proprietario. Senza questo, rirevisioneresti gli stessi 300 riscontri a ogni iterazione e ti arrenderesti alla terza.
---
## Comandi```bash
# size it up first: file count, total bytes, biggest folders, transfer estimates
sift survey \\fileserver\share
# take a rate-limited local copy (resumable; gentle = 5 MB/s by default)
sift copy \\fileserver\share C:\IR\case-4471 --speed gentle
# scan a share and open the triage UI
sift scan \\fileserver\share
# maximum recall: more noise, but a human is reading anyway
sift scan D:\dfs\dept --tier 3
# re-open the UI over the most recent scan
sift ui
# build a share of fabricated credentials, scan it, open the UI
sift demo C:\temp\demoshare
# inherit other tools' vendor-key rules, then use them in the live rescan loop
sift import-rules gitleaks.toml # gitleaks TOML
sift import-rules path/to/kingfisher/data/rules # a directory of YAML
# pull in what the other scanners found, into the same queue
trufflehog filesystem \\fileserver\share --json > th.json
sift import-findings th.json
# hand off to the incident record (redacted unless you say otherwise)
sift export --fmt pdf --status confirmed --out ir-4471.pdf
sift export --fmt csv --out ir-4471.csv
sift export --fmt pdf --no-redact # plaintext; handle as evidence
sift rules # what is loaded
sift selftest # detection tests against a synthetic share
# ask vendors whether confirmed findings are still live. NETWORK. Opt-in.
sift validate --status confirmed
Ovunque compaia sift puoi usare python -m sift in alternativa, dalla directory C:\Dev\sift.
I risultati finiscono in una cartella per-target sotto %LOCALAPPDATA%\sift\, mai nella directory di lavoro: il database contiene credenziali in chiaro, ed eseguire lo strumento dalla propria home directory non dovrebbe lasciarne silenziosamente una lì. Ogni condivisione ha il proprio store, quindi due engagement non condividono mai una coda di triage. sift ui senza argomenti riapre il più recente; --data DIR ha la precedenza.
Le regole personalizzate sono globali, in %LOCALAPPDATA%\sift\user-rules.json, quindi un pattern che scrivi durante un engagement ti aiuta sulla prossima condivisione che esamini.
Entrambe sono nell'intestazione della UI, accanto al box del percorso, e nella CLI.
Check size è una scansione solo-stat. Non viene aperto nulla, quindi è economica anche via SMB, e ti dice il numero di file, i byte totali, le cartelle più grandi, la suddivisione per estensione, quanto sift leggerebbe effettivamente, e quanto tempo richiederebbe una copia a ciascuna velocità. Puntare uno scanner a una radice DFS sconosciuta e aspettare è il modo in cui un pomeriggio sparisce.
Copy locally copia prima la condivisione in una cartella locale. Vale la pena farlo perché:
Il trasferimento è limitato in velocità, con default a 5 MB/s. Saturare il collegamento a un file server di produzione alle 14:00 trasforma la tua indagine in un secondo incidente. Aumentalo quando sai che il percorso è inattivo.
| Velocità | Tasso |
|---|---|
| gentile (predefinita) | 5 MB/s |
| normale | 25 MB/s |
| veloce | 100 MB/s |
| illimitata | quello che offre il collegamento |
I trasferimenti riprendono: un file di destinazione con stessa dimensione e mtime viene saltato, quindi un'acquisizione interrotta all'80% riprende da dove si era fermata. I file bloccati o negati vengono registrati e saltati invece di interrompere l'esecuzione.
PDF, CSV, MD e JSON, dall'intestazione della UI o con sift export --fmt.
Il PDF è quello da consegnare per il rapporto sull'incidente: una copertina con il target, i totali per gravità e stato di triage, le regole più frequenti, i valori riutilizzati tra i file, e poi i risultati raggruppati per gravità. Viene generato direttamente, senza librerie PDF, quindi funziona su una macchina che non ha mai visto pip.
Gli export sono oscurati per impostazione predefinita. I valori vengono mascherati, ogni pagina è marcata con un banner, e l'endpoint disabilita la mascheratura solo con un esplicito redact=0: una richiesta troncata o malformata non può far fuoriuscire nulla. Disattivare l'oscuramento nella UI richiede la conferma di un dialogo di avviso, e il file risultante è marcato con banner UNREDACTED - CONTAINS PLAINTEXT CREDENTIALS su ogni pagina.
Il database dei risultati conserva comunque i valori reali, perché un analista deve sapere quale password è trapelata per sapere cosa cambiare. Il confine è ciò che esce dallo strumento.
Tutto è cliccabile. Il controllo sort in cima all'elenco dei risultati riordina per gravità, percorso file, regola, stato di triage o più recente, con un pulsante per invertirlo. I facet della barra laterale filtrano per gravità, categoria, regola e valore riutilizzato. Triage, espansione del contesto, apertura di un file e creazione di regole sono tutti pulsanti.
Le scorciatoie da tastiera qui sotto sono acceleratori per una coda lunga, non l'unico modo per gestirla.
Il pulsante snapshot renderizza lo snippet evidenziato in un PNG da incollare in un ticket dell'incidente; copy snippet fa lo stesso in markdown.
La UI renderizza credenziali in chiaro in tempo reale in una finestra del browser, quindi:
127.0.0.1 e rifiuta qualsiasi altro indirizzo senza --unsafe-bind;SameSite=Strict;Host, quindi una pagina ostile non può fare DNS-rebind verso di esso;/api/context non è una lettura arbitraria di file e /api/open non è un avvio arbitrario di processi.Il database dei risultati contiene credenziali in chiaro per progettazione: un analista IR deve sapere quale password è trapelata per sapere cosa ruotare. Tratta %LOCALAPPDATA%\sift\<target>\findings.db come prova: stessa gestione della condivisione, ed eliminalo quando l'engagement si chiude. Usa --redact se lascerà il perimetro dell'incidente.
E quella ovvia: esegui sift solo contro sistemi su cui sei autorizzato ad accedere.
sift/rules_builtin.py - regole sui contenuti. sift/rules_filename.py - regole sui nomi file. Entrambi sono Python semplice con pattern raw-string, quindi leggibili e diffabili; le regole utente vivono in JSON in .sift/user-rules.json.
Tre livelli ti permettono di scambiare precisione con recall:
cpassword GPP, dump NTLM, password LDAP bind. La sola forma è una prova.Una regola può anche avere min_digits, min_lowercase, min_uppercase e min_special, così una singola regola rumorosa può essere stretta senza toccare le altre, e examples - stringhe che deve continuare a matchare.
examples è la metà utile. sift selftest esegue ogni regola contro le stringhe per cui è stata scritta, attraverso l'intero percorso: match, estrazione, poi i filtri di soppressione. Quell'ultimo passaggio è quello che conta, perché la regressione che si verifica davvero non è un pattern che smette di fare match - è un filtro anti-rumore, stretto per buoni motivi da qualche altra parte, che silenziosamente divora un risultato reale mentre esce.
Si fa valere subito. Aggiungere examples alle regole esistenti ha portato alla luce un gap reale: un underscore è un carattere di parola, quindi il \b iniziale nella regola generic-assignment rifiutava di fare match dentro DB_PASSWORD, MYSQL_PASSWORD o REDIS_PASSWORD - tre dei nomi di variabile per credenziali più comuni in assoluto, persi silenziosamente. L'esempio sembrava ovviamente corretto e non faceva match, che è esattamente il suo scopo.
Le regole che scrivi nella UI ottengono questo gratuitamente: la riga che hai selezionato viene salvata come esempio della regola, quindi una regola che modifichi sei mesi dopo ti dice quando ha smesso di fare match con la cosa che ti ha spinto a scriverla.
sift import-rules accetta un .toml di gitleaks, un .yml in stile Kingfisher/Titus, o una directory di questi. Contro il pack di Kingfisher parliamo di 1.073 regole importate su 1.082, portando con sé i loro minimi di entropia, i requisiti di cifre e maiuscole, e gli esempi. Lo YAML è letto da sift/yamlmini.py, un lettore per il sottoinsieme usato da questi pack - nessuna dipendenza, e solleva un'eccezione su anchor e tag invece di fingere di capirli.
Due cose vengono deliberatamente scartate durante l'importazione:
validation:, che nominano un URL per regola. sift contatta solo host hardcoded in validate.py. Un rule pack che potesse nominare un endpoint sceglierebbe dove vengono inviati i tuoi risultati, e un file di regole è dato, non una decisione.[[:alnum:]] è una classe POSIX; Python la legge come un insieme di caratteri letterali, la compila felicemente e fa match con la cosa sbagliata. La traduzione viene verificata contro gli esempi di ogni regola, quindi un pattern che è sopravvissuto alla compilazione ma ha cambiato significato viene rifiutato invece di non scattare mai silenziosamente. Dodici delle regole di Kingfisher falliscono quel controllo e non vengono importate.Il fatto che la soppressione di sift scarterebbe un dato esempio non squalifica la regola - quei pack includono deliberatamente campioni finti (keyXXXXXXXX, ...EXAMPLE), quindi il filtro dei placeholder ha ragione sull'esempio e non dice nulla sul pattern. Essere severi su questo ha scartato 121 regole funzionanti prima che la distinzione fosse tracciata.
Le regole generiche per i segreti vengono abbandonate a causa del rumore, quindi i filtri anti-rumore sono calibrati con la stessa cura dei pattern. Misurato su un albero reale di 6.000 file, le regole di soppressione qui sotto riducono i risultati da 1.088 a 257 senza alcuna perdita di recall sul corpus di test:
password: process.env.DB_PASS è una variabile, non un valore. Questa singola soppressione rimuove la maggior parte del rumore delle regole generiche negli alberi sorgente.def login(user: str, password: str) è una firma..wrangler, .next, site-packages, node_modules, …) più bundle minificati e source map vengono saltate. Il testo generato da macchine produce solo falsi positivi generati da macchine.Non esiste deliberatamente nessuna regola di contenuto https://user:pass@host con una coda lasca. La versione ovvia è scattata 476 volte su un albero di sviluppo, perché il JSON minificato non ha spazi bianchi e il pattern correva da un URL attraverso virgolette e virgole fino a trovare una @ non correlata centinaia di caratteri dopo.
python tests/run_all.py
Dodici suite: rilevamento contro una condivisione sintetica (diviso in "ciò che
gli scanner CI già rilevano", "il vuoto che questo strumento mira a colmare", e "esche che
devono rimanere silenziose"), la riga di comando, la classifica dei suggerimenti di regole, ricognizione e
acquisizione limitata (inclusa la misurazione del limite di velocità rispetto all'orologio reale),
lo scrittore PDF (analizzato come farebbe un lettore, per dimostrare che la redazione ha raggiunto
il contenuto della pagina), gli importatori gitleaks/trufflehog, l'API HTTP incluse
tutte le guardie di sicurezza, l'analisi statica dell'interfaccia utente e il lettore a blocchi per
file di grandi dimensioni (verificando che un segreto nell'ultimo blocco riporti comunque il suo vero
numero di riga nell'intero file).
Per ottenere una condivisione con cui giocare:```bash
sift demo C:\temp\demoshare
Ogni credenziale in quel corpus è inventata.
Due livelli, perché comportano rischi molto diversi.
I checksum sono gratuiti e sempre attivi. I token ghp_, npm_, Atlassian ATATT, Bitbucket ATCTT e i token glpat- instradabili di GitLab contengono tutti un CRC32 sul proprio corpo. Ricalcolarlo risponde offline a ciò che prima richiedeva internet: è un token reale, o l'esempio che qualcuno ha incollato in un README? I risultati vengono etichettati come checksum ok o malformed nella coda. Un checksum dimostra la forma, non la vita - un token ben formato potrebbe essere stato revocato un anno fa.
La validazione live è disattivata finché non la esegui. sift validate chiede al fornitore se una credenziale funziona ancora. È un comando separato, non una flag su scan, e ti fa digitare validate a un prompt che prima elenca ogni endpoint che contatterà. Le regole di sicurezza:
sift/validate.py. Nessuna regola - integrata, scritta dall'utente o importata dal pack di qualcun altro - può fornire un URL. Senza questo, importare un pacchetto di regole sarebbe sufficiente per inviare ogni credenziale sulla condivisione a un indirizzo scelto dall'autore del pacchetto.--redact: se i valori sono mascherati perché il database sta oltrepassando il confine dell'incidente, trasmettere il testo in chiaro è esattamente ciò contro cui ci si sta proteggendo.validation-log.json accanto al database dei risultati
I tentativi di validazione finiscono nei log di audit del proprietario della credenziale, attribuiti al tuo indirizzo, in quel momento. A volte è esattamente ciò che vuoi e a volte mette in allarme un avversario che sta osservando. Decidi prima di eseguirlo; è per questo che lo chiede.
Attualmente GitHub, npm, Slack e Stripe. trufflehog verifica ancora molto di più -
eseguilo e usa import-findings per ottenere entrambi.
import-findings; i risultati verificati finiscono in cima..kdbx o .pfx viene segnalato
per nome; sift non tenta di aprirlo.name=value, senza cui la parola segreta e il suo valore si trovano in colonne
separate e nessuna regola vede la coppia..7z, .rar e annidati vengono segnalati ma non estratti. Solo i formati basati su
ZIP e i messaggi .eml vengono letti all'interno.--max-size vengono letti a blocchi, non in cache. Vengono scansionati
(una stringa di connessione in un .bak da 4 GB viene trovata, al suo vero numero di riga), ma
la cache di testo contiene solo ciò che entra, quindi la riscansione cache di un secondo non li
copre - una nuova regola raggiunge un file di grandi dimensioni alla successiva scansione completa.Apache-2.0. Vedi LICENSE.
Il corpus di test di rilevamento (sift/selftest.py, tests/) contiene
credenziali formalmente corrette ma inventate di proposito; gli avvisi di
scansione dei segreti per quei percorsi vengono soppressi tramite .github/secret_scanning.yml.
| Flag | Effetto |
|---|
--tier 1|2|3 | Manopola del recall. 1 = segnale alto, 2 = predefinito, 3 = non perdere nulla |
--redact | Maschera i valori nello store e negli export. Usalo se il DB esce dal perimetro dell'incidente |
--no-ui | Popola lo store ed esce, per esecuzioni scriptate |
--no-browser | Avvia il server UI ma non aprire un browser (utile via RDP) |
--include/--exclude GLOB | Restringi l'esplorazione |
--no-archives | Non aprire i contenitori docx/xlsx/zip |
--no-strings | Non eseguire una passata strings sui binari |
--no-large | Salta i file oltre --max-size invece di leggerli a blocchi |
--jobs N | Processi worker (predefinito: auto) |
--max-size MB | Salta i file sopra questa dimensione (predefinito 25) |
--port N | Porta UI (predefinita 8973) |
| Key | Azione |
|---|
j / k | risultato successivo / precedente |
↑ / ↓ | espandi il contesto verso l'alto / verso il basso |
c / f | conferma / segna come falso positivo |
x | attiva la selezione per il triage di massa |
o / n | apri in VS Code alla riga / apri il file in Notepad |
r | crea una regola dal testo evidenziato |
y | copia il valore |
/ | cerca |
.doc/.xls/.pdf (pre-2007 e PDF) passano attraverso una passata di stringhe piuttosto che
un parser reale, quindi la recall su questi è inferiore rispetto ai formati OOXML.