Database
 sql >> Base de données >  >> RDS >> Database

Les internes de WITH ENCRYPTION

Il est assez facile pour un administrateur SQL Server de récupérer le texte des procédures stockées, des vues, des fonctions et des déclencheurs protégés à l'aide de WITH ENCRYPTION . De nombreux articles ont été écrits à ce sujet, et plusieurs outils commerciaux sont disponibles. Le schéma de base de la méthode commune consiste à :

  1. Obtenez le formulaire crypté (A) à l'aide de la connexion administrateur dédiée.
  2. Démarrer une transaction.
  3. Remplacez la définition de l'objet par un texte connu (B) d'au moins la même longueur que l'original.
  4. Obtenir la forme chiffrée du texte connu (C).
  5. Annuler la transaction pour laisser l'objet cible dans son état initial.
  6. Obtenez l'original non chiffré en appliquant un ou exclusif à chaque caractère :A XOR (B XOR C)

Tout cela est assez simple, mais semble un peu magique :cela n'explique pas grand-chose sur comment et pourquoi cela fonctionne . Cet article couvre cet aspect pour ceux d'entre vous qui trouvent ce genre de détails intéressants et propose une méthode alternative de déchiffrement plus illustrative du processus.

Le chiffrement de flux

L'algorithme de chiffrement sous-jacent que SQL Server utilise pour le chiffrement du module est le chiffrement de flux RC4™. Voici un aperçu du processus de cryptage :

  1. Initialiser le chiffrement RC4 avec une clé cryptographique.
  2. Générer un flux d'octets pseudo-aléatoires.
  3. Combinez le texte brut du module avec le flux d'octets à l'aide du ou exclusif.

Nous pouvons voir ce processus se produire en utilisant un débogueur et des symboles publics. Par exemple, la trace de pile ci-dessous montre que SQL Server initialise la clé RC4 tout en se préparant à chiffrer le texte du module :

La suivante montre SQL Server cryptant le texte à l'aide du flux d'octets pseudo-aléatoire RC4 :

Comme la plupart des chiffrements de flux, le processus de déchiffrement est le même que le chiffrement, en utilisant le fait que le ou exclusif est réversible (A XOR B XOR B = A ).

L'utilisation d'un chiffrement de flux est la raison pour laquelle exclusive-or est utilisé dans la méthode décrite au début de l'article. Il n'y a rien d'intrinsèquement dangereux à utiliser le OU exclusif, à condition qu'une méthode de cryptage sécurisée soit utilisée, que la clé d'initialisation soit gardée secrète et que la clé ne soit pas réutilisée.

RC4 n'est pas particulièrement fort, mais ce n'est pas le problème principal ici. Cela dit, il convient de noter que le chiffrement utilisant RC4 est progressivement supprimé de SQL Server et est obsolète (ou désactivé, selon la version et le niveau de compatibilité de la base de données) pour les opérations utilisateur telles que la création d'une clé symétrique.

La clé d'initialisation RC4

SQL Server utilise trois informations pour générer la clé utilisée pour initialiser le chiffrement de flux RC4 :

  1. GUID de la famille de bases de données.

    Ceci peut être obtenu plus facilement en interrogeant sys.database_recovery_status . Il est également visible dans les commandes non documentées comme DBCC DBINFO et DBCC DBTABLE .

  2. L'ID d'objet du module cible.

    Il s'agit simplement de l'ID d'objet familier. Notez que tous les modules qui autorisent le chiffrement ne sont pas limités au schéma. Vous devrez utiliser des vues de métadonnées (sys.triggers ou sys.server_triggers ) pour obtenir l'ID d'objet pour les déclencheurs DDL et de portée serveur, plutôt que sys.objects ou OBJECT_ID , car ceux-ci ne fonctionnent qu'avec des objets de portée de schéma.

  3. L'ID de sous-objet du module cible.

    Il s'agit du numéro de procédure pour les procédures stockées numérotées. Il vaut 1 pour une procédure stockée non numérotée et zéro dans tous les autres cas.

En utilisant à nouveau le débogueur, nous pouvons voir que le GUID de la famille est récupéré lors de l'initialisation de la clé :

Le GUID de la famille de la base de données est de type uniqueidentifier , l'ID d'objet est entier , et l'ID de sous-objet est smallint .

Chaque partie de la clé doit être converti dans un format binaire spécifique. Pour le GUID de la famille de bases de données, conversion de l'identifiant unique tapez en binaire(16) produit la représentation binaire correcte. Les deux identifiants doivent être convertis en binaire en représentation petit-boutiste (octet le moins significatif en premier).

Remarque : Faites très attention à ne pas fournir accidentellement le GUID sous forme de chaîne ! Il doit être saisi uniqueidentifier .

L'extrait de code ci-dessous montre les opérations de conversion correctes pour certains exemples de valeurs :

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)));

La dernière étape pour générer la clé d'initialisation RC4 consiste à concaténer les trois valeurs binaires ci-dessus en un seul binaire (22) et à calculer le hachage SHA-1 du résultat :

