É muito fácil para um administrador do SQL Server recuperar o texto de stored procedures, views, funções e triggers protegidos usando
WITH ENCRYPTION
. Muitos artigos foram escritos sobre isso e várias ferramentas comerciais estão disponíveis. O esboço básico do método comum é:- Obtenha o formulário criptografado (A) usando a Conexão de Administrador Dedicado.
- Inicie uma transação.
- Substitua a definição do objeto por um texto conhecido (B) de pelo menos o mesmo comprimento que o original.
- Obtenha o formulário criptografado para o texto conhecido (C).
- Reverta a transação para deixar o objeto de destino em seu estado inicial.
- Obtenha o original não criptografado aplicando um exclusivo-ou a cada caractere:
A XOR (B XOR C)
Isso tudo é bastante simples, mas parece um pouco mágico:não explica muito sobre como e por que funciona . Este artigo aborda esse aspecto para aqueles que acham esses tipos de detalhes interessantes e fornece um método alternativo para descriptografia que é mais ilustrativo do processo.
A cifra de fluxo
O algoritmo de criptografia subjacente que o SQL Server usa para criptografia de módulo é a cifra de fluxo RC4™. Um esboço do processo de criptografia é:
- Inicie a cifra RC4 com uma chave criptográfica.
- Gere um fluxo pseudo-aleatório de bytes.
- Combine o texto simples do módulo com o fluxo de bytes usando ou exclusivo.
Podemos ver esse processo ocorrendo usando um depurador e símbolos públicos. Por exemplo, o rastreamento de pilha abaixo mostra o SQL Server inicializando a chave RC4 enquanto se prepara para criptografar o texto do módulo:
Este próximo mostra o SQL Server criptografando o texto usando o fluxo de bytes pseudoaleatórios RC4:
Como a maioria das cifras de fluxo, o processo de descriptografia é o mesmo que a criptografia, fazendo uso do fato de que exclusivo-ou é reversível (
A XOR B XOR B = A
). O uso de uma cifra de fluxo é o motivo exclusivo-ou é usado no método descrito no início do artigo. Não há nada inerentemente inseguro em usar o exclusivo-ou, desde que um método de criptografia seguro seja usado, a chave de inicialização seja mantida em segredo e a chave não seja reutilizada.
RC4 não é particularmente forte, mas esse não é o principal problema aqui. Dito isso, vale a pena notar que a criptografia usando RC4 está sendo gradualmente removida do SQL Server e está obsoleta (ou desabilitada, dependendo da versão e do nível de compatibilidade do banco de dados) para operações do usuário, como a criação de uma chave simétrica.
A chave de inicialização RC4
O SQL Server usa três informações para gerar a chave usada para inicializar a cifra de fluxo RC4:
- O GUID da família de banco de dados.
Isso pode ser obtido mais facilmente consultando sys.database_recovery_status . Também é visível em comandos não documentados comoDBCC DBINFO
eDBCC DBTABLE
.
- O ID do objeto do módulo de destino.
Este é apenas o ID do objeto familiar. Observe que nem todos os módulos que permitem criptografia têm escopo de esquema. Você precisará usar visualizações de metadados (sys.triggers ou sys.server_triggers ) para obter o ID do objeto para DDL e acionadores com escopo de servidor, em vez de sys.objects ouOBJECT_ID
, já que eles só funcionam com objetos com escopo de esquema.
- O ID do subobjeto do módulo de destino.
Este é o número do procedimento para procedimentos armazenados numerados. É 1 para um procedimento armazenado não numerado e zero em todos os outros casos.
Usando o depurador novamente, podemos ver o GUID da família sendo recuperado durante a inicialização da chave:
O GUID da família de banco de dados é digitado identificador único , o ID do objeto é inteiro , e o ID do subobjeto é pequeno .
Cada parte da chave deve ser convertido para um formato binário específico. Para o GUID da família de banco de dados, convertendo o identificador único digite para binário(16) produz a representação binária correta. Os dois IDs devem ser convertidos em binário em representação little-endian (byte menos significativo primeiro).
Observação: Tenha muito cuidado para não fornecer acidentalmente o GUID como uma string! Deve ser digitado identificador único .
O snippet de código abaixo mostra as operações de conversão corretas para alguns valores de exemplo:
DECLARE @family_guid binary(16) = CONVERT(binary(16), {guid 'B1FC892E-5824-4FD3-AC48-FBCD91D57763'}), @objid binary(4) = CONVERT(binary(4), REVERSE(CONVERT(binary(4), 800266156))), @subobjid binary(2) = CONVERT(binary(2), REVERSE(CONVERT(binary(2), 0)));
A etapa final para gerar a chave de inicialização RC4 é concatenar os três valores binários acima em um único binário(22) e calcular o hash SHA-1 do resultado:
DECLARE @RC4key binary(20) = HASHBYTES('SHA1', @family_guid + @objid + @subobjid);
Para os dados de amostra fornecidos acima, a chave de inicialização final é:
0x6C914908E041A08DD8766A0CFEDC113585D69AF8
A contribuição do ID do objeto e do ID do subobjeto do módulo de destino para o hash SHA-1 é difícil de ver em uma única captura de tela do depurador, mas o leitor interessado pode consultar a desmontagem de uma parte do initspkey abaixo de:
call sqllang!A_SHAInit lea rdx,[rsp+40h] lea rcx,[rsp+50h] mov r8d,10h call sqllang!A_SHAUpdate lea rdx,[rsp+24h] lea rcx,[rsp+50h] mov r8d,4 call sqllang!A_SHAUpdate lea rdx,[rsp+20h] lea rcx,[rsp+50h] mov r8d,2 call sqllang!A_SHAUpdate lea rdx,[rsp+0D0h] lea rcx,[rsp+50h] call sqllang!A_SHAFinal lea r8,[rsp+0D0h] mov edx,14h mov rcx,rbx call sqllang!rc4_key (00007fff`89672090)
O SHAInit e SHAUpdate chamadas adicionam componentes ao hash SHA, que é eventualmente calculado por uma chamada para SHAFinal .
O SHAInit chamada contribui com 10h bytes (16 decimal) armazenados em [rsp+40h], que é o GUID da família . A primeira SHAUpdate chamada adiciona 4 bytes (como indicado no registrador r8d), armazenado em [rsp+24h], que é o objeto EU IRIA. A segunda SHAUpdate chamada adiciona 2 bytes, armazenados em [rsp+20h], que é o subobjid .
As instruções finais passam o hash SHA-1 calculado para a rotina de inicialização da chave RC4 rc4_key . O comprimento do hash é armazenado no registrador edx:14h (20 decimais) bytes, que é o comprimento do hash definido para SHA e SHA-1 (160 bits).
A implementação do RC4
O algoritmo principal do RC4 é bem conhecido e relativamente simples. Seria melhor implementado em uma linguagem .Net por motivos de eficiência e desempenho, mas há uma implementação T-SQL abaixo.
Essas duas funções T-SQL implementam o algoritmo de agendamento de chaves RC4 e o gerador de números pseudoaleatórios e foram originalmente escritas pelo MVP do SQL Server Peter Larsson. Fiz algumas pequenas modificações para melhorar um pouco o desempenho e permitir que binários de comprimento LOB sejam codificados e decodificados. Esta parte do processo pode ser substituída por qualquer implementação RC4 padrão.
/* ** RC4 functions ** Based on http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=76258 ** by Peter Larsson (SwePeso) */ IF OBJECT_ID(N'dbo.fnEncDecRc4', N'FN') IS NOT NULL DROP FUNCTION dbo.fnEncDecRc4; GO IF OBJECT_ID(N'dbo.fnInitRc4', N'TF') IS NOT NULL DROP FUNCTION dbo.fnInitRc4; GO CREATE FUNCTION dbo.fnInitRc4 (@Pwd varbinary(256)) RETURNS @Box table ( i tinyint PRIMARY KEY, v tinyint NOT NULL ) WITH SCHEMABINDING AS BEGIN DECLARE @Key table ( i tinyint PRIMARY KEY, v tinyint NOT NULL ); DECLARE @Index smallint = 0, @PwdLen tinyint = DATALENGTH(@Pwd); WHILE @Index <= 255 BEGIN INSERT @Key (i, v) VALUES (@Index, CONVERT(tinyint, SUBSTRING(@Pwd, @Index % @PwdLen + 1, 1))); INSERT @Box (i, v) VALUES (@Index, @Index); SET @Index += 1; END; DECLARE @t tinyint = NULL, @b smallint = 0; SET @Index = 0; WHILE @Index <= 255 BEGIN SELECT @b = (@b + b.v + k.v) % 256 FROM @Box AS b JOIN @Key AS k ON k.i = b.i WHERE b.i = @Index; SELECT @t = b.v FROM @Box AS b WHERE b.i = @Index; UPDATE b1 SET b1.v = (SELECT b2.v FROM @Box AS b2 WHERE b2.i = @b) FROM @Box AS b1 WHERE b1.i = @Index; UPDATE @Box SET v = @t WHERE i = @b; SET @Index += 1; END; RETURN; END; GO CREATE FUNCTION dbo.fnEncDecRc4 ( @Pwd varbinary(256), @Text varbinary(MAX) ) RETURNS varbinary(MAX) WITH SCHEMABINDING, RETURNS NULL ON NULL INPUT AS BEGIN DECLARE @Box AS table ( i tinyint PRIMARY KEY, v tinyint NOT NULL ); INSERT @Box (i, v) SELECT FIR.i, FIR.v FROM dbo.fnInitRc4(@Pwd) AS FIR; DECLARE @Index integer = 1, @i smallint = 0, @j smallint = 0, @t tinyint = NULL, @k smallint = NULL, @CipherBy tinyint = NULL, @Cipher varbinary(MAX) = 0x; WHILE @Index <= DATALENGTH(@Text) BEGIN SET @i = (@i + 1) % 256; SELECT @j = (@j + b.v) % 256, @t = b.v FROM @Box AS b WHERE b.i = @i; UPDATE b SET b.v = (SELECT w.v FROM @Box AS w WHERE w.i = @j) FROM @Box AS b WHERE b.i = @i; UPDATE @Box SET v = @t WHERE i = @j; SELECT @k = b.v FROM @Box AS b WHERE b.i = @i; SELECT @k = (@k + b.v) % 256 FROM @Box AS b WHERE b.i = @j; SELECT @k = b.v FROM @Box AS b WHERE b.i = @k; SELECT @CipherBy = CONVERT(tinyint, SUBSTRING(@Text, @Index, 1)) ^ @k, @Cipher = @Cipher + CONVERT(binary(1), @CipherBy); SET @Index += 1; END; RETURN @Cipher; END; GO
O texto do módulo criptografado
A maneira mais fácil para um administrador do SQL Server obter isso é ler o varbinary(max) valor armazenado no imageval coluna de sys.sysobjvalues , que só é acessível por meio da Conexão de Administrador Dedicado (DAC).
Esta é a mesma ideia do método de rotina descrito na introdução, embora adicionemos um filtro em valclass =1. Esta tabela interna também é um local conveniente para obter o subobjid . Caso contrário, precisaríamos verificar sys.numbered_procedures quando o objeto de destino for um procedimento, use 1 para um procedimento não numerado ou zero para qualquer outra coisa, conforme descrito anteriormente.
É possível evitar usar o DAC lendo o imageval de sys.sysobjvalues diretamente, usando várias
DBCC PAGE
chamadas. Isso envolve um pouco mais de trabalho para localizar as páginas dos metadados, siga o imageval cadeia LOB e leia os dados binários de destino de cada página. A última etapa é muito mais fácil de fazer em uma linguagem de programação diferente do T-SQL. Observe que DBCC PAGE
funcionará, mesmo que o objeto base não seja normalmente legível a partir de uma conexão não DAC. Se a página não estiver na memória, ela será lida do armazenamento persistente normalmente. O esforço extra para evitar o requisito de DAC compensa ao permitir que vários usuários usem o processo de descriptografia simultaneamente. Usarei a abordagem DAC neste artigo por motivos de simplicidade e espaço.
Exemplo trabalhado
O código a seguir cria uma função escalar criptografada de teste:
CREATE FUNCTION dbo.FS() RETURNS varchar(255) WITH ENCRYPTION, SCHEMABINDING AS BEGIN RETURN ( SELECT 'My code is so awesome is needs to be encrypted!' ); END;
A implementação completa de descriptografia está abaixo. O único parâmetro que precisa ser alterado para funcionar com outros objetos é o valor inicial de
@objectid
definido no primeiro DECLARE
demonstração. -- *** DAC connection required! *** -- Make sure the target database is the context USE Sandpit; DECLARE -- Note: OBJECT_ID only works for schema-scoped objects @objectid integer = OBJECT_ID(N'dbo.FS', N'FN'), @family_guid binary(16), @objid binary(4), @subobjid binary(2), @imageval varbinary(MAX), @RC4key binary(20); -- Find the database family GUID SELECT @family_guid = CONVERT(binary(16), DRS.family_guid) FROM sys.database_recovery_status AS DRS WHERE DRS.database_id = DB_ID(); -- Convert object ID to little-endian binary(4) SET @objid = CONVERT(binary(4), REVERSE(CONVERT(binary(4), @objectid))); SELECT -- Read the encrypted value @imageval = SOV.imageval, -- Get the subobjid and convert to little-endian binary @subobjid = CONVERT(binary(2), REVERSE(CONVERT(binary(2), SOV.subobjid))) FROM sys.sysobjvalues AS SOV WHERE SOV.[objid] = @objectid AND SOV.valclass = 1; -- Compute the RC4 initialization key SET @RC4key = HASHBYTES('SHA1', @family_guid + @objid + @subobjid); -- Apply the standard RC4 algorithm and -- convert the result back to nvarchar PRINT CONVERT ( nvarchar(MAX), dbo.fnEncDecRc4 ( @RC4key, @imageval ) );
Observe a conversão final para nvarchar porque o texto do módulo é digitado como nvarchar(max) .
A saída é:
Conclusão
As razões pelas quais o método descrito na introdução funciona são:
- O SQL Server usa a cifra de fluxo RC4 para reversivelmente exclusivo ou o texto de origem.
- A chave RC4 depende apenas do guid da família de banco de dados, id do objeto e subobjid.
- Substituir temporariamente o texto do módulo significa que a mesma chave RC4 (com hash SHA-1) é gerada.
- Com a mesma chave, o mesmo fluxo RC4 é gerado, permitindo a descriptografia ou exclusividade.
Os usuários que não têm acesso a tabelas do sistema, arquivos de banco de dados ou outro acesso em nível de administrador não podem recuperar o texto do módulo criptografado. Como o próprio SQL Server precisa ser capaz de descriptografar o módulo, não há como impedir que usuários com privilégios adequados façam o mesmo.