|
Home → WINDEV 25 → Solução segura para bloquear comandos Drop, delete e outros do Postgresql com Windev |
Solução segura para bloquear comandos Drop, delete e outros do Postgresql com Windev |
Started by Boller, Jun., 11 2025 3:12 PM - 4 replies |
| |
| | | |
|
| |
Registered member 4,520 messages |
|
Posted on June, 11 2025 - 3:12 PM |
Vou criar uma solução completa para bloquear comandos SQL perigosos no PostgreSQL usando um arquivo INI. Esta implementação intercepta comandos antes de enviá-los ao banco.
## 1. Estrutura do Arquivo INI
Primeiro, vamos definir o arquivo `pgsql_security.ini`:
```ini [SECURITY] ; Ativa ou desativa o bloqueio de comandos EnableBlocking=1 ; Modo: 0=Bloquear, 1=Avisar apenas, 2=Log apenas BlockMode=0 ; Ativar log de tentativas EnableLogging=1 ; Caminho do arquivo de log LogFile=C:\ProgramData\PostgreSQL\security.log
[BLOCKED_COMMANDS] ; Comandos completamente bloqueados DELETE=1 TRUNCATE=1 DROP=1 ALTER=1 UPDATE=1 GRANT=1 REVOKE=1 INSERT=1
[WHITELIST] ; Queries permitidas mesmo com comandos bloqueados ; Use hashes MD5 das queries completas Query1=5d41402abc4b2a76b9719d911017c592 Query2=098f6bcd4621d373cade4e832627b4f6
[EXCEPTIONS] ; Usuários com permissão total AdminUsers=postgres,admin_sistema ; IPs com permissão total TrustedIPs=127.0.0.1,192.168.1.100 ; Aplicações com permissão total TrustedApps=pgAdmin.exe,maintenance.exe ```
## 2. Classe WinDev para Gerenciar Segurança
```wlanguage // Classe para gerenciar segurança PostgreSQL CLASS PgSecurityManager PRIVATE m_sIniPath is string m_bEnabled is boolean m_nBlockMode is int m_bLogging is boolean m_sLogFile is string m_arrBlockedCommands is array of string m_arrWhitelistHashes is array of string m_arrAdminUsers is array of string m_arrTrustedIPs is array of string m_arrTrustedApps is array of string
PUBLIC // Constructor CONSTRUCTOR() // Buscar o diretório das DLLs do PostgreSQL m_sIniPath = GetPostgreSQLPath() + "\pgsql_security.ini" LoadConfiguration() END
// Obter caminho do PostgreSQL PROCEDURE PRIVATE GetPostgreSQLPath(): string sPath is string
// Tentar localizar via registro do Windows sPath = RegistryQueryValue("HKEY_LOCAL_MACHINE\SOFTWARE\PostgreSQL\Installations\postgresql-14", "Base Directory") IF sPath = "" THEN // Tentar caminhos padrão IF fDirectoryExist("C:\Program Files\PostgreSQL\14") THEN sPath = "C:\Program Files\PostgreSQL\14\bin" ELSE IF fDirectoryExist("C:\Program Files\PostgreSQL\13") THEN sPath = "C:\Program Files\PostgreSQL\13\bin" ELSE // Usar diretório da aplicação como fallback sPath = fExeDir() END ELSE sPath += "\bin" END
RETURN sPath END
// Carregar configuração do INI PROCEDURE PRIVATE LoadConfiguration() // Verificar se o arquivo existe IF NOT fFileExist(m_sIniPath) THEN m_bEnabled = False RETURN END
// Carregar configurações gerais m_bEnabled = (INIRead("SECURITY", "EnableBlocking", "1", m_sIniPath) = "1") m_nBlockMode = Val(INIRead("SECURITY", "BlockMode", "0", m_sIniPath)) m_bLogging = (INIRead("SECURITY", "EnableLogging", "1", m_sIniPath) = "1") m_sLogFile = INIRead("SECURITY", "LogFile", fExeDir() + "\pgsql_security.log", m_sIniPath)
// Carregar comandos bloqueados DeleteAll(m_arrBlockedCommands) arrCommands is array of string = ["DELETE", "TRUNCATE", "DROP", "ALTER", "UPDATE", "GRANT", "REVOKE", "INSERT"] FOR EACH sCmd OF arrCommands IF INIRead("BLOCKED_COMMANDS", sCmd, "0", m_sIniPath) = "1" THEN Add(m_arrBlockedCommands, sCmd) END END
// Carregar whitelist DeleteAll(m_arrWhitelistHashes) i is int = 1 sHash is string LOOP sHash = INIRead("WHITELIST", "Query" + i, "", m_sIniPath) IF sHash = "" THEN BREAK Add(m_arrWhitelistHashes, sHash) i++ END
// Carregar exceções LoadExceptions() END
// Carregar lista de exceções PROCEDURE PRIVATE LoadExceptions() sValue is string
// Usuários admin sValue = INIRead("EXCEPTIONS", "AdminUsers", "", m_sIniPath) IF sValue <> "" THEN StringToArray(sValue, m_arrAdminUsers, ",") END
// IPs confiáveis sValue = INIRead("EXCEPTIONS", "TrustedIPs", "", m_sIniPath) IF sValue <> "" THEN StringToArray(sValue, m_arrTrustedIPs, ",") END
// Aplicações confiáveis sValue = INIRead("EXCEPTIONS", "TrustedApps", "", m_sIniPath) IF sValue <> "" THEN StringToArray(sValue, m_arrTrustedApps, ",") END END
// Verificar se query é permitida PROCEDURE ValidateQuery(sQuery is string, sUser is string = "", sIP is string = ""): boolean // Se segurança desabilitada, permitir tudo IF NOT m_bEnabled THEN RETURN True
// Verificar exceções IF IsException(sUser, sIP) THEN RETURN True
// Verificar whitelist (hash MD5 da query) sQueryHash is string = HashString(HA_MD5, Upper(NoSpace(sQuery))) IF Seek(m_arrWhitelistHashes, sQueryHash) > 0 THEN RETURN True END
// Verificar comandos bloqueados sQueryUpper is string = Upper(sQuery) FOR EACH sCmd OF m_arrBlockedCommands IF StringContains(sQueryUpper, sCmd) THEN // Verificar se é realmente o comando (não parte de string) IF IsActualCommand(sQueryUpper, sCmd) THEN HandleBlockedCommand(sQuery, sCmd, sUser, sIP) RETURN (m_nBlockMode = 1) // Retorna True se modo for "Avisar apenas" END END END
RETURN True END
// Verificar se é realmente um comando SQL PROCEDURE PRIVATE IsActualCommand(sQuery is string, sCommand is string): boolean // Remover comentários SQL sClean is string = RemoveSQLComments(sQuery)
// Padrões para identificar comandos reais arrPatterns is array of string Add(arrPatterns, "^" + sCommand + "\s") // Início da query Add(arrPatterns, ";\s*" + sCommand + "\s") // Após ponto e vírgula
// Verificar padrões FOR EACH sPattern OF arrPatterns IF RegexMatch(sClean, sPattern) THEN RETURN True END END
RETURN False END
// Remover comentários SQL PROCEDURE PRIVATE RemoveSQLComments(sQuery is string): string sResult is string = sQuery
// Remover comentários de linha ( sResult = RegexReplace(sResult, "--.*?$", "", RegexMultiline)
// Remover comentários de bloco () sResult = RegexReplace(sResult, "/\*.*?\*/", "", RegexDotAll)
RETURN sResult END
// Verificar exceções PROCEDURE PRIVATE IsException(sUser is string, sIP is string): boolean // Verificar usuário admin IF sUser <> "" AND Seek(m_arrAdminUsers, sUser) > 0 THEN RETURN True END
// Verificar IP confiável IF sIP <> "" AND Seek(m_arrTrustedIPs, sIP) > 0 THEN RETURN True END
// Verificar aplicação confiável sAppName is string = fExtractPath(ExeInfo(exeName), fFileName + fExtension) IF Seek(m_arrTrustedApps, sAppName) > 0 THEN RETURN True END
RETURN False END
// Tratar comando bloqueado PROCEDURE PRIVATE HandleBlockedCommand(sQuery is string, sCommand is string, sUser is string, sIP is string) sMessage is string
SWITCH m_nBlockMode CASE 0: // Bloquear sMessage = StringBuild("BLOQUEADO: Comando %1 não permitido", sCommand) Error(sMessage)
CASE 1: // Avisar sMessage = StringBuild("AVISO: Comando %1 detectado mas executado", sCommand) Warning(sMessage)
CASE 2: // Log apenas sMessage = StringBuild("LOG: Comando %1 executado", sCommand) END
// Registrar no log IF m_bLogging THEN LogCommand(sQuery, sCommand, sUser, sIP, sMessage) END END
// Registrar no log PROCEDURE PRIVATE LogCommand(sQuery is string, sCommand is string, sUser is string, sIP is string, sMessage is string) sLogEntry is string sLogEntry = DateToString(Today(), maskDateSystem) + " " + TimeToString(Now(), "HH:MM:SS") + TAB sLogEntry += sUser + TAB + sIP + TAB + sCommand + TAB sLogEntry += sMessage + TAB + Left(sQuery, 200) + CR
fSaveText(m_sLogFile, sLogEntry, foAdd) END
// Recarregar configuração PROCEDURE ReloadConfiguration() LoadConfiguration() END
// Obter status PROCEDURE GetStatus(): string IF NOT m_bEnabled THEN RETURN "Segurança desabilitada"
sStatus is string = "Segurança ativa - " SWITCH m_nBlockMode CASE 0: sStatus += "Modo: Bloquear" CASE 1: sStatus += "Modo: Avisar" CASE 2: sStatus += "Modo: Log apenas" END
RETURN sStatus END END ```
## 3. Wrapper para Conexão PostgreSQL Segura
```wlanguage // Classe wrapper para conexão PostgreSQL com segurança CLASS SecurePostgreSQLConnection PRIVATE m_Connection is Connection m_Security is PgSecurityManager m_sCurrentUser is string m_sClientIP is string
PUBLIC // Constructor CONSTRUCTOR(sServer is string, sDatabase is string, sUser is string, sPassword is string) // Inicializar gerenciador de segurança m_Security = new PgSecurityManager()
// Configurar conexão m_Connection..Provider = hAccessNativePostgreSQL m_Connection..Server = sServer m_Connection..Database = sDatabase m_Connection..User = sUser m_Connection..Password = sPassword
m_sCurrentUser = sUser m_sClientIP = NetIPAddress()
// Abrir conexão IF NOT HOpenConnection(m_Connection) THEN Error("Erro ao conectar: " + HErrorInfo()) END END
// Executar query com validação PROCEDURE ExecuteQuery(sQuery is string, sDataSource is string = "QueryResult"): boolean // Validar query antes de executar IF NOT m_Security.ValidateQuery(sQuery, m_sCurrentUser, m_sClientIP) THEN RETURN False END
// Executar query RETURN HExecuteSQLQuery(sDataSource, m_Connection, hQueryWithoutCorrection, sQuery) END
// Executar query parametrizada (mais segura) PROCEDURE ExecuteParameterizedQuery(sQuery is string, ...): boolean // Validar query base IF NOT m_Security.ValidateQuery(sQuery, m_sCurrentUser, m_sClientIP) THEN RETURN False END
// Montar query com parâmetros sFullQuery is string = StringBuild(sQuery, MyParameters)
RETURN HExecuteSQLQuery("QueryResult", m_Connection, hQueryWithoutCorrection, sFullQuery) END
// Adicionar query à whitelist PROCEDURE AddToWhitelist(sQuery is string, sDescription is string = "") sHash is string = HashString(HA_MD5, Upper(NoSpace(sQuery)))
// Encontrar próximo índice disponível i is int = 1 WHILE INIRead("WHITELIST", "Query" + i, "", m_Security.m_sIniPath) <> "" i++ END
// Adicionar ao INI INIWrite("WHITELIST", "Query" + i, sHash, m_Security.m_sIniPath) IF sDescription <> "" THEN INIWrite("WHITELIST", "Query" + i + "_Desc", sDescription, m_Security.m_sIniPath) END
// Recarregar configuração m_Security.ReloadConfiguration() END
// Obter status de segurança PROCEDURE GetSecurityStatus(): string RETURN m_Security.GetStatus() END END ```
## 4. Exemplo de Uso
```wlanguage // Inicializar conexão segura MyDB is SecurePostgreSQLConnection("localhost", "meu_banco", "usuario", "senha")
// Queries permitidas IF MyDB.ExecuteQuery("SELECT * FROM clientes WHERE ativo = true") THEN // Processar resultados WHILE NOT HOut() Info(QueryResult.nome) HReadNext() END END
// Esta query será bloqueada IF NOT MyDB.ExecuteQuery("DELETE FROM clientes WHERE id = 1") THEN // Mensagem de erro já foi exibida END
// Adicionar uma query específica à whitelist sQueryEspecial is string = "UPDATE configuracoes SET valor = 'novo' WHERE chave = 'versao'" MyDB.AddToWhitelist(sQueryEspecial, "Atualização de versão do sistema")
// Verificar status Info(MyDB.GetSecurityStatus()) ```
## 5. Utilitário de Configuração
```wlanguage // Window para gerenciar configurações de segurança PROCEDURE WIN_SecurityConfig() // Carregar configurações atuais sIniPath is string = fExeDir() + "\pgsql_security.ini"
// Interface para editar configurações EDT_EnableBlocking = INIRead("SECURITY", "EnableBlocking", "1", sIniPath) COMBO_BlockMode = Val(INIRead("SECURITY", "BlockMode", "0", sIniPath)) + 1 EDT_LogFile = INIRead("SECURITY", "LogFile", "", sIniPath)
// Carregar comandos bloqueados TABLE_Commands.DeleteAll() arrCommands is array of string = ["DELETE", "TRUNCATE", "DROP", "ALTER", "UPDATE", "GRANT", "REVOKE", "INSERT"] FOR EACH sCmd OF arrCommands nRow is int = TABLE_Commands.AddLine(sCmd, INIRead("BLOCKED_COMMANDS", sCmd, "0", sIniPath) = "1") END END
// Botão Salvar PROCEDURE BTN_Save_Click() sIniPath is string = fExeDir() + "\pgsql_security.ini"
// Salvar configurações gerais INIWrite("SECURITY", "EnableBlocking", EDT_EnableBlocking, sIniPath) INIWrite("SECURITY", "BlockMode", COMBO_BlockMode - 1, sIniPath) INIWrite("SECURITY", "LogFile", EDT_LogFile, sIniPath)
// Salvar comandos bloqueados FOR nRow = 1 TO TABLE_Commands.Count sCommand is string = TABLE_Commands[nRow].COL_Command bBlocked is boolean = TABLE_Commands[nRow].COL_Blocked INIWrite("BLOCKED_COMMANDS", sCommand, bBlocked ? "1" : "0", sIniPath) END
Info("Configurações salvas com sucesso!") END ```
## 6. Monitoramento e Análise de Logs
```wlanguage // Procedure para analisar logs de segurança PROCEDURE AnalyzeSecurityLogs(sLogFile is string): string sReport is string mapCommandCount is associative array of int mapUserViolations is associative array of int
// Ler arquivo de log sContent is string = fLoadText(sLogFile) arrLines is array of string StringToArray(sContent, arrLines, CR)
// Analisar cada linha FOR EACH sLine OF arrLines arrFields is array of string StringToArray(sLine, arrFields, TAB)
IF Count(arrFields) >= 5 THEN sCommand is string = arrFields[4] sUser is string = arrFields[2]
// Contar por comando IF mapCommandCount[sCommand] = 0 THEN mapCommandCount[sCommand] = 0 END mapCommandCount[sCommand]++
// Contar por usuário IF mapUserViolations[sUser] = 0 THEN mapUserViolations[sUser] = 0 END mapUserViolations[sUser]++ END END
// Gerar relatório sReport = "=== RELATÓRIO DE SEGURANÇA ===" + CR + CR sReport += "Comandos bloqueados:" + CR FOR EACH sCmd, nCount OF mapCommandCount sReport += StringBuild(" %1: %2 tentativas", sCmd, nCount) + CR END
sReport += CR + "Violações por usuário:" + CR FOR EACH sUser, nCount OF mapUserViolations sReport += StringBuild(" %1: %2 tentativas", sUser, nCount) + CR END
RETURN sReport END ```
**Principais características desta solução:**
1. **Flexibilidade**: Configuração via arquivo INI permite ajustes sem recompilar 2. **Whitelist**: Permite exceções para queries específicas usando hash MD5 3. **Modos de operação**: Bloquear, avisar ou apenas registrar 4. **Exceções**: Por usuário, IP ou aplicação 5. **Logging completo**: Registra todas as tentativas bloqueadas 6. **Fácil manutenção**: Interface para gerenciar configurações
A solução intercepta queries antes de enviá-las ao PostgreSQL, garantindo que comandos perigosos sejam bloqueados conforme configurado no arquivo INI.
-- Adriano José Boller ______________________________________________ Consultor e Representante Oficial da PcSoft no Brasil +55 (41) 99949 1800 adrianoboller@gmail.com skype: adrianoboller http://wxinformatica.com.br/ |
| |
| |
| | | |
|
| | |
| |
Registered member 4,520 messages |
|
Posted on June, 11 2025 - 3:27 PM |
Outra forma para desativar esses comandos SQL perigosos, você pode usar diferentes estratégias dependendo do SGBD e do contexto.
Vou mostrar as principais abordagens:
## 1. Controle via Permissões do Banco de Dados
### MySQL/MariaDB ```sql
CREATE USER 'usuario_leitura'@'localhost' IDENTIFIED BY 'senha'; GRANT SELECT ON database.* TO 'usuario_leitura'@'localhost';
REVOKE DELETE, DROP, ALTER, UPDATE, INSERT, CREATE, GRANT OPTION ON database.* FROM 'usuario'@'localhost'; ```
### SQL Server ```sql
CREATE ROLE db_readonly; GRANT SELECT ON SCHEMA::dbo TO db_readonly;
DENY DELETE, UPDATE, INSERT, ALTER TO [usuario]; DENY DROP TO [usuario]; ```
### PostgreSQL ```sql
REVOKE DELETE, TRUNCATE, DROP, ALTER, UPDATE, INSERT ON ALL TABLES IN SCHEMA public FROM usuario; REVOKE CREATE ON SCHEMA public FROM usuario; ```
## 2. Implementação no WinDev/WebDev
### Criar Camada de Validação ```wlanguage // Procedure para validar queries antes de executar PROCEDURE ValidateSQL(sQuery is string): boolean // Lista de comandos proibidos arrForbiddenCommands is array of strings = ["DELETE", "TRUNCATE", "DROP", "ALTER", "UPDATE", "GRANT", "REVOKE", "INSERT"]
// Converter query para maiúsculas para comparação sQueryUpper is string = Upper(NoSpace(sQuery))
// Verificar cada comando proibido FOR EACH sCommand OF arrForbiddenCommands IF Position(sQueryUpper, sCommand) > 0 THEN Error("Comando SQL não permitido: " + sCommand) RETURN False END END
RETURN True ```
### Wrapper Seguro para Execução SQL
```wlanguage // Procedure segura para executar queries PROCEDURE ExecuteSafeSQL(sQuery is string, sConnection is string = ""): boolean // Validar query primeiro IF NOT ValidateSQL(sQuery) THEN RETURN False END
// Executar apenas se for SELECT IF Position(Upper(sQuery), "SELECT") = 1 THEN IF sConnection = "" THEN RETURN HExecuteSQLQuery(dsResult, sQuery) ELSE
RETURN HExecuteSQLQuery(dsResult, sConnection, hQueryWithoutCorrection, sQuery) END ELSE Error("Apenas consultas SELECT são permitidas") RETURN False END ``` [/code]
## 3. Configuração de Conexão Restrita no WinDev
```wlanguage // Criar conexão com usuário limitado MyConnection is Connection MyConnection.Provider = hAccessHFClientServer MyConnection.Server = "servidor:4900" MyConnection.Database = "MinhaBase" MyConnection.User = "usuario_leitura" // Usuário com apenas SELECT MyConnection.Password = "senha"
// Usar sempre esta conexão para operações de usuários IF NOT HOpenConnection(MyConnection) THEN Error("Erro na conexão") ELSE HChangeConnection("*", MyConnection) END ```
## 4. Usar Views e Stored Procedures
### Criar Views para Leitura ```sql
CREATE VIEW vw_ClientesPublicos AS SELECT ID, Nome, Email FROM Clientes WHERE Ativo = 1;
GRANT SELECT ON vw_ClientesPublicos TO usuario_leitura; ```
### Stored Procedures Controladas ```wlanguage // No WinDev, criar procedures servidor HFSQL PROCEDURE SERVIDOR GetClientData(nIDClient): string // Aqui você controla exatamente o que pode ser feito sResult is string
// Apenas leitura permitida HReadSeekFirst(Cliente, IDCliente, nIDClient) IF HFound(Cliente) THEN sResult = Cliente.Nome + TAB + Cliente.Email END
RETURN sResult ```
## 5. Implementar Auditoria e Logs
```wlanguage // Procedure para logar tentativas de comandos proibidos PROCEDURE LogSecurityViolation(sUser is string, sCommand is string) LogFile is Log LogFile.NomeFicheiro = "security_violations.log" LogFile.Separador = TAB
sMessage is string = DateToString(Today()) + TAB + TimeToString(Now()) + TAB sMessage += sUser + TAB + "Tentativa de executar: " + sCommand
LogFile.Adiciona(sMessage) ```
## 6. Configuração Completa de Segurança
```wlanguage // Classe para gerenciar SQL seguro CLASS SecureDatabase PRIVATE m_bAllowWrite is boolean = False m_sCurrentUser is string
PUBLIC // Constructor CONSTRUCTOR(sUser is string) m_sCurrentUser = sUser // Verificar permissões do usuário m_bAllowWrite = CheckUserPermissions(sUser) END
// Executar query com validação PROCEDURE ExecuteQuery(sQuery is string): boolean // Remover espaços e converter para maiúsculas sCleanQuery is string = Upper(Trim(sQuery))
// Lista de comandos de escrita IF NOT m_bAllowWrite THEN arrWriteCommands is array of strings = [ "INSERT", "UPDATE", "DELETE", "TRUNCATE", "DROP", "ALTER", "CREATE", "GRANT", "REVOKE" ]
FOR EACH sCmd OF arrWriteCommands IF Position(sCleanQuery, sCmd) > 0 THEN LogSecurityViolation(m_sCurrentUser, sQuery) Error(StringBuild("Usuário %1 não tem permissão para executar %2", m_sCurrentUser, sCmd)) RETURN False END END END
// Executar query RETURN HExecuteSQLQuery(dsResult, sQuery) END END ```
## Exemplo de Uso Prático
```wlanguage // No início da aplicação MyDB is SecureDatabase("usuario_atual")
// Tentar executar comandos IF NOT MyDB.ExecuteQuery("SELECT * FROM Clientes") THEN // Erro será mostrado END
// Este comando será bloqueado IF NOT MyDB.ExecuteQuery("DELETE FROM Clientes WHERE ID = 1") THEN // Mensagem: "Usuário não tem permissão para executar DELETE" END ```
**Recomendações Finais:** 1. Sempre use o princípio do menor privilégio 2. Crie usuários diferentes para leitura e escrita 3. Implemente validação em múltiplas camadas 4. Mantenha logs de todas as tentativas bloqueadas 5. Use conexões SSL/TLS para o banco de dados 6. Realize auditorias regulares de segurança
-- Adriano José Boller ______________________________________________ Consultor e Representante Oficial da PcSoft no Brasil +55 (41) 99949 1800 adrianoboller@gmail.com skype: adrianoboller http://wxinformatica.com.br/ |
| |
| |
| | | |
|
| | |
| |
Registered member 4,520 messages |
|
Posted on June, 11 2025 - 3:52 PM |
A seguir analisei a estrutura do PostgreSQL e identifiquei os pontos de modificação feitos por esses comandos e estou sugerindo implementar o bloqueio via DLL CUSTOMIZADA DO POSTGRESQL com suporte a múltiplos bancos de dados.
Espero que essa contribuição venha a servir para os senhores:
## 1. Arquivos-chave do PostgreSQL para Modificação
### Arquivos Principais a Modificar:
```c // src/interfaces/libpq/fe-exec.c // Este arquivo contém as funções principais de execução de queries PGresult *PQexec(PGconn *conn, const char *query) PGresult *PQexecParams(PGconn *conn, const char *command, ...) PGresult *PQexecPrepared(PGconn *conn, const char *stmtName, ...)
// src/interfaces/libpq/libpq-fe.h // Header principal da libpq - interface cliente
// src/backend/tcop/postgres.c // Loop principal de processamento de comandos static void exec_simple_query(const char *query_string) ```
## 2. Estrutura do Arquivo INI Estendido
```ini [GLOBAL] ; Configurações globais EnableSecurity=1 DefaultMode=0 LogPath=C:\PostgreSQL\logs\security\ ; DLL de segurança customizada SecurityDLL=pgsql_security.dll
[DATABASE:producao] ; Banco de produção - somente leitura BlockMode=0 AllowedCommands=SELECT BlockedCommands=DELETE,TRUNCATE,DROP,ALTER,UPDATE,GRANT,REVOKE,INSERT LogFile=producao_security.log
[DATABASE:desenvolvimento] ; Banco de desenvolvimento - acesso completo BlockMode=2 AllowedCommands=ALL LogFile=dev_security.log
[DATABASE:relatorios] ; Banco de relatórios - leitura + views BlockMode=0 AllowedCommands=SELECT,CREATE VIEW,DROP VIEW BlockedCommands=DELETE,TRUNCATE,DROP TABLE,ALTER,UPDATE,INSERT LogFile=relatorios_security.log
[WHITELIST:producao] ; Queries permitidas por hash MD5 para o banco producao Query1=5d41402abc4b2a76b9719d911017c592 Query1_Desc=Update de status de pedidos pelo sistema
[EXCEPTIONS:producao] AdminUsers=postgres,dba_master TrustedIPs=192.168.1.10,192.168.1.11 TrustedApps=maintenance.exe,backup_tool.exe ```
## 3. DLL de Interceptação (pgsql_security.dll)
```c // pgsql_security.h #ifndef PGSQL_SECURITY_H #define PGSQL_SECURITY_H
#include <windows.h> #include <stdbool.h>
// Estrutura de configuração por banco typedef struct DatabaseConfig { char dbname[128]; int block_mode; // 0=Block, 1=Warn, 2=Log only char allowed_commands[1024]; char blocked_commands[1024]; char log_file[256]; struct DatabaseConfig *next; } DatabaseConfig;
// Estrutura principal de segurança typedef struct SecurityContext { bool enabled; char ini_path[256]; char log_path[256]; DatabaseConfig *db_configs; CRITICAL_SECTION cs; } SecurityContext;
// Funções exportadas __declspec(dllexport) bool InitializeSecurity(const char *ini_path); __declspec(dllexport) bool ValidateQuery(const char *dbname, const char *query, const char *user, const char *ip); __declspec(dllexport) void CleanupSecurity(void); __declspec(dllexport) void ReloadConfiguration(void);
#endif ```
```c // pgsql_security.c #include "pgsql_security.h" #include <stdio.h> #include <string.h> #include <time.h>
static SecurityContext g_security = {0};
// Função para carregar configuração do INI static void LoadINIConfiguration(const char *ini_path) { char section[256]; char key[256]; char value[1024];
// Carregar configurações globais GetPrivateProfileString("GLOBAL", "EnableSecurity", "1", value, sizeof(value), ini_path); g_security.enabled = (atoi(value) == 1);
GetPrivateProfileString("GLOBAL", "LogPath", "C:\\PostgreSQL\\logs\\", g_security.log_path, sizeof(g_security.log_path), ini_path);
// Limpar configurações antigas DatabaseConfig *current = g_security.db_configs; while (current) { DatabaseConfig *next = current->next; free(current); current = next; } g_security.db_configs = NULL;
// Carregar seções de banco de dados char sections[8192]; GetPrivateProfileSectionNames(sections, sizeof(sections), ini_path);
char *section_ptr = sections; while (*section_ptr) { if (strncmp(section_ptr, "DATABASE:", 9) == 0) { // Extrair nome do banco char *dbname = section_ptr + 9;
// Criar nova configuração DatabaseConfig *config = (DatabaseConfig*)calloc(1, sizeof(DatabaseConfig)); strcpy(config->dbname, dbname);
// Carregar configurações do banco sprintf(section, "DATABASE:%s", dbname);
GetPrivateProfileString(section, "BlockMode", "0", value, sizeof(value), ini_path); config->block_mode = atoi(value);
GetPrivateProfileString(section, "AllowedCommands", "", config->allowed_commands, sizeof(config->allowed_commands), ini_path);
GetPrivateProfileString(section, "BlockedCommands", "", config->blocked_commands, sizeof(config->blocked_commands), ini_path);
GetPrivateProfileString(section, "LogFile", "security.log", config->log_file, sizeof(config->log_file), ini_path);
// Adicionar à lista config->next = g_security.db_configs; g_security.db_configs = config; }
section_ptr += strlen(section_ptr) + 1; } }
// Inicializar sistema de segurança bool InitializeSecurity(const char *ini_path) { InitializeCriticalSection(&g_security.cs);
if (ini_path) { strcpy(g_security.ini_path, ini_path); } else { // Caminho padrão GetModuleFileName(NULL, g_security.ini_path, sizeof(g_security.ini_path)); char *last_slash = strrchr(g_security.ini_path, '\\'); if (last_slash) { strcpy(last_slash + 1, "pgsql_security.ini"); } }
LoadINIConfiguration(g_security.ini_path); return g_security.enabled; }
// Encontrar configuração para um banco específico static DatabaseConfig* FindDatabaseConfig(const char *dbname) { DatabaseConfig *current = g_security.db_configs; while (current) { if (strcmp(current->dbname, dbname) == 0) { return current; } current = current->next; } return NULL; }
// Verificar se comando está na lista static bool IsCommandInList(const char *command, const char *command_list) { if (strcmp(command_list, "ALL") == 0) return true;
char list_copy[1024]; strcpy(list_copy, command_list);
char *token = strtok(list_copy, ","); while (token) { // Remover espaços while (*token == ' ') token++; char *end = token + strlen(token) - 1; while (end > token && *end == ' ') *end
if (strcasecmp(token, command) == 0) { return true; } token = strtok(NULL, ","); }
return false; }
// Extrair comando principal da query static void ExtractCommand(const char *query, char *command, size_t cmd_size) { // Pular espaços iniciais while (*query && isspace(*query)) query++;
// Copiar até encontrar espaço ou fim size_t i = 0; while (*query && !isspace(*query) && i < cmd_size - 1) { command[i++] = toupper(*query++); } command[i] = '\0'; }
// Registrar no log static void LogSecurityEvent(const char *dbname, const char *query, const char *user, const char *ip, const char *action, const char *log_file) { EnterCriticalSection(&g_security.cs);
char full_path[512]; sprintf(full_path, "%s%s", g_security.log_path, log_file);
FILE *fp = fopen(full_path, "a"); if (fp) { time_t now = time(NULL); struct tm *tm_info = localtime(&now); char timestamp[64]; strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info);
fprintf(fp, "%s|%s|%s|%s|%s|%.200s\n", timestamp, dbname, user, ip, action, query); fclose(fp); }
LeaveCriticalSection(&g_security.cs); }
// Validar query bool ValidateQuery(const char *dbname, const char *query, const char *user, const char *ip) { if (!g_security.enabled) return true;
EnterCriticalSection(&g_security.cs);
// Encontrar configuração do banco DatabaseConfig *config = FindDatabaseConfig(dbname); if (!config) { // Se não há configuração específica, permitir LeaveCriticalSection(&g_security.cs); return true; }
// Extrair comando char command[64]; ExtractCommand(query, command, sizeof(command));
bool allowed = true; char action[64] = "ALLOWED";
// Verificar se comando está bloqueado if (strlen(config->blocked_commands) > 0) { if (IsCommandInList(command, config->blocked_commands)) { switch (config->block_mode) { case 0: // Block allowed = false; strcpy(action, "BLOCKED"); break; case 1: // Warn strcpy(action, "WARNING"); break; case 2: // Log only strcpy(action, "LOGGED"); break; } } }
// Verificar se comando está permitido (override blocked) if (strlen(config->allowed_commands) > 0) { if (!IsCommandInList(command, config->allowed_commands)) { if (config->block_mode == 0) { allowed = false; strcpy(action, "BLOCKED"); } } }
// Registrar evento if (!allowed || config->block_mode > 0) { LogSecurityEvent(dbname, query, user, ip, action, config->log_file); }
LeaveCriticalSection(&g_security.cs);
return allowed; }
// Limpar recursos void CleanupSecurity(void) { EnterCriticalSection(&g_security.cs);
DatabaseConfig *current = g_security.db_configs; while (current) { DatabaseConfig *next = current->next; free(current); current = next; } g_security.db_configs = NULL;
LeaveCriticalSection(&g_security.cs); DeleteCriticalSection(&g_security.cs); }
// Recarregar configuração void ReloadConfiguration(void) { EnterCriticalSection(&g_security.cs); LoadINIConfiguration(g_security.ini_path); LeaveCriticalSection(&g_security.cs); } ```
## 4. Modificação do libpq (fe-exec.c)
```c // Adicionar no início do arquivo fe-exec.c #include "pgsql_security.h"
typedef bool (*ValidateQueryFunc)(const char*, const char*, const char*, const char*); static ValidateQueryFunc g_validateQuery = NULL; static HMODULE g_securityDll = NULL;
// Função para carregar DLL de segurança static void LoadSecurityDLL() { if (g_securityDll) return;
char dll_path[MAX_PATH]; GetModuleFileName(NULL, dll_path, sizeof(dll_path)); char *last_slash = strrchr(dll_path, '\\'); if (last_slash) { strcpy(last_slash + 1, "pgsql_security.dll"); }
g_securityDll = LoadLibrary(dll_path); if (g_securityDll) { g_validateQuery = (ValidateQueryFunc)GetProcAddress(g_securityDll, "ValidateQuery");
// Inicializar segurança typedef bool (*InitFunc)(const char*); InitFunc initSecurity = (InitFunc)GetProcAddress(g_securityDll, "InitializeSecurity"); if (initSecurity) { initSecurity(NULL); // Usa caminho padrão } } }
// Modificar PQexec para incluir validação PGresult * PQexec(PGconn *conn, const char *query) { // Carregar DLL na primeira vez static int dll_loaded = 0; if (!dll_loaded) { LoadSecurityDLL(); dll_loaded = 1; }
// Validar query se DLL está carregada if (g_validateQuery && conn && conn->dbName) { char client_ip[64] = ""; if (conn->pghostaddr) { strcpy(client_ip, conn->pghostaddr); }
if (!g_validateQuery(conn->dbName, query, conn->pguser, client_ip)) { // Criar resultado de erro PGresult *res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); if (res) { res->errMsg = strdup("Query blocked by security policy"); } return res; } }
// Continuar com execução normal if (!PQexecStart(conn)) return NULL; if (!PQsendQuery(conn, query)) return NULL; return PQexecFinish(conn); } ```
## 5. Script de Build para Windows
```batch @echo off REM build_security_dll.bat
set PGSQL_SRC=C:\postgresql-source set PGSQL_INSTALL=C:\Program Files\PostgreSQL\14
REM Compilar DLL de segurança cl /LD /MT /I"%PGSQL_SRC%\src\include" ^ /I"%PGSQL_SRC%\src\interfaces\libpq" ^ pgsql_security.c ^ /link /DEF:pgsql_security.def ^ /OUT:pgsql_security.dll
REM Copiar para diretório do PostgreSQL copy pgsql_security.dll "%PGSQL_INSTALL%\bin\" copy pgsql_security.ini "%PGSQL_INSTALL%\bin\"
echo Build concluído! ```
## 6. Arquivo DEF para exportação
```def ; pgsql_security.def LIBRARY pgsql_security EXPORTS InitializeSecurity ValidateQuery CleanupSecurity ReloadConfiguration ```
## 7. Classe WinDev para Gerenciar Múltiplos Bancos
```wlanguage // Classe para gerenciar segurança multi-banco CLASS PostgreSQLMultiDBSecurity PRIVATE m_sIniPath is string m_mapDatabaseConfigs is associative array of DatabaseSecurityConfig
PUBLIC // Constructor CONSTRUCTOR(sIniPath is string = "") IF sIniPath = "" THEN m_sIniPath = CompleteDir(fExeDir()) + "pgsql_security.ini" ELSE m_sIniPath = sIniPath END
LoadAllConfigurations() END
// Carregar todas as configurações PROCEDURE PRIVATE LoadAllConfigurations() // Obter todas as seções DATABASE:* sSections is string = INISectionList(m_sIniPath) arrSections is array of string StringToArray(sSections, arrSections, CR)
FOR EACH sSection OF arrSections IF sSection.StartsWith("DATABASE:") THEN sDBName is string = sSection[[10 TO]] LoadDatabaseConfig(sDBName) END END END
// Carregar configuração de um banco específico PROCEDURE PRIVATE LoadDatabaseConfig(sDBName is string) config is DatabaseSecurityConfig config.DatabaseName = sDBName
sSection is string = "DATABASE:" + sDBName config.BlockMode = Val(INIRead(sSection, "BlockMode", "0", m_sIniPath)) config.AllowedCommands = INIRead(sSection, "AllowedCommands", "", m_sIniPath) config.BlockedCommands = INIRead(sSection, "BlockedCommands", "", m_sIniPath) config.LogFile = INIRead(sSection, "LogFile", sDBName + "_security.log", m_sIniPath)
m_mapDatabaseConfigs[sDBName] = config END
// Adicionar nova configuração de banco PROCEDURE AddDatabaseConfig(sDBName is string, nMode is int = 0, sAllowed is string = "SELECT", sBlocked is string = "DELETE,UPDATE,INSERT,DROP,ALTER,TRUNCATE") sSection is string = "DATABASE:" + sDBName
INIWrite(sSection, "BlockMode", nMode, m_sIniPath) INIWrite(sSection, "AllowedCommands", sAllowed, m_sIniPath) INIWrite(sSection, "BlockedCommands", sBlocked, m_sIniPath) INIWrite(sSection, "LogFile", sDBName + "_security.log", m_sIniPath)
// Recarregar LoadDatabaseConfig(sDBName) END
// Obter configuração de um banco PROCEDURE GetDatabaseConfig(sDBName is string): DatabaseSecurityConfig IF m_mapDatabaseConfigs[sDBName]..Empty THEN RESULT DatabaseSecurityConfig END RESULT m_mapDatabaseConfigs[sDBName] END
// Listar todos os bancos configurados PROCEDURE ListConfiguredDatabases(): array of string arrDatabases is array of string FOR EACH config, sDBName OF m_mapDatabaseConfigs arrDatabases.Add(sDBName) END RESULT arrDatabases END
// Gerar relatório de configuração PROCEDURE GenerateConfigReport(): string sReport is string = "=== CONFIGURAÇÃO DE SEGURANÇA POSTGRESQL ===" + CR + CR
FOR EACH config, sDBName OF m_mapDatabaseConfigs sReport += "Banco: " + sDBName + CR sReport += " Modo: " SWITCH config.BlockMode CASE 0: sReport += "Bloquear" CASE 1: sReport += "Avisar" CASE 2: sReport += "Log apenas" END sReport += CR sReport += " Comandos permitidos: " + config.AllowedCommands + CR sReport += " Comandos bloqueados: " + config.BlockedCommands + CR sReport += " Arquivo de log: " + config.LogFile + CR + CR END
RESULT sReport END END
// Estrutura de configuração STRUCTURE DatabaseSecurityConfig DatabaseName is string BlockMode is int AllowedCommands is string BlockedCommands is string LogFile is string END ```
## 8. Interface de Configuração Multi-Banco
```wlanguage // Window de configuração multi-banco PROCEDURE WIN_MultiDBSecurity() // Carregar gerenciador gclSecurityManager is PostgreSQLMultiDBSecurity()
// Preencher tabela de bancos TABLE_Databases.DeleteAll() arrDatabases is array of string = gclSecurityManager.ListConfiguredDatabases()
FOR EACH sDB OF arrDatabases config is DatabaseSecurityConfig = gclSecurityManager.GetDatabaseConfig(sDB) TABLE_Databases.AddLine(sDB, config.BlockMode, config.AllowedCommands) END END
// Adicionar novo banco PROCEDURE BTN_AddDatabase_Click() sDBName is string = Input("Nome do banco de dados:") IF sDBName = "" THEN RETURN
// Configuração padrão para novo banco gclSecurityManager.AddDatabaseConfig(sDBName)
// Atualizar interface WIN_MultiDBSecurity() END
// Editar configuração selecionada PROCEDURE BTN_EditDatabase_Click() IF TABLE_Databases..Empty THEN RETURN
sDBName is string = TABLE_Databases.COL_Database config is DatabaseSecurityConfig = gclSecurityManager.GetDatabaseConfig(sDBName)
// Abrir janela de edição Open(WIN_EditDBConfig, config)
// Atualizar após edição WIN_MultiDBSecurity() END ```
**Principais vantagens desta implementação:**
1. **Modular**: DLL separada facilita atualizações sem recompilar PostgreSQL 2. **Multi-banco**: Cada banco pode ter suas próprias regras 3. **Performance**: Validação rápida em memória 4. **Flexível**: Fácil adicionar novos bancos via INI 5. **Compatível**: Funciona com libpq existente 6. **Auditoria**: Log separado por banco
A solução intercepta queries no nível da libpq, permitindo controle granular por banco de dados sem modificar o servidor PostgreSQL.
-- Adriano José Boller ______________________________________________ Consultor e Representante Oficial da PcSoft no Brasil +55 (41) 99949 1800 adrianoboller@gmail.com skype: adrianoboller http://wxinformatica.com.br/ |
| |
| |
| | | |
|
| | |
| |
Registered member 4,520 messages |
|
Posted on June, 11 2025 - 3:58 PM |
# Documentação Completa: Bloqueio de Comandos SQL PostgreSQL via DLL Customizada
## 1. Visão Geral do Projeto
### Objetivo Implementar um sistema de segurança que intercepta e valida comandos SQL antes de serem executados no PostgreSQL, usando uma DLL customizada que lê configurações de um arquivo INI, permitindo diferentes níveis de acesso por banco de dados.
### Arquitetura da Solução
``` ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ Aplicação │────▶│ libpq.dll │────▶│ PostgreSQL │ │ (WinDev) │ │ (modificada) │ │ Server │ └─────────────────┘ └────────┬─────────┘ └─────────────────┘ │ ▼ ┌──────────────────┐ │ pgsql_security │ │ .dll │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ pgsql_security │ │ .ini │ └──────────────────┘ ```
## 2. Ferramentas e Ambiente Necessários
### 2.1 Compiladores e IDEs
#### Visual Studio 2019/2022 (Recomendado) ``` - Download: https://visualstudio.microsoft.com/ - Workload necessário: "Desenvolvimento para desktop com C++" - Componentes individuais: * MSVC v142/v143 - Build Tools C++ * Windows 10 SDK * CMake tools for Windows ```
#### MinGW-w64 (Alternativa) ``` - Download: https://www.mingw-w64.org/ - Versão recomendada: MinGW-W64 GCC-8.1.0 - Arquitetura: x86_64-posix-seh ```
### 2.2 PostgreSQL Source Code ``` - Download: https://www.postgresql.org/ftp/source/ - Versão: PostgreSQL 14.x ou 15.x - Extrair em: C:\postgresql-source ```
### 2.3 Ferramentas Auxiliares
#### Dependency Walker ``` - Para analisar dependências de DLL - Download: http://www.dependencywalker.com/ ```
#### Process Monitor ``` - Para monitorar acesso a arquivos/DLLs - Download: Microsoft Sysinternals ```
## 3. Preparação do Ambiente
### 3.1 Estrutura de Diretórios ``` C:\PostgreSQL-Security\ │ ├── source\ # Código fonte │ ├── pgsql_security.h │ ├── pgsql_security.c │ ├── pgsql_security.def │ └── libpq_patch.c │ ├── build\ # Arquivos compilados │ ├── pgsql_security.dll │ ├── pgsql_security.lib │ └── libpq.dll (modificada) │ ├── config\ # Configurações │ └── pgsql_security.ini │ ├── tools\ # Scripts e ferramentas │ ├── build.bat │ ├── install.bat │ └── test.bat │ └── docs\ # Documentação └── README.md ```
### 3.2 Configurar Variáveis de Ambiente
```batch @echo off REM setup_env.bat - Configurar ambiente de compilação
set PGSQL_SRC=C:\postgresql-source set PGSQL_INSTALL=C:\Program Files\PostgreSQL\14 set PROJECT_ROOT=C:\PostgreSQL-Security
REM Visual Studio 2022 call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
REM Adicionar PostgreSQL ao PATH set PATH=%PGSQL_INSTALL%\bin;%PATH% ```
## 4. Implementação Detalhada
### 4.1 Código da DLL de Segurança
#### pgsql_security.h (Estendido) ```c #ifndef PGSQL_SECURITY_H #define PGSQL_SECURITY_H
#ifdef _WIN32 #ifdef PGSQL_SECURITY_EXPORTS #define PGSQL_API __declspec(dllexport) #else #define PGSQL_API __declspec(dllimport) #endif #else #define PGSQL_API #endif
#include <windows.h> #include <stdbool.h> #include <stdint.h>
// Versão da DLL #define PGSQL_SECURITY_VERSION "1.0.0"
// Modos de bloqueio typedef enum { BLOCK_MODE_DENY = 0, // Bloqueia execução BLOCK_MODE_WARN = 1, // Permite mas avisa BLOCK_MODE_LOG_ONLY = 2 // Apenas registra } BlockMode;
// Estrutura de configuração por banco typedef struct DatabaseConfig { char dbname[128]; BlockMode block_mode; char allowed_commands[1024]; char blocked_commands[1024]; char log_file[256]; bool use_whitelist; struct DatabaseConfig *next; } DatabaseConfig;
// Contexto de segurança global typedef struct SecurityContext { bool enabled; char ini_path[MAX_PATH]; char log_path[MAX_PATH]; DatabaseConfig *db_configs; CRITICAL_SECTION cs; HANDLE hLogMutex; uint32_t query_count; uint32_t blocked_count; } SecurityContext;
// API Pública PGSQL_API bool InitializeSecurity(const char *ini_path); PGSQL_API bool ValidateQuery(const char *dbname, const char *query, const char *user, const char *ip); PGSQL_API void CleanupSecurity(void); PGSQL_API void ReloadConfiguration(void); PGSQL_API const char* GetSecurityVersion(void); PGSQL_API bool GetSecurityStats(uint32_t *total_queries, uint32_t *blocked_queries);
#endif // PGSQL_SECURITY_H ```
### 4.2 Compilação com Visual Studio
#### build_vs2022.bat ```batch @echo off REM Script de compilação usando Visual Studio 2022 echo ======================================== echo Compilando PostgreSQL Security DLL echo ========================================
REM Configurar ambiente call setup_env.bat
cd /d %PROJECT_ROOT%\source
REM Limpar build anterior if exist ..\build\*.* del /Q ..\build\*.*
REM Compilar DLL de segurança echo. echo Compilando pgsql_security.dll... cl /nologo /MT /O2 /W3 /D_WINDOWS /DPGSQL_SECURITY_EXPORTS ^ /I"%PGSQL_SRC%\src\include" ^ /I"%PGSQL_SRC%\src\interfaces\libpq" ^ /I"%PGSQL_SRC%\src\port" ^ /c pgsql_security.c
REM Linkar DLL link /nologo /DLL /DEF:pgsql_security.def ^ /OUT:..\build\pgsql_security.dll ^ /IMPLIB:..\build\pgsql_security.lib ^ pgsql_security.obj ^ kernel32.lib user32.lib advapi32.lib
REM Compilar libpq modificada echo. echo Compilando libpq modificada... cd %PGSQL_SRC%\src\interfaces\libpq
REM Aplicar patch copy /Y %PROJECT_ROOT%\source\libpq_patch.c fe-exec.c
REM Compilar nmake /f win32.mak clean nmake /f win32.mak
REM Copiar resultado copy /Y Release\libpq.dll %PROJECT_ROOT%\build\
echo. echo Compilação concluída! echo Arquivos gerados em: %PROJECT_ROOT%\build\ pause ```
### 4.3 Compilação com MinGW
#### build_mingw.bat ```batch @echo off REM Script de compilação usando MinGW echo ======================================== echo Compilando com MinGW-w64 echo ========================================
set PATH=C:\mingw64\bin;%PATH% set CC=gcc set PROJECT_ROOT=C:\PostgreSQL-Security
cd /d %PROJECT_ROOT%\source
REM Compilar DLL echo Compilando pgsql_security.dll... %CC% -shared -O2 -Wall -DPGSQL_SECURITY_EXPORTS ^ -I"%PGSQL_SRC%\src\include" ^ -I"%PGSQL_SRC%\src\interfaces\libpq" ^ pgsql_security.c ^ -o ..\build\pgsql_security.dll ^ -Wl,--out-implib,..\build\libpgsql_security.a ^ -lkernel32 -luser32 -ladvapi32
echo Compilação concluída! pause ```
## 5. Modificação da libpq
### 5.1 Patch para fe-exec.c
```c // libpq_patch.c - Adicionar ao início de fe-exec.c #ifdef _WIN32 #include <windows.h>
// Tipos de função da DLL de segurança typedef bool (*PFN_InitializeSecurity)(const char*); typedef bool (*PFN_ValidateQuery)(const char*, const char*, const char*, const char*); typedef void (*PFN_CleanupSecurity)(void);
// Variáveis globais para DLL static HMODULE g_hSecurityDll = NULL; static PFN_ValidateQuery g_pfnValidateQuery = NULL; static bool g_bSecurityInitialized = false;
// Carregar DLL de segurança static void LoadSecurityDLL(void) { if (g_bSecurityInitialized) return; char szDllPath[MAX_PATH]; GetModuleFileNameA(NULL, szDllPath, MAX_PATH); char *pLastSlash = strrchr(szDllPath, '\\'); if (pLastSlash) { strcpy(pLastSlash + 1, "pgsql_security.dll"); } // Tentar carregar DLL g_hSecurityDll = LoadLibraryA(szDllPath); if (g_hSecurityDll) { PFN_InitializeSecurity pfnInit = (PFN_InitializeSecurity)GetProcAddress(g_hSecurityDll, "InitializeSecurity"); g_pfnValidateQuery = (PFN_ValidateQuery)GetProcAddress(g_hSecurityDll, "ValidateQuery"); if (pfnInit && g_pfnValidateQuery) { // Inicializar com INI no mesmo diretório strcpy(strrchr(szDllPath, '\\') + 1, "pgsql_security.ini"); pfnInit(szDllPath); } } g_bSecurityInitialized = true; }
// Função auxiliar para validar query static bool ValidateQuerySecurity(PGconn *conn, const char *query) { LoadSecurityDLL(); if (g_pfnValidateQuery && conn && conn->dbName) { const char *user = conn->pguser ? conn->pguser : "unknown"; const char *ip = conn->pghostaddr ? conn->pghostaddr : "127.0.0.1"; return g_pfnValidateQuery(conn->dbName, query, user, ip); } return true; // Permitir se DLL não carregada } #endif
// Modificar função PQexec original PGresult * PQexec(PGconn *conn, const char *query) { if (!PQexecStart(conn)) return NULL; #ifdef _WIN32 // Validar query antes de enviar if (!ValidateQuerySecurity(conn, query)) { PGresult *res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); if (res) { res->errMsg = strdup("ERROR: Query blocked by security policy"); res->errQuery = strdup(query); } return res; } #endif if (!PQsendQuery(conn, query)) return NULL; return PQexecFinish(conn); } ```
## 6. Instalação e Configuração
### 6.1 Script de Instalação
#### install.bat ```batch @echo off REM Script de instalação echo ======================================== echo Instalando PostgreSQL Security echo ========================================
set PGSQL_BIN=C:\Program Files\PostgreSQL\14\bin set BUILD_DIR=C:\PostgreSQL-Security\build
REM Verificar se PostgreSQL está instalado if not exist "%PGSQL_BIN%\libpq.dll" ( echo ERRO: PostgreSQL não encontrado em %PGSQL_BIN% pause exit /b 1 )
REM Fazer backup da libpq original echo Fazendo backup da libpq.dll original... if not exist "%PGSQL_BIN%\libpq.dll.original" ( copy "%PGSQL_BIN%\libpq.dll" "%PGSQL_BIN%\libpq.dll.original" )
REM Parar serviços que possam estar usando a DLL echo Parando serviços... net stop postgresql-x64-14 2>nul
REM Copiar arquivos echo Copiando arquivos... copy /Y "%BUILD_DIR%\pgsql_security.dll" "%PGSQL_BIN%\" copy /Y "%BUILD_DIR%\libpq.dll" "%PGSQL_BIN%\" copy /Y "..\config\pgsql_security.ini" "%PGSQL_BIN%\"
REM Reiniciar serviços echo Reiniciando serviços... net start postgresql-x64-14
echo. echo Instalação concluída! echo. echo IMPORTANTE: echo 1. Configure o arquivo: %PGSQL_BIN%\pgsql_security.ini echo 2. Reinicie aplicações que usam PostgreSQL echo. pause ```
### 6.2 Arquivo de Configuração Exemplo
#### pgsql_security.ini (Completo) ```ini ; PostgreSQL Security Configuration ; Última atualização: 2024
[GLOBAL] ; Habilitar sistema de segurança (0=Desabilitado, 1=Habilitado) EnableSecurity=1
; Modo padrão para bancos não configurados (0=Permitir, 1=Bloquear) DefaultPolicy=0
; Diretório para arquivos de log LogPath=C:\ProgramData\PostgreSQL\Security\Logs\
; Nível de log (0=Erros, 1=Avisos, 2=Info, 3=Debug) LogLevel=2
; Tamanho máximo do arquivo de log em MB MaxLogSize=100
; Número de arquivos de log para manter LogRotation=10
; ======================================== ; Configurações por Banco de Dados ; ========================================
[DATABASE:producao] ; Modo de bloqueio: 0=Bloquear, 1=Avisar, 2=Log apenas BlockMode=0
; Comandos permitidos (separados por vírgula) AllowedCommands=SELECT
; Comandos bloqueados (tem prioridade sobre permitidos) BlockedCommands=DELETE,TRUNCATE,DROP,ALTER,UPDATE,GRANT,REVOKE,INSERT
; Arquivo de log específico LogFile=producao_security.log
; Usar whitelist de queries (0=Não, 1=Sim) UseWhitelist=1
[DATABASE:desenvolvimento] BlockMode=2 AllowedCommands=ALL LogFile=dev_security.log UseWhitelist=0
[DATABASE:homologacao] BlockMode=1 AllowedCommands=SELECT,INSERT,UPDATE BlockedCommands=DELETE,TRUNCATE,DROP,ALTER,GRANT,REVOKE LogFile=homolog_security.log UseWhitelist=0
[DATABASE:relatorios] BlockMode=0 AllowedCommands=SELECT,CREATE VIEW,DROP VIEW BlockedCommands=DELETE,TRUNCATE,DROP TABLE,ALTER TABLE,UPDATE,INSERT LogFile=relatorios_security.log UseWhitelist=0
; ======================================== ; Whitelist - Queries permitidas por hash MD5 ; ========================================
[WHITELIST:producao] ; Hash MD5 da query normalizada (sem espaços extras, uppercase) ; Exemplo: UPDATE pedidos SET status = 'processado' WHERE id = $1 Query1=a1b2c3d4e5f6789012345678901234567 Query1_Desc=Atualização de status de pedidos
Query2=b2c3d4e5f6789012345678901234568 Query2_Desc=Inserção de logs de auditoria
[WHITELIST:homologacao] Query1=c3d4e5f6789012345678901234569 Query1_Desc=Reset de dados de teste
; ======================================== ; Exceções - Usuários e IPs com acesso total ; ========================================
[EXCEPTIONS] ; Usuários com permissão total (separados por vírgula) AdminUsers=postgres,dba_master,backup_user
; IPs com permissão total TrustedIPs=127.0.0.1,::1,192.168.1.10,192.168.1.11
; Aplicações com permissão total TrustedApps=pg_dump.exe,pg_restore.exe,pgAdmin4.exe,maintenance.exe
; Horário de manutenção (formato HH:MM-HH:MM) MaintenanceWindow=02:00-04:00
; Dias da semana para manutenção (0=Dom, 6=Sáb) MaintenanceDays=0,6 ```
## 7. Testes e Validação
### 7.1 Script de Teste
#### test_security.bat ```batch @echo off REM Script de teste da DLL de segurança echo ======================================== echo Testando PostgreSQL Security echo ========================================
set PGHOST=localhost set PGPORT=5432 set PGUSER=test_user set PGPASSWORD=test_pass
REM Teste 1: Query permitida echo. echo Teste 1: SELECT (deve funcionar) psql -d producao -c "SELECT * FROM clientes LIMIT 1"
REM Teste 2: Query bloqueada echo. echo Teste 2: DELETE (deve ser bloqueado) psql -d producao -c "DELETE FROM clientes WHERE id = -1"
REM Teste 3: Banco sem restrições echo. echo Teste 3: DELETE em desenvolvimento (deve funcionar) psql -d desenvolvimento -c "DELETE FROM teste WHERE id = -1"
pause ```
### 7.2 Programa de Teste em C
```c // test_security.c #include <stdio.h> #include <stdlib.h> #include <libpq-fe.h>
void test_query(PGconn *conn, const char *query) { printf("\nTestando: %s\n", query); PGresult *res = PQexec(conn, query); ExecStatusType status = PQresultStatus(res); if (status == PGRES_TUPLES_OK || status == PGRES_COMMAND_OK) { printf("✓ Query executada com sucesso\n"); } else { printf("✗ Query bloqueada: %s\n", PQerrorMessage(conn)); } PQclear(res); }
int main() { // Conectar ao banco producao PGconn *conn = PQconnectdb("host=localhost dbname=producao user=test_user"); if (PQstatus(conn) != CONNECTION_OK) { fprintf(stderr, "Erro de conexão: %s\n", PQerrorMessage(conn)); PQfinish(conn); return 1; } printf("Conectado ao banco: producao\n"); // Testar diferentes queries test_query(conn, "SELECT version()"); test_query(conn, "SELECT * FROM clientes LIMIT 1"); test_query(conn, "INSERT INTO clientes (nome) VALUES ('Teste')"); test_query(conn, "UPDATE clientes SET nome = 'Teste2' WHERE id = 1"); test_query(conn, "DELETE FROM clientes WHERE id = 1"); test_query(conn, "DROP TABLE teste"); PQfinish(conn); return 0; } ```
## 8. Monitoramento e Manutenção
### 8.1 Visualizador de Logs
```c // log_viewer.c - Utilitário para visualizar logs #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h>
typedef struct { char timestamp[20]; char database[64]; char user[64]; char ip[64]; char action[20]; char query[500]; } LogEntry;
void parse_log_line(const char *line, LogEntry *entry) { sscanf(line, "%19[^|]|%63[^|]|%63[^|]|%63[^|]|%19[^|]|%499[^\n]", entry->timestamp, entry->database, entry->user, entry->ip, entry->action, entry->query); }
int main(int argc, char *argv[]) { if (argc != 2) { printf("Uso: %s <arquivo_de_log>\n", argv[0]); return 1; } FILE *fp = fopen(argv[1], "r"); if (!fp) { perror("Erro ao abrir arquivo"); return 1; } char line[1024]; int blocked_count = 0; int total_count = 0; printf("%-20s %-15s %-15s %-15s %-10s %s\n", "TIMESTAMP", "DATABASE", "USER", "IP", "ACTION", "QUERY"); printf("%s\n", "=".repeat(100)); while (fgets(line, sizeof(line), fp)) { LogEntry entry; parse_log_line(line, &entry); printf("%-20s %-15s %-15s %-15s %-10s %.50s...\n", entry.timestamp, entry.database, entry.user, entry.ip, entry.action, entry.query); total_count++; if (strcmp(entry.action, "BLOCKED") == 0) { blocked_count++; } } printf("\nTotal de queries: %d\n", total_count); printf("Queries bloqueadas: %d (%.1f%%)\n", blocked_count, (float)blocked_count/total_count * 100); fclose(fp); return 0; } ```
### 8.2 Script de Rotação de Logs
```batch @echo off REM rotate_logs.bat - Rotacionar logs de segurança set LOG_DIR=C:\ProgramData\PostgreSQL\Security\Logs
REM Obter data atual for /f "tokens=1-3 delims=/" %%a in ('date /t') do ( set TODAY=%%c%%a%%b )
REM Rotacionar logs cd /d "%LOG_DIR%" for %%f in (*.log) do ( if exist "%%f" ( move "%%f" "%%~nf_%TODAY%%%~xf" ) )
REM Comprimir logs antigos for %%f in (*_20*.log) do ( 7z a -tzip "%%~nf.zip" "%%f" && del "%%f" )
REM Limpar logs com mais de 30 dias forfiles /p "%LOG_DIR%" /m "*.zip" /d -30 /c "cmd /c del @file" ```
## 9. Troubleshooting
### 9.1 Problemas Comuns e Soluções
#### DLL não carrega ``` Problema: A DLL de segurança não é carregada Soluções: 1. Verificar se está no diretório correto (mesmo da libpq.dll) 2. Usar Dependency Walker para verificar dependências 3. Verificar permissões do arquivo 4. Checar Event Viewer do Windows para erros ```
#### Queries válidas sendo bloqueadas ``` Problema: Queries legítimas estão sendo bloqueadas Soluções: 1. Verificar configuração do banco no INI 2. Adicionar query ao whitelist 3. Revisar logs para entender o motivo 4. Temporariamente mudar BlockMode para 2 (log only) ```
#### Performance degradada ``` Problema: Lentidão após instalar a DLL Soluções: 1. Verificar se logs estão muito grandes 2. Desabilitar validação para bancos não críticos 3. Usar whitelist para queries frequentes 4. Verificar I/O do disco onde estão os logs ```
### 9.2 Debug e Diagnóstico
```c // debug_security.c - Ferramenta de debug #include <windows.h> #include <stdio.h> #include "pgsql_security.h"
int main() { // Testar carregamento da DLL HMODULE hDll = LoadLibrary("pgsql_security.dll"); if (!hDll) { printf("Erro ao carregar DLL: %lu\n", GetLastError()); return 1; } // Obter ponteiros das funções PFN_GetSecurityVersion pfnVersion = (PFN_GetSecurityVersion)GetProcAddress(hDll, "GetSecurityVersion"); if (pfnVersion) { printf("Versão da DLL: %s\n", pfnVersion()); } // Testar inicialização PFN_InitializeSecurity pfnInit = (PFN_InitializeSecurity)GetProcAddress(hDll, "InitializeSecurity"); if (pfnInit) { if (pfnInit("pgsql_security.ini")) { printf("✓ Segurança inicializada com sucesso\n"); } else { printf("✗ Falha ao inicializar segurança\n"); } } FreeLibrary(hDll); return 0; } ```
## 10. Integração com WinDev
### 10.1 Classe de Gerenciamento Completa
```wlanguage // Classe completa para gerenciar segurança PostgreSQL CLASS PostgreSQLSecurityManager PRIVATE m_sIniPath is string m_hDLL is system int m_bInitialized is boolean PUBLIC // Constructor CONSTRUCTOR(sIniPath is string = "") // Determinar caminho do INI IF sIniPath = "" THEN m_sIniPath = GetPostgreSQLBinPath() + "\pgsql_security.ini" ELSE m_sIniPath = sIniPath END // Carregar DLL LoadSecurityDLL() END // Destructor DESTRUCTOR() IF m_hDLL <> 0 THEN API("kernel32", "FreeLibrary", m_hDLL) END END // Obter caminho bin do PostgreSQL PROCEDURE PRIVATE GetPostgreSQLBinPath(): string sPath is string // Tentar registro 64-bit sPath = RegistryQueryValue("HKEY_LOCAL_MACHINE\SOFTWARE\PostgreSQL\Installations\postgresql-x64-14", "Base Directory") IF sPath <> "" THEN RESULT sPath + "\bin" // Tentar registro 32-bit sPath = RegistryQueryValue("HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\PostgreSQL\Installations\postgresql-14", "Base Directory") IF sPath <> "" THEN RESULT sPath + "\bin" // Caminhos padrão IF fDirectoryExist("C:\Program Files\PostgreSQL\14\bin") THEN RESULT "C:\Program Files\PostgreSQL\14\bin" END // Usar diretório da aplicação RESULT fExeDir() END // Carregar DLL de segurança PROCEDURE PRIVATE LoadSecurityDLL(): boolean sDLLPath is string = GetPostgreSQLBinPath() + "\pgsql_security.dll" IF NOT fFileExist(sDLLPath) THEN Error("DLL de segurança não encontrada: " + sDLLPath) RESULT False END // Carregar DLL m_hDLL = API("kernel32", "LoadLibraryA", sDLLPath) IF m_hDLL = 0 THEN Error("Erro ao carregar DLL de segurança") RESULT False END // Inicializar nProcAddr is system int = API("kernel32", "GetProcAddress", m_hDLL, "InitializeSecurity") IF nProcAddr <> 0 THEN m_bInitialized = API(nProcAddr, m_sIniPath) END RESULT m_bInitialized END // Validar query manualmente PROCEDURE ValidateQuery(sDatabase is string, sQuery is string, sUser is string = ""): boolean IF NOT m_bInitialized THEN RESULT True nProcAddr is system int = API("kernel32", "GetProcAddress", m_hDLL, "ValidateQuery") IF nProcAddr = 0 THEN RESULT True sIP is string = "127.0.0.1" RESULT API(nProcAddr, sDatabase, sQuery, sUser, sIP) END // Obter estatísticas PROCEDURE GetStatistics(): (nTotal is int, nBlocked is int) nTotal = 0 nBlocked = 0 IF m_bInitialized THEN nProcAddr is system int = API("kernel32", "GetProcAddress", m_hDLL, "GetSecurityStats") IF nProcAddr <> 0 THEN API(nProcAddr, &nTotal, &nBlocked) END END RESULT (nTotal, nBlocked) END // Recarregar configuração PROCEDURE ReloadConfiguration() IF m_bInitialized THEN nProcAddr is system int = API("kernel32", "GetProcAddress", m_hDLL, "ReloadConfiguration") IF nProcAddr <> 0 THEN API(nProcAddr) END END END END ```
## 11. Considerações de Segurança
### 11.1 Boas Práticas
1. **Permissões de Arquivo** - INI: Leitura apenas para o serviço PostgreSQL - DLL: Leitura e execução - Logs: Escrita apenas para o serviço
2. **Validação de Entrada** - Sempre validar e sanitizar queries - Usar prepared statements quando possível - Implementar rate limiting
3. **Monitoramento** - Alertas para tentativas de bloqueio - Análise regular de logs - Métricas de performance
### 11.2 Checklist de Segurança
- [ ] DLL assinada digitalmente - [ ] INI em local protegido - [ ] Logs com rotação automática - [ ] Backup da libpq original - [ ] Documentação de emergência - [ ] Plano de rollback
## 12. Conclusão
Esta solução fornece um sistema robusto e flexível para controlar acesso a comandos SQL no PostgreSQL, com as seguintes vantagens:
- **Transparente**: Aplicações não precisam ser modificadas - **Granular**: Controle por banco de dados - **Auditável**: Logs completos de todas as ações - **Flexível**: Configuração via INI sem recompilação - **Seguro**: Múltiplas camadas de validação
A implementação via DLL customizada permite atualizar as políticas de segurança sem afetar o servidor PostgreSQL ou as aplicações cliente.
-- Adriano José Boller ______________________________________________ Consultor e Representante Oficial da PcSoft no Brasil +55 (41) 99949 1800 adrianoboller@gmail.com skype: adrianoboller http://wxinformatica.com.br/ |
| |
| |
| | | |
|
| | |
| |
Registered member 4,520 messages |
|
Posted on June, 11 2025 - 4:15 PM |
Sim, você pode cadastrar comandos adicionais facilmente! A solução foi projetada para ser flexível. Vou mostrar como adicionar novos comandos bloqueados:
## 1. Adicionando Comandos via Arquivo INI
### 1.1 Método Simples - Direto no INI
```ini [DATABASE:producao] ; Comandos bloqueados padrão BlockedCommands=DELETE,TRUNCATE,DROP,ALTER,UPDATE,GRANT,REVOKE,INSERT
; Para adicionar novos comandos, simplesmente inclua na lista BlockedCommands=DELETE,TRUNCATE,DROP,ALTER,UPDATE,GRANT,REVOKE,INSERT,MERGE,COPY,VACUUM,ANALYZE,CLUSTER,REINDEX,COMMENT,EXECUTE,CALL
; Ou bloqueie comandos específicos do PostgreSQL BlockedCommands=DELETE,TRUNCATE,DROP,ALTER,UPDATE,GRANT,REVOKE,INSERT,CREATE FUNCTION,CREATE PROCEDURE,CREATE TRIGGER,CREATE RULE,CREATE TYPE,CREATE EXTENSION ```
### 1.2 Seção de Comandos Customizados
Vamos melhorar o INI para suportar comandos customizados:
```ini [CUSTOM_COMMANDS] ; Definir comandos adicionais e suas regras ; Formato: COMANDO=tipo_bloqueio,mensagem_personalizada
; Bloquear comandos de backup/restore COPY=BLOCK,Comando COPY não permitido por política de segurança COPY TO=BLOCK,Exportação de dados não permitida COPY FROM=BLOCK,Importação de dados não permitida
; Bloquear comandos administrativos VACUUM=WARN,VACUUM deve ser executado apenas em horário de manutenção ANALYZE=LOG,ANALYZE executado - verificar performance CLUSTER=BLOCK,CLUSTER não permitido em produção REINDEX=BLOCK,REINDEX não permitido - usar manutenção programada
; Bloquear comandos DDL específicos CREATE FUNCTION=BLOCK,Criação de funções não permitida CREATE PROCEDURE=BLOCK,Criação de procedures não permitida CREATE TRIGGER=BLOCK,Criação de triggers não permitida CREATE EXTENSION=BLOCK,Instalação de extensões não permitida
; Bloquear comandos perigosos SET ROLE=BLOCK,Mudança de role não permitida SET SESSION AUTHORIZATION=BLOCK,Mudança de autorização não permitida LOAD=BLOCK,Carregamento de bibliotecas não permitido
; Comandos de transação BEGIN ISOLATION LEVEL SERIALIZABLE=WARN,Nível de isolamento pode impactar performance LOCK TABLE=WARN,Lock de tabela pode causar bloqueios
[DATABASE:producao] ; Referenciar comandos customizados UseCustomCommands=1 BlockedCommands=DELETE,TRUNCATE,DROP,ALTER,UPDATE,GRANT,REVOKE,INSERT ; Adicionar categoria de comandos BlockedCategories=DDL,DML,ADMIN,BACKUP ```
## 2. Código C Atualizado para Suportar Comandos Customizados
### 2.1 Estrutura Expandida
```c // pgsql_security_extended.h typedef struct CustomCommand { char command[128]; char block_type[16]; // BLOCK, WARN, LOG char message[256]; struct CustomCommand *next; } CustomCommand;
typedef struct CommandCategory { char name[32]; char commands[1024]; } CommandCategory;
typedef struct DatabaseConfig { char dbname[128]; BlockMode block_mode; char allowed_commands[1024]; char blocked_commands[1024]; char blocked_categories[256]; bool use_custom_commands; char log_file[256]; struct DatabaseConfig *next; } DatabaseConfig;
typedef struct SecurityContext { bool enabled; char ini_path[MAX_PATH]; char log_path[MAX_PATH]; DatabaseConfig *db_configs; CustomCommand *custom_commands; CommandCategory categories[10]; int category_count; CRITICAL_SECTION cs; } SecurityContext; ```
### 2.2 Função para Carregar Comandos Customizados
```c // Carregar comandos customizados do INI static void LoadCustomCommands(const char *ini_path) { char buffer[32768]; char *line; // Limpar comandos anteriores CustomCommand *current = g_security.custom_commands; while (current) { CustomCommand *next = current->next; free(current); current = next; } g_security.custom_commands = NULL; // Ler seção CUSTOM_COMMANDS GetPrivateProfileSection("CUSTOM_COMMANDS", buffer, sizeof(buffer), ini_path); line = buffer; while (*line) { char *equals = strchr(line, '='); if (equals) { *equals = '\0'; char *value = equals + 1; // Parse: COMANDO=tipo,mensagem char *comma = strchr(value, ','); if (comma) { *comma = '\0'; char *message = comma + 1; // Criar novo comando customizado CustomCommand *cmd = (CustomCommand*)calloc(1, sizeof(CustomCommand)); strncpy(cmd->command, line, sizeof(cmd->command) - 1); strncpy(cmd->block_type, value, sizeof(cmd->block_type) - 1); strncpy(cmd->message, message, sizeof(cmd->message) - 1); // Adicionar à lista cmd->next = g_security.custom_commands; g_security.custom_commands = cmd; } } line += strlen(line) + 1; } // Carregar categorias LoadCommandCategories(ini_path); }
// Carregar categorias de comandos static void LoadCommandCategories(const char *ini_path) { g_security.category_count = 0; // DDL - Data Definition Language GetPrivateProfileString("COMMAND_CATEGORIES", "DDL", "CREATE,ALTER,DROP,RENAME,TRUNCATE,COMMENT", g_security.categories[0].commands, sizeof(g_security.categories[0].commands), ini_path); strcpy(g_security.categories[0].name, "DDL"); // DML - Data Manipulation Language GetPrivateProfileString("COMMAND_CATEGORIES", "DML", "INSERT,UPDATE,DELETE,MERGE,COPY", g_security.categories[1].commands, sizeof(g_security.categories[1].commands), ini_path); strcpy(g_security.categories[1].name, "DML"); // ADMIN - Administrative Commands GetPrivateProfileString("COMMAND_CATEGORIES", "ADMIN", "VACUUM,ANALYZE,CLUSTER,REINDEX,CHECKPOINT,LOAD", g_security.categories[2].commands, sizeof(g_security.categories[2].commands), ini_path); strcpy(g_security.categories[2].name, "ADMIN"); // BACKUP - Backup/Restore Commands GetPrivateProfileString("COMMAND_CATEGORIES", "BACKUP", "COPY TO,COPY FROM,pg_dump,pg_restore", g_security.categories[3].commands, sizeof(g_security.categories[3].commands), ini_path); strcpy(g_security.categories[3].name, "BACKUP"); g_security.category_count = 4; }
// Verificar comando customizado static CustomCommand* FindCustomCommand(const char *query) { CustomCommand *current = g_security.custom_commands; while (current) { if (strncasecmp(query, current->command, strlen(current->command)) == 0) { return current; } current = current->next; } return NULL; }
// Verificar se comando está em categoria bloqueada static bool IsCommandInBlockedCategory(const char *command, const char *blocked_categories) { if (!blocked_categories || strlen(blocked_categories) == 0) return false; // Para cada categoria bloqueada char categories_copy[256]; strcpy(categories_copy, blocked_categories); char *category = strtok(categories_copy, ","); while (category) { // Remover espaços while (*category == ' ') category++; // Procurar categoria for (int i = 0; i < g_security.category_count; i++) { if (strcasecmp(g_security.categories[i].name, category) == 0) { // Verificar se comando está nesta categoria if (IsCommandInList(command, g_security.categories[i].commands)) { return true; } } } category = strtok(NULL, ","); } return false; } ```
### 2.3 Função ValidateQuery Atualizada
```c // Validar query com suporte a comandos customizados bool ValidateQuery(const char *dbname, const char *query, const char *user, const char *ip) { if (!g_security.enabled) return true; EnterCriticalSection(&g_security.cs); DatabaseConfig *config = FindDatabaseConfig(dbname); if (!config) { LeaveCriticalSection(&g_security.cs); return true; } // Extrair comando principal char command[64]; ExtractCommand(query, command, sizeof(command)); bool allowed = true; char action[64] = "ALLOWED"; char message[256] = ""; // 1. Verificar comandos customizados primeiro if (config->use_custom_commands) { CustomCommand *custom = FindCustomCommand(query); if (custom) { strcpy(action, custom->block_type); strcpy(message, custom->message); if (strcasecmp(custom->block_type, "BLOCK") == 0) { allowed = false; } } } // 2. Verificar categorias bloqueadas if (allowed && strlen(config->blocked_categories) > 0) { if (IsCommandInBlockedCategory(command, config->blocked_categories)) { allowed = false; strcpy(action, "BLOCKED"); sprintf(message, "Comando %s bloqueado por categoria", command); } } // 3. Verificar lista de comandos bloqueados if (allowed && strlen(config->blocked_commands) > 0) { if (IsCommandInList(command, config->blocked_commands)) { switch (config->block_mode) { case 0: // Block allowed = false; strcpy(action, "BLOCKED"); sprintf(message, "Comando %s não permitido", command); break; case 1: // Warn strcpy(action, "WARNING"); sprintf(message, "Aviso: Comando %s executado", command); break; case 2: // Log only strcpy(action, "LOGGED"); break; } } } // Registrar evento if (!allowed || config->block_mode > 0 || strlen(message) > 0) { LogSecurityEventEx(dbname, query, user, ip, action, message, config->log_file); } LeaveCriticalSection(&g_security.cs); // Se bloqueado, definir mensagem de erro if (!allowed && strlen(message) > 0) { SetLastError(ERROR_ACCESS_DENIED); // Armazenar mensagem para ser recuperada pela aplicação strcpy(g_last_error_message, message); } return allowed; } ```
## 3. Interface WinDev para Gerenciar Comandos Customizados
### 3.1 Window de Gerenciamento de Comandos
```wlanguage // WIN_CustomCommands - Janela para gerenciar comandos customizados PROCEDURE WIN_CustomCommands() // Carregar comandos existentes LoadCustomCommands() // Preencher categorias COMBO_Category.Add("DDL - Data Definition Language") COMBO_Category.Add("DML - Data Manipulation Language") COMBO_Category.Add("ADMIN - Administrative") COMBO_Category.Add("BACKUP - Backup/Restore") COMBO_Category.Add("CUSTOM - Personalizado") END
// Carregar comandos do INI PROCEDURE LoadCustomCommands() sIniPath is string = GetSecurityINIPath() TABLE_Commands.DeleteAll() // Ler seção CUSTOM_COMMANDS sSection is string = INIRead("CUSTOM_COMMANDS", "", "", sIniPath) IF sSection = "" THEN RETURN // Parse cada linha FOR EACH STRING sLine OF sSection SEPARATED BY CR nPos is int = Position(sLine, "=") IF nPos > 0 THEN sCommand is string = sLine[[1 TO nPos-1]] sValue is string = sLine[[nPos+1 TO]] // Parse tipo e mensagem nComma is int = Position(sValue, ",") IF nComma > 0 THEN sType is string = sValue[[1 TO nComma-1]] sMessage is string = sValue[[nComma+1 TO]] TABLE_Commands.AddLine(sCommand, sType, sMessage) END END END END
// Adicionar novo comando PROCEDURE BTN_AddCommand_Click() sCommand is string = EDT_Command sType is string = COMBO_Type sMessage is string = EDT_Message IF sCommand = "" THEN Error("Digite o comando SQL") RETURN END // Adicionar à tabela TABLE_Commands.AddLine(sCommand, sType, sMessage) // Limpar campos EDT_Command = "" EDT_Message = "" END
// Salvar comandos no INI PROCEDURE BTN_Save_Click() sIniPath is string = GetSecurityINIPath() // Limpar seção existente INIWrite("CUSTOM_COMMANDS", "", "", sIniPath) // Salvar cada comando FOR nRow = 1 TO TABLE_Commands.Count sCommand is string = TABLE_Commands[nRow].COL_Command sType is string = TABLE_Commands[nRow].COL_Type sMessage is string = TABLE_Commands[nRow].COL_Message sValue is string = sType + "," + sMessage INIWrite("CUSTOM_COMMANDS", sCommand, sValue, sIniPath) END Info("Comandos salvos com sucesso!") // Notificar DLL para recarregar ReloadSecurityConfiguration() END
// Importar comandos de arquivo PROCEDURE BTN_Import_Click() sFile is string = fSelect("", "", "Selecione arquivo de comandos", ... "Arquivos texto (*.txt)" + TAB + "*.txt" + CR + ... "Arquivos CSV (*.csv)" + TAB + "*.csv") IF sFile = "" THEN RETURN // Ler arquivo sContent is string = fLoadText(sFile) // Parse linhas (formato: COMANDO|TIPO|MENSAGEM) FOR EACH STRING sLine OF sContent SEPARATED BY CR arrParts is array of string StringToArray(sLine, arrParts, "|") IF arrParts.Count >= 3 THEN TABLE_Commands.AddLine(arrParts[1], arrParts[2], arrParts[3]) END END Info("Comandos importados com sucesso!") END ```
### 3.2 Classe para Gerenciar Comandos por Categoria
```wlanguage // Classe para gerenciar comandos por categoria CLASS CommandCategoryManager PRIVATE m_mapCategories is associative array of array of string m_sIniPath is string PUBLIC // Constructor CONSTRUCTOR(sIniPath is string = "") m_sIniPath = sIniPath LoadCategories() END // Carregar categorias padrão PROCEDURE PRIVATE LoadCategories() // DDL - Data Definition Language m_mapCategories["DDL"].Add("CREATE") m_mapCategories["DDL"].Add("ALTER") m_mapCategories["DDL"].Add("DROP") m_mapCategories["DDL"].Add("RENAME") m_mapCategories["DDL"].Add("TRUNCATE") m_mapCategories["DDL"].Add("COMMENT") // DML - Data Manipulation Language m_mapCategories["DML"].Add("INSERT") m_mapCategories["DML"].Add("UPDATE") m_mapCategories["DML"].Add("DELETE") m_mapCategories["DML"].Add("MERGE") m_mapCategories["DML"].Add("COPY") // DCL - Data Control Language m_mapCategories["DCL"].Add("GRANT") m_mapCategories["DCL"].Add("REVOKE") m_mapCategories["DCL"].Add("CREATE ROLE") m_mapCategories["DCL"].Add("ALTER ROLE") m_mapCategories["DCL"].Add("DROP ROLE") // TCL - Transaction Control Language m_mapCategories["TCL"].Add("BEGIN") m_mapCategories["TCL"].Add("COMMIT") m_mapCategories["TCL"].Add("ROLLBACK") m_mapCategories["TCL"].Add("SAVEPOINT") m_mapCategories["TCL"].Add("SET TRANSACTION") // Administrative m_mapCategories["ADMIN"].Add("VACUUM") m_mapCategories["ADMIN"].Add("ANALYZE") m_mapCategories["ADMIN"].Add("CLUSTER") m_mapCategories["ADMIN"].Add("REINDEX") m_mapCategories["ADMIN"].Add("CHECKPOINT") // Perigosos m_mapCategories["DANGEROUS"].Add("DROP DATABASE") m_mapCategories["DANGEROUS"].Add("DROP SCHEMA") m_mapCategories["DANGEROUS"].Add("TRUNCATE") m_mapCategories["DANGEROUS"].Add("DELETE FROM") m_mapCategories["DANGEROUS"].Add("UPDATE SET") // Extensões e Funções m_mapCategories["EXTENSIONS"].Add("CREATE EXTENSION") m_mapCategories["EXTENSIONS"].Add("DROP EXTENSION") m_mapCategories["EXTENSIONS"].Add("CREATE FUNCTION") m_mapCategories["EXTENSIONS"].Add("CREATE PROCEDURE") m_mapCategories["EXTENSIONS"].Add("CREATE TRIGGER") END // Adicionar comando a uma categoria PROCEDURE AddCommandToCategory(sCategory is string, sCommand is string) IF NOT m_mapCategories[sCategory]..Exists THEN m_mapCategories[sCategory] = new array of string END m_mapCategories[sCategory].Add(sCommand) SaveCategories() END // Obter comandos de uma categoria PROCEDURE GetCategoryCommands(sCategory is string): array of string IF m_mapCategories[sCategory]..Exists THEN RESULT m_mapCategories[sCategory] END RESULT new array of string END // Salvar categorias no INI PROCEDURE PRIVATE SaveCategories() FOR EACH arrCommands, sCategory OF m_mapCategories sCommands is string = ArrayToString(arrCommands, ",") INIWrite("COMMAND_CATEGORIES", sCategory, sCommands, m_sIniPath) END END // Verificar se comando pertence a categoria PROCEDURE IsCommandInCategory(sCommand is string, sCategory is string): boolean IF NOT m_mapCategories[sCategory]..Exists THEN RESULT False END sCommandUpper is string = Upper(sCommand) FOR EACH sCmd OF m_mapCategories[sCategory] IF sCommandUpper.StartsWith(Upper(sCmd)) THEN RESULT True END END RESULT False END // Listar todas as categorias PROCEDURE ListCategories(): array of string arrCategories is array of string FOR EACH arrCommands, sCategory OF m_mapCategories arrCategories.Add(sCategory) END RESULT arrCategories END END ```
## 4. Exemplos de Uso
### 4.1 Configuração para Diferentes Cenários
#### Banco de Produção Ultra-Restritivo ```ini [DATABASE:producao_critica] BlockMode=0 ; Permitir apenas SELECT básico AllowedCommands=SELECT ; Bloquear categorias inteiras BlockedCategories=DDL,DML,DCL,TCL,ADMIN,DANGEROUS,EXTENSIONS ; Usar comandos customizados UseCustomCommands=1
[CUSTOM_COMMANDS] ; Bloquear SELECTs perigosos SELECT INTO=BLOCK,SELECT INTO não permitido em produção SELECT * FROM=WARN,Evite SELECT * - especifique colunas SELECT WITHOUT WHERE=WARN,SELECT sem WHERE pode impactar performance
; Bloquear funções perigosas pg_sleep=BLOCK,Função pg_sleep não permitida pg_terminate_backend=BLOCK,Terminar conexões não permitido pg_cancel_backend=BLOCK,Cancelar queries não permitido ```
#### Banco de Desenvolvimento com Avisos ```ini [DATABASE:desenvolvimento] BlockMode=1 AllowedCommands=ALL UseCustomCommands=1
[CUSTOM_COMMANDS] ; Avisar sobre comandos perigosos DROP TABLE=WARN,⚠️ CUIDADO: Você está deletando uma tabela! TRUNCATE=WARN,⚠️ TRUNCATE remove TODOS os dados! DELETE WITHOUT WHERE=WARN,⚠️ DELETE sem WHERE afeta todos registros! UPDATE WITHOUT WHERE=WARN,⚠️ UPDATE sem WHERE afeta todos registros! ```
### 4.2 Script para Popular Comandos Comuns
```wlanguage // Procedure para adicionar comandos PostgreSQL comuns PROCEDURE PopulateCommonPostgreSQLCommands() manager is CommandCategoryManager() // Comandos de Sistema manager.AddCommandToCategory("SYSTEM", "SHOW") manager.AddCommandToCategory("SYSTEM", "SET") manager.AddCommandToCategory("SYSTEM", "RESET") manager.AddCommandToCategory("SYSTEM", "DISCARD") // Comandos de Cópia e Backup manager.AddCommandToCategory("BACKUP", "COPY TO STDOUT") manager.AddCommandToCategory("BACKUP", "COPY TO PROGRAM") manager.AddCommandToCategory("BACKUP", "\\copy") // Comandos de Informação manager.AddCommandToCategory("INFO", "EXPLAIN") manager.AddCommandToCategory("INFO", "EXPLAIN ANALYZE") manager.AddCommandToCategory("INFO", "\\d") manager.AddCommandToCategory("INFO", "\\dt") manager.AddCommandToCategory("INFO", "\\l") // Comandos Perigosos Específicos manager.AddCommandToCategory("DANGEROUS", "DROP DATABASE IF EXISTS") manager.AddCommandToCategory("DANGEROUS", "DROP SCHEMA CASCADE") manager.AddCommandToCategory("DANGEROUS", "DROP OWNED BY") manager.AddCommandToCategory("DANGEROUS", "REASSIGN OWNED") Info("Comandos PostgreSQL adicionados com sucesso!") END ```
### 4.3 Validação em Tempo Real
```wlanguage // Procedure para validar query em tempo real PROCEDURE ValidateQueryRealTime(sQuery is string, sDatabase is string): (bValid is boolean, sMessage is string) security is PostgreSQLSecurityManager() // Validar query IF NOT security.ValidateQuery(sDatabase, sQuery, CurrentUser()) THEN // Obter mensagem de erro detalhada sMessage = security.GetLastErrorMessage() RESULT (False, sMessage) END // Query permitida RESULT (True, "Query permitida") END
// Exemplo de uso em campo de entrada PROCEDURE EDT_Query_Exit() bValid, sMessage = ValidateQueryRealTime(EDT_Query, COMBO_Database) IF NOT bValid THEN // Mostrar aviso visual EDT_Query..BrushColor = LightRed STC_Warning = sMessage STC_Warning..Visible = True ELSE EDT_Query..BrushColor = White STC_Warning..Visible = False END END ```
## 5. Monitoramento de Comandos Bloqueados
### 5.1 Dashboard de Comandos
```wlanguage // Window para monitorar comandos bloqueados PROCEDURE WIN_SecurityDashboard() // Atualizar estatísticas RefreshStatistics() // Carregar comandos mais bloqueados LoadTopBlockedCommands() // Iniciar timer de atualização Timer(RefreshStatistics, 5s) END
// Carregar comandos mais bloqueados PROCEDURE LoadTopBlockedCommands() // Analisar logs sLogPath is string = GetSecurityLogPath() mapCommands is associative array of int // Ler todos os logs arrFiles is array of string = fListFile(sLogPath + "*.log") FOR EACH sFile OF arrFiles AnalyzeLogFile(sFile, mapCommands) END // Ordenar e mostrar top 10 CHART_BlockedCommands.DeleteAll() arrSorted is array of CommandCount FOR EACH nCount, sCommand OF mapCommands cc is CommandCount cc.Command = sCommand cc.Count = nCount arrSorted.Add(cc) END ArraySort(arrSorted, asMember, "-Count") FOR i = 1 TO Min(10, arrSorted.Count) CHART_BlockedCommands.AddData(1, arrSorted[i].Count, arrSorted[i].Command) END END ```
Com essas implementações, você tem total flexibilidade para:
1. **Adicionar novos comandos** via interface ou diretamente no INI 2. **Criar categorias** de comandos para facilitar gerenciamento 3. **Definir mensagens personalizadas** para cada comando 4. **Importar/Exportar** listas de comandos 5. **Monitorar** quais comandos são mais bloqueados 6. **Validar em tempo real** antes da execução
A solução é completamente extensível e permite adicionar qualquer comando SQL que você queira bloquear, avisar ou registrar.
-- Adriano José Boller ______________________________________________ Consultor e Representante Oficial da PcSoft no Brasil +55 (41) 99949 1800 adrianoboller@gmail.com skype: adrianoboller http://wxinformatica.com.br/ |
| |
| |
| | | |
|
| | | | |
| | |
|