DECLARE 
    @RC4key binary(20) = HASHBYTES('SHA1', @family_guid + @objid + @subobjid);

Pour les exemples de données ci-dessus, la clé d'initialisation finale est :

0x6C914908E041A08DD8766A0CFEDC113585D69AF8

La contribution de l'ID d'objet et de l'ID de sous-objet du module cible au hachage SHA-1 est difficile à voir dans une seule capture d'écran du débogueur, mais le lecteur intéressé peut se référer au désassemblage d'une partie de initspkey ci-dessous :

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)

Le SHAInit et SHAUpdate les appels ajoutent des composants au hachage SHA, qui est finalement calculé par un appel à SHAFinal .

Le SHAInit call contribue 10h octets (16 décimal) stockés à [rsp+40h], qui est le GUID familial . La première SHAUpdate call ajoute 4 octets (comme indiqué dans le registre r8d), stockés à [rsp+24h], qui est l'objet IDENTIFIANT. La deuxième SHAUpdate call ajoute 2 octets, stockés à [rsp+20h], qui est le subobjid .

Les instructions finales transmettent le hachage SHA-1 calculé à la routine d'initialisation de la clé RC4 rc4_key . La longueur du hachage est stockée dans le registre edx :14h (20 décimal) octets, qui est la longueur de hachage définie pour SHA et SHA-1 (160 bits).

La mise en œuvre de RC4

L'algorithme de base RC4 est bien connu et relativement simple. Il serait préférable de l'implémenter dans un langage .Net pour des raisons d'efficacité et de performances, mais il existe une implémentation T-SQL ci-dessous.

Ces deux fonctions T-SQL implémentent l'algorithme de planification de clé RC4 et le générateur de nombres pseudo-aléatoires, et ont été écrites à l'origine par le MVP SQL Server Peter Larsson. J'ai apporté quelques modifications mineures pour améliorer un peu les performances et permettre aux binaires de longueur LOB d'être encodés et décodés. Cette partie du processus pourrait être remplacée par n'importe quelle implémentation RC4 standard.

/*
** 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

Le texte chiffré du module

Le moyen le plus simple pour un administrateur SQL Server d'obtenir cela est de lire le varbinary(max) valeur stockée dans imageval colonne de sys.sysobjvalues , accessible uniquement via la connexion administrateur dédiée (DAC).

C'est la même idée que la méthode de routine décrite dans l'introduction, bien que nous ajoutions un filtre sur valclass =1. Cette table interne est également un endroit pratique pour obtenir le subobjid . Sinon, nous aurions besoin de vérifier sys.numbered_procedures lorsque l'objet cible est une procédure, utilisez 1 pour une procédure non numérotée, ou zéro pour toute autre chose, comme décrit précédemment.

Il est possible d'éviter d'utiliser le DAC en lisant le imageval à partir de sys.sysobjvalues directement, en utilisant plusieurs DBCC PAGE appels. Cela implique un peu plus de travail pour localiser les pages à partir des métadonnées, suivez le imageval chaîne LOB et lire les données binaires cibles de chaque page. Cette dernière étape est beaucoup plus facile à faire dans un langage de programmation autre que T-SQL. Notez que DBCC PAGE fonctionnera, même si l'objet de base n'est normalement pas lisible à partir d'une connexion non DAC. Si la page n'est pas en mémoire, elle sera lue à partir du stockage persistant comme d'habitude.

L'effort supplémentaire pour éviter l'exigence DAC est payant en permettant à plusieurs utilisateurs d'utiliser simultanément le processus de décryptage. J'utiliserai l'approche DAC dans cet article pour des raisons de simplicité et d'espace.

Exemple concret

Le code suivant crée une fonction scalaire chiffrée de test :

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;

La mise en œuvre complète du décryptage est ci-dessous. Le seul paramètre qui doit être modifié pour fonctionner avec d'autres objets est la valeur initiale de @objectid défini dans le premier DECLARE déclaration.

-- *** 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
        )
    );

Notez la conversion finale en nvarchar parce que le texte du module est tapé comme nvarchar(max) .

La sortie est :

Conclusion

Les raisons pour lesquelles la méthode décrite dans l'introduction fonctionne sont :

  • SQL Server utilise le chiffrement de flux RC4 pour exclure ou de manière réversible le texte source.
  • La clé RC4 dépend uniquement du guid de la famille de la base de données, de l'identifiant de l'objet et du subobjid.
  • Le remplacement temporaire du texte du module signifie que la même clé RC4 (hachée SHA-1) est générée.
  • Avec la même clé, le même flux RC4 est généré, permettant le déchiffrement ou exclusif.

Les utilisateurs qui n'ont pas accès aux tables système, aux fichiers de base de données ou à tout autre accès de niveau administrateur ne peuvent pas récupérer le texte chiffré du module. Étant donné que SQL Server lui-même doit pouvoir déchiffrer le module, il n'y a aucun moyen d'empêcher les utilisateurs disposant de privilèges appropriés de faire de même.