Noções de Segurança no SQL Server - Parte I
Por Robson Brandão, MVP
Uma frase que gosto muito de falar é: "Segurança não é um produto, mas sim um processo". Não existe um produto seguro, se os processos adotados para segurança não são seguros.
O primeiro item da segurança em um servidor é o LOGIN. E por isso, é por ele que você deve começar os procedimentos de segurança, como senhas longas e complexas.
Se você for capaz de garantir que sua rede está totalmente protegida de ferramentas de captura de pacotes, que todo mundo na rede é honesto e confiável, e que na sua empresa não há segredos e que nenhum dado precisa ser protegido ou escondido, então, você pode ignorar como as contas de usuários e senhas trafegam entre o cliente e o servidor da sua empresa e continuar usando senhas simples como teste123.
Como esse cenário não existe você precisa impedir que as senhas estejam roubadas, que as identidades dos usuários sejam descobertas, e que os dados possam ser vistos por pessoas erradas.
Estratégias de Senhas no SQL Server
Não pretendo falar nesse artigo sobre os métodos de autenticação do SQL Server, mas sim, de mostrar como trabalha a comunicação entre o cliente e o servidor.
Em versões anteriores do SQL Server, as senhas dos usuários não eram criptografadas na tabela syslogins. O sa podia ler a senha de qualquer usuário simplesmente consultando a syslogins.
A partir da versão 6.5, o SQL Server passou a criptografar as senhas dos usuários num algoritmo de hashing antes de armazená-las na tabela syslogins (SQL Server 6.5) e sysxlogins (SQL Server 7 em diante).
Observando o código da stored procedure de sistema sp_addlogin, podemos entender como esse preocesso é feito:
create procedure sp_addlogin @loginame sysname ,@passwd sysname = Null ,@defdb sysname = 'master' -- UNDONE: DEFAULT CONFIGURABLE??? ,@deflanguage sysname = Null ,@sid varbinary(16) = Null ,@encryptopt varchar(20) = Null AS -- SETUP RUNTIME OPTIONS / DECLARE VARIABLES -- set nocount on Declare @ret int -- return value of sp call -- CHECK PERMISSIONS -- IF (not is_srvrolemember('securityadmin') = 1) begin dbcc auditevent (104, 1, 0, @loginame, NULL, NULL, @sid) raiserror(15247,-1,-1) return (1) end ELSE begin dbcc auditevent (104, 1, 1, @loginame, NULL, NULL, @sid) end -- DISALLOW USER TRANSACTION -- set implicit_transactions off IF (@@trancount > 0) begin raiserror(15002,-1,-1,'sp_addlogin') return (1) end -- VALIDATE LOGIN NAME AS: -- (1) Valid SQL Name (SQL LOGIN) -- (2) No backslash (NT users only) -- (3) Not a reserved login name execute @ret = sp_validname @loginame if (@ret <> 0) return (1) if (charindex('\', @loginame) > 0) begin raiserror(15006,-1,-1,@loginame) return (1) end --Note: different case sa is allowed. if (@loginame = 'sa' or lower(@loginame) in ('public')) begin raiserror(15405, -1 ,-1, @loginame) return (1) end -- LOGIN NAME MUST NOT ALREADY EXIST -- if exists(select * from master.dbo.syslogins where loginname = @loginame) begin raiserror(15025,-1,-1,@loginame) return (1) end -- VALIDATE DEFAULT DATABASE -- IF db_id(@defdb) IS NULL begin raiserror(15010,-1,-1,@defdb) return (1) end -- VALIDATE DEFAULT LANGUAGE -- IF (@deflanguage IS NOT Null) begin Execute @ret = sp_validlang @deflanguage IF (@ret <> 0) return (1) end ELSE begin select @deflanguage = name from master.dbo.syslanguages where langid = @@default_langid --server default language if @deflanguage is null select @deflanguage = N'us_english' end -- VALIDATE SID IF GIVEN -- if ((@sid IS NOT Null) and (datalength(@sid) <> 16)) begin raiserror(15419,-1,-1) return (1) end else if @sid is null select @sid = newid() if (suser_sname(@sid) IS NOT Null) begin raiserror(15433,-1,-1) return (1) end -- VALIDATE AND USE ENCRYPTION OPTION -- declare @xstatus smallint select @xstatus = 2 -- access if @encryptopt is null select @passwd = pwdencrypt(@passwd) else if @encryptopt = 'skip_encryption_old' begin select @xstatus = @xstatus | 0x800, -- old-style encryption @passwd = convert(sysname, convert(varbinary(30), convert(varchar(30), @passwd))) end else if @encryptopt <> 'skip_encryption' begin raiserror(15600,-1,-1,'sp_addlogin') return 1 end -- ATTEMPT THE INSERT OF THE NEW LOGIN -- INSERT INTO master.dbo.sysxlogins VALUES (NULL, @sid, @xstatus, getdate(), getdate(), @loginame, convert(varbinary(256), @passwd), db_id(@defdb), @deflanguage) if @@error <> 0 -- this indicates we saw duplicate row return (1) -- UPDATE PROTECTION TIMESTAMP FOR MASTER DB, TO INDICATE SYSLOGINS CHANGE -- exec('use master grant all to null') -- FINALIZATION: RETURN SUCCESS/FAILURE -- raiserror(15298,-1,-1) return (0) -- sp_addlogin GO
Note que para criptografar as senhas de usuários, a stored procedure usa uma função não documentada chamada pwdencrypt(), que pega a senha sem criptografia como parâmetro e retorna o valor criptografado, sendo impossível reverter o processo para descriptografar a senha. O algoritmo dessa função é guardado a sete chaves pela Microsoft.
Outra função não documentada, é a pwdcompare().
Ela pega dois parâmetros para comparação, a senha não-criptografada e um texto criptografado. Se o retorno for TRUE, é porque o hash da senha não-criptografada bateu com o texto criptografado, caso contrário, o valor será FALSE. É assim que o SQL Server verifica se a senha digitada em texto puro do usuário é a mesma daquela que está na tabela sysxlogins criptografada.
É possível fazer um ataque de força bruta nas senhas dos usuários gerando todas as combinações possíveis de caracteres e testá-los com a função pwdcompare(), mas senhas longas fará com que um ataque demore muito tempo.
Criando senhas complexas
Nem o melhor esquema de criptografia do mundo pode proteger você com senhas fracas.
A sp_password é uma stored procedure de sistema usado pelo SQL Server desde a versão 6.5 que serve para "setar" e alterar senhas. No SQL Server 6.5, usuários podiam ter senhas de até 30 caracteres. O SQL Server 7 e o 2000 permitem ter até 128 caracteres e permitem todos os caracteres do teclado. O número de caracteres numa senha é muito importante, pois determina maior complexidade da senha.
Todas as versões do SQL Server interpretam senhas diferenciando case-sensitive e case-insensitive. Se o servidor usa case-insensitive, as senhas são convertidas para maiúsculas antes de serem guardadas na syslogins (SQL Server 6.5) e sysxlogins (SQL Server 7 em diante) e são comparadas com as entradas nessas tabelas quando o usuário logar.
Ex: a senha Password0 será armazenada como PASSWORD0 no servidor com case-insensitive sort order.
Vamos começar com um exemplo de senha que usa letras de A a Z em maiúsculas. Cada caractere da senha poderá ter até 26 opções possíveis e nenhum caractere é dependente de outro. Essas 26 opções tem n cominações possíveis, sendo que n = número de caracteres da senha.
Exemplo prático:
Normalmente nas empresas, usam-se senhas com 6 caracteres. Isso significa que há de 266 a 308.915.776 senhas possíveis usando letras maiúsculas.
Se o padrão de senhas conter letras maiúsculas e minúsculas, as opções mudam para 52 e o número de senhas possíveis vão de 336 a 19.770.609.664.
Considerando um ataque que cheque 1000 senhas por segundo, o ataque demoraria 85,81 horas.
Vamos mais além, se você adicionar, letras maiúsculas e minúsculas, e números, o número de combinações passa para 72n e se você mudar a senha de 6 para 8 caracteres, para quebrar a senha vai levar de 604,66 a 783.641,64 horas, considerando as 1000 senhas por segundo.
Agora se você acrescentar 1 caractere especial ! @ # $ % ^ & * ( ) à sua senha com 8 caracteres e caracteres em maiúsculos e minúsculos, você terá de 468 a 20.047.612.231.936 possibilidades de combinações de senha. O ataque demoraria 5.568.781,17 horas (635,7 anos).
Bom, acho que te convenci sobre a importância de uma senha complexa, principalmente para contas administrativas, que recomendo 20 caracteres pelo menos.