
Auditorias Autodestrutivas: laboratório reproduzível que demonstra uma role PostgreSQL de baixo privilégio cegando reversivelmente um auditor baseado em triggers + envenenamento de atribuição (PG14/16), com defesas. Dados sintéticos; primitivas do CVE-2018-1058 citadas.
Licença: MIT (código) / CC BY 4.0 (docs)
Pesquisa em segurança defensiva que testa se uma role de banco de dados de baixo privilégio, somente injeção (injection-only) pode cegar reversivelmente um auditor baseado em triggers e envenenar a atribuição no PostgreSQL padrão — e quais defesas realmente impedem isso. Tudo roda em containers Docker locais e descartáveis com dados totalmente sintéticos (IPs de documentação RFC 5737, identidades inventadas). Nenhum sistema, credencial ou dado real está envolvido.
Consulte o resumo do laboratório em self-defeating-audits-lab.md.
Constrói a vítima de trigger de auditoria do wiki do PostgreSQL, amplamente copiada, e então percorre uma matriz de viabilidade célula por célula no PostgreSQL 14 e 16, medindo — fora de banda — se cada vetor de ataque realmente impede que uma escrita seja auditada, se é reversível com zero resíduo e quais defesas o neutralizam.
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
Requer Docker. Usa as imagens -alpine (motor funcionalmente idêntico).
O estudo é reproduzido em mais duas famílias de motores. Família MySQL (MariaDB, modelo de trigger DEFINER) em 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 (modelo de encadeamento de propriedade) em scripts/mssql/ — executa em uma instância LocalDB local via sqlcmd do host (sem Docker; o atacante de baixo privilégio é modelado com personificação EXECUTE AS USER):
cd scripts/mssql
./run_matrix_mssql.sh # -> results/RESULTS_mssql.md ; then ./teardown.sh
Ressalva de reprodutibilidade (SQL Server). Ao contrário dos motores Docker, o terceiro motor (SQL Server) tem uma dependência do host: ele executa em uma instância local SQL Server LocalDB via
sqlcmd(caminho embutido no código emrun_matrix_mssql.sh), portanto é exclusivo para Windows e não pode ser fixado a um digest de imagem. O atacante com privilégios mínimos é modelado com personificaçãoEXECUTE AS USER(LocalDB só aceita autenticação Windows). Os achados de comportamento do motor (DISABLE de zero resíduo; a guarda de DDL não detecta DISABLE) são independentes do tipo de sessão e valeriam para um login real; os achados sobre concessões de objeto são aplicados fielmente pela personificação. É o menos portátil dos três motores — registrado com honestidade.
Resultado principal entre motores: a classe de sombreamento search_path (1A/1B) não se transfere nem para o MySQL nem para o SQL Server (não há search_path), mas substituição de trigger, bypass dormente e envenenamento de atribuição se transferem. A vinculação de privilégio DEFINER do MySQL torna o cegamento mais detectável; o SQL Server, assim como o PostgreSQL, permite restauração byte a byte e adiciona um primitivo DISABLE TRIGGER de zero resíduo que a própria defesa de DDL do motor não detecta. Comparação completa dos três motores em FINDINGS.md.
| Caminho | Descrição |
|---|---|
scripts/00_up.sh | container descartável com versão fixada (PostgreSQL) |
scripts/mysql/ | vítima, ataques, defesas e orquestrador da família MySQL (MariaDB) |
scripts/mssql/ | vítima, ataques, defesas e orquestrador do SQL Server (LocalDB) |
results/RESULTS_mariadb_*.md | matrizes de viabilidade + defesa da família MySQL |
results/RESULTS_mssql.md | matriz de viabilidade + defesa do SQL Server |
scripts/10_victim.sql | vítima de auditoria no padrão do wiki (trigger SECURITY DEFINER); parâmetros: pub_create, pin_path, app_owns |
scripts/20_attacks.sql | todos os vetores de ataque (1A/1B/1C, GUC dormente, envenenamento de atribuição), um guard \if por célula |
scripts/30_defenses.sql | guarda de event trigger de DDL + sink somente-acréscimo encadeado por hash |
scripts/40_snapshot.sql | snapshot do catálogo para o diff de objetos do RQ2 |
scripts/run_matrix.sh | orquestrador: constrói cada célula, mede e grava results/RESULTS_pg<VER>.md |
scripts/teardown.sh | remove containers |
results/RESULTS_pg14.md, results/RESULTS_pg16.md | matrizes de viabilidade + defesa preenchidas por versão |
results/_evidence_pg*.log, results/_snap_1C_*.txt | evidências brutas de comando/saída |
FINDINGS.md | veredito RQ1–RQ5 em linguagem simples, matriz defesa×ataque, posicionamento de novidade, resumo |
CREATE SCHEMA), não porque "pg_catalog vence". Plantar no public já gravável de fato faz sombreamento: 1S (mesma assinatura, ordenação de schema) e 1T (sobrecarga exata vence a função nativa mesmo com pg_catalog em primeiro — isola a especificidade de tipo) ambos cegam (payload anulado).row_to_json para tipo composto — cega o auditor quando
public é gravável e o search_path do trigger não está fixado. Atingível no PostgreSQL
padrão ≤14; na 15+, exige má configuração. Fixar o search_path o derrota em qualquer cenário.pgaudit/log_statement) + sink encadeado por hash são as
defesas que fecham o conjunto.Reproduzido em PostgreSQL padrão em um laboratório local com dados anonimizados/sintéticos. O SQL de ataque está restrito ao schema de laboratório construído aqui; este é um estudo de viabilidade/defesa, não um payload ofensivo. Os primitivos subjacentes já são públicos (CVE-2018-1058; escalada de search_path via SECURITY DEFINER) — a contribuição é a composição e a avaliação de defesa. Nenhum sistema real é identificado.