É 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 DBINFOeDBCC 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 https://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.