
Audit che si auto-sconfiggono: laboratorio riproducibile che mostra un ruolo PostgreSQL a bassi privilegi che acceca reversibilmente un auditor basato su trigger + avvelenamento dell'attribuzione (PG14/16), con difese. Dati sintetici; citate le primitive di CVE-2018-1058.
Licenza: MIT (codice) / CC BY 4.0 (documentazione)
Ricerca sulla sicurezza difensiva che verifica se un ruolo di database a bassi privilegi, solo injection possa accecare reversibilmente un auditor basato su trigger e avvelenare l'attribuzione su PostgreSQL standard — e quali difese lo fermano davvero. Tutto viene eseguito in contenitori Docker locali e usa-e-getta con dati interamente sintetici (IP di documentazione RFC 5737, identità inventate). Non sono coinvolti sistemi, credenziali o dati reali.
Vedi il briefing del laboratorio in self-defeating-audits-lab.md.
Costruisce la vittima "audit trigger" del wiki di PostgreSQL, ampiamente copiata, quindi elabora una matrice di fattibilità cella per cella su PostgreSQL 14 e 16, misurando — fuori banda — se ogni vettore d'attacco impedisce davvero la registrazione di una scrittura, se è reversibile senza lasciare tracce e quali difese lo neutralizzano.
cd scripts
./00_up.sh 16 # bring up postgres:16-alpine on localhost:55432
./run_matrix.sh 16 # run every matrix cell + defenses -> ../results/RESULTS_pg16.md
docker rm -f sda_pg16 # free the port between versions
./00_up.sh 14
./run_matrix.sh 14 # -> ../results/RESULTS_pg14.md
./teardown.sh # remove all lab containers
Richiede Docker. Utilizza le immagini -alpine (motore funzionalmente identico).
Lo studio è riprodotto su altre due famiglie di motori. Famiglia MySQL (MariaDB, modello a trigger DEFINER) in scripts/mysql/:
cd scripts/mysql
./00_up.sh 10.11 && ./run_matrix_mysql.sh 10.11 # -> results/RESULTS_mariadb_10.11.md
docker rm -f sda_maria_10_11 && ./00_up.sh 10.6 && ./run_matrix_mysql.sh 10.6
SQL Server (modello ownership-chaining) in scripts/mssql/ — viene eseguito su un'istanza LocalDB locale tramite sqlcmd dell'host (niente Docker; l'attaccante a bassi privilegi è modellato con l'impersonificazione EXECUTE AS USER):
cd scripts/mssql
./run_matrix_mssql.sh # -> results/RESULTS_mssql.md ; then ./teardown.sh
Avvertenza sulla riproducibilità (SQL Server). A differenza dei motori Docker, il terzo motore SQL Server ha una dipendenza dall'host: viene eseguito su un'istanza locale di SQL Server LocalDB tramite
sqlcmd(percorso hardcoded inrun_matrix_mssql.sh), quindi è solo Windows e non può essere ancorato a un digest di immagine. L'attaccante a bassi privilegi è modellato con l'impersonificazioneEXECUTE AS USER(LocalDB supporta solo l'autenticazione Windows). I risultati sul comportamento del motore (DISABLE a residuo zero; la guardia DDL non intercetta DISABLE) sono indipendenti dal tipo di sessione e varrebbero per un login reale; i risultati sui privilegi sugli oggetti sono applicati fedelmente dall'impersonificazione. È il meno portabile dei tre motori — detto onestamente.
Risultato principale cross-engine: la classe shadow di search_path (1A/1B) non si trasferisce né a MySQL né a SQL Server (niente search_path), ma la sostituzione del trigger, il bypass dormiente e l'avvelenamento dell'attribuzione sì. Il binding dei privilegi DEFINER di MySQL rende l'accecamento più rilevabile; SQL Server, come PostgreSQL, consente un ripristino byte-perfetto e aggiunge una primitiva DISABLE TRIGGER a residuo zero che la sua stessa difesa DDL non intercetta. Confronto completo dei tre motori in FINDINGS.md.
| Percorso | Descrizione |
|---|---|
scripts/00_up.sh | contenitore usa-e-getta con versione bloccata (PostgreSQL) |
scripts/mysql/ | vittima, attacchi, difese e orchestratore per la famiglia MySQL (MariaDB) |
scripts/mssql/ | vittima, attacchi, difese e orchestratore per SQL Server (LocalDB) |
results/RESULTS_mariadb_*.md | matrici di fattibilità e difesa per la famiglia MySQL |
results/RESULTS_mssql.md | matrice di fattibilità e difesa per SQL Server |
scripts/10_victim.sql | la vittima di audit secondo lo schema del wiki (trigger SECURITY DEFINER); parametri: pub_create, pin_path, app_owns |
scripts/20_attacks.sql | tutti i vettori d'attacco (1A/1B/1C, dormienza GUC, avvelenamento dell'attribuzione), una guardia \if per cella |
scripts/30_defenses.sql | guardia con event trigger DDL + sink append-only con hash a catena |
scripts/40_snapshot.sql | snapshot del catalogo per l'object-diff di RQ2 |
scripts/run_matrix.sh | orchestratore: costruisce ogni cella, misura, scrive results/RESULTS_pg<VER>.md |
scripts/teardown.sh | rimuove i contenitori |
results/RESULTS_pg14.md, results/RESULTS_pg16.md | matrici di fattibilità e difesa compilate per versione |
results/_evidence_pg*.log, results/_snap_1C_*.txt | prove grezze di comandi/output |
FINDINGS.md | verdetto RQ1–RQ5 in linguaggio semplice, matrice difesa×attacco, posizionamento di novità, abstract |
CREATE SCHEMA), non perché "pg_catalog vince". Piantarlo nel già scrivibile public fa shadow: 1S (stessa firma, ordinamento per schema) e 1T (l'overload esatto batte il built-in anche con pg_catalog per primo — isola la specificità di tipo) entrambi accecano (payload azzerato).row_to_json con tipo composito — acceca l'auditor quando public è scrivibile e il search_path del trigger non è fissato. Raggiungibile su PostgreSQL standard ≤14; condizionato da una cattiva configurazione su 15+. Fissare il search_path lo neutralizza ovunque.pgaudit/log_statement) + il sink con hash a catena sono le difese che chiudono l'insieme.Riprodotto su PostgreSQL standard in un laboratorio locale con dati anonimizzati/sintetici. L'SQL degli attacchi è limitato allo schema di laboratorio costruito qui; questo è uno studio di fattibilità/difesa, non un payload weaponizzato. Le primitive sottostanti sono già pubbliche (CVE-2018-1058; escalation del search_path di SECURITY DEFINER) — il contributo è la composizione e la valutazione delle difese. Nessun sistema reale è identificato.