Les serveurs SQL de Microsoft connus sous le nom MSSQL ont la faculté depuis la version 6.5 (puis 7, 2000, 2005, 2008 et enfin 2012) de protéger les objets via un algorithme de chiffrement.
Les serveurs MSSQL autorisent le chiffrement sur :
- Les vues (view – v)
- Les fonctions (function – fct)
- Les procédures stockées (stored procedure – sp)
- Les déclencheurs (trigger)
Cette protection se caractérise par un petit cadenas apposé sur ces dits objets au travers du client “Microsoft SQL Server Management Studio” (SSMS). Leur édition devient impossible, tout comme la visualisation du code source de ces objets.
Ces objets chiffrés ne peuvent être édités, modifiés, ou visualisés :
Ces mécanismes sont largement employés par de nombreux éditeurs et développeurs de produits logiciels basés sur MSSQL, afin de protéger la propriété intellectuelle et le droit d’auteur de leurs scripts. Toutefois, l’algorithme en place dans chacune des versions de MSSQL Server s’avère faible et peut être facilement inversé.
Comment définir un objet chiffré au sein d’une base MSSQL ?
La méthode pour protéger ces objets est d’une grande simplicité. Il suffit de déclarer chacun d’eux en utilisant les mots clés “WITH ENCRYPTION”. Exemple pour une procédure simple :
[sql]</p>
<p>CREATE PROCEDURE helloWorld<br />
WITH ENCRYPTION<br />
AS<br />
PRINT ‘Encrypted procedure !’</p>
<p>[/sql]
Dès que ces mots clés sont renseignés, la procédure, fonction, trigger ou vue est protégée de l’édition et de la consultation. Il convient donc de toujours conserver une version en clair de l’objet, pour pouvoir le mettre à jour aisément.
État de l’Art du déchiffrement MSSQL toutes versions
Depuis de nombreuse années, de multiples outils permettent de déchiffrer les objets des base MSSQL. La plupart sont payants, et dispose d’une version d’essai qui limite le déchiffrement à N caractères. De plus, certains ne prennent pas en compte toutes les versions de MSSQL, encore moins la dernière MSSQL 2012.
Liste non-exhaustive de solutions existantes :
- DecryptSQL (MSSQL2000 à 2008, shareware)
- SQL Script Recovery Pro 1.50 (MSSQL 7 à 2000)
- SQL Object Decrypt (MSSQL 7 à 2005, EXPRESS édition comprise)
- SQLShield, qui fourni diverses solutions pour différentes versions jusqu’à 2008.
- Orbital SQL Decryptor (MSSQL 6.5 à 2000)
- dbForge SQL Decryptor
- Optillect SQL Decryptor (MSSQL 2000 à 2008, freeware)
Déchiffrement d’objets MSSQL 2000, 2005 et 2008 via Optillect SQL Decryptor
Au sein de cette jungle d’outil, il y en a un qui sort du lot pour sa simplicité d’exploitation, d’utilisation et ses fonctionnalités. Un autre avantage est qu’il est totalement gratuit. Cet outil est “Optillect SqlDecryptor_v2.0.0 – freeware” (de 2011). Il assure le déchiffrement d’objet de toutes les versions des bases MSSQL sauf de la dernière en date : la 2012 (sortie en avril de la même année). L’éditeur de ce programme ne semble plus actif et aucune mise à jour n’a l’air prévu.
Après son installation, il suffit de lancer l’outil, renseigner le serveur MSSQL auquel se connecter ainsi que les crédentiels associés et la liste des objets devient visible (un MSSQL 2008 est exploité dans cet exemple) :
Il suffit par la suite de choisir l’entité à déchiffrer. Un double-clic permet de visualiser directement le code en clair (avec les commentaires) de l’objet. Un clic droit permet d’accéder aux diverses fonctionnalités sur l’entité, comme son remplacement direct dans la base de données.
Déchiffrement d’objets MSSQL 2000, 2005, 2008 et 2012 via une procédure stockée
Lorsque les outils disponibles ne permettent pas de déchiffrer des objets d’une version récente (MSSQL Server 2012 notamment), il est possible de définir une procédure manuellement dans la base à ces fins. Cette procédure implémente l’algorithme de déchiffrement complet.
Microsoft a fait évoluer la structure et le schéma de ses tables systèmes entre les versions 2000 (et antérieur) et les versions supérieures à 2000. Toutefois, entre les versions 2005, 2008 et 2012, l’algorithme et la structure des tables n’a pas évolué. Autrement dit, il est possible de déchiffrer n’importe quel objet via une procédure stockée SQL spécifique, quelque soit la version de MSSQL.
Cette procédure pour MSSQL 2005, 2008 et 2012 est la suivante :
[sql]</p>
<p>/*==================================================================================</p>
<p>NAME: Decrypt SQL 2005, 2008 and 2012 stored procedures, functions, views, and triggers</p>
<p>DESCRIPTION: HEADS UP: In order to run this script you must log in<br />
to the server in DAC mode: To do so, type<br />
ADMIN:&amp;lt;SQLInstanceName&amp;gt; as your server name and use the &amp;quot;sa&amp;quot;<br />
or any other server admin user with the appropriate password.</p>
<p> CAUTION! DAC (dedicated admin access) will kick out all other<br />
server users.</p>
<p> The script below accepts an object (schema name + object name)<br />
that were created using the WITH ENCRYPTION option and returns<br />
the decrypted script that creates the object. This script<br />
is useful to decrypt stored procedures, views, functions,<br />
and triggers that were created WITH ENCRYPTION.</p>
<p> The algorithm used below is the following:<br />
1. Check that the object exists and that it is encrypted.<br />
2. In order to decrypt the object, the script ALTER (!!!) it<br />
and later restores the object to its original one. This is<br />
required as part of the decryption process: The object<br />
is altered to contain dummy text (the ALTER uses WITH ENCRYPTION)<br />
and then compared to the CREATE statement of the same dummy<br />
content.</p>
<p> Note: The object is altered in a transaction, which is rolled<br />
back immediately after the object is changed to restore<br />
all previous settings.</p>
<p> 3. A XOR operation between the original binary stream of the<br />
enrypted object with the binary representation of the dummy<br />
object and the binary version of the object in clear-text<br />
is used to decrypt the original object.</p>
<p>USER PARAMETERS: @ObjectOwnerOrSchema<br />
@ObjectName</p>
<p>RESULTSET: NA</p>
<p>RESULTSET SORT: NA</p>
<p>USING TABLES/VIEWS: sys.sysobjvalues<br />
syscomments</p>
<p>REVISIONS</p>
<p>DATE DEVELOPER DESCRIPTION OF REVISION VERSION<br />
========= =============== ================================= ===========<br />
01/01/2007 Omri Bahat Initial release 1.00</p>
<p>01/18/2013 Yann CAM Test script on MSSQL 2008 and 2012<br />
==================================================================================*/</p>
<p>DECLARE @ObjectOwnerOrSchema NVARCHAR(128)<br />
DECLARE @ObjectName NVARCHAR(128)</p>
<p>SET @ObjectOwnerOrSchema = ‘dbo’<br />
SET @ObjectName = ‘helloWorld’</p>
<p>DECLARE @i INT<br />
DECLARE @ObjectDataLength INT<br />
DECLARE @ContentOfEncryptedObject NVARCHAR(MAX)<br />
DECLARE @ContentOfDecryptedObject NVARCHAR(MAX)<br />
DECLARE @ContentOfFakeObject NVARCHAR(MAX)<br />
DECLARE @ContentOfFakeEncryptedObject NVARCHAR(MAX)<br />
DECLARE @ObjectType NVARCHAR(128)<br />
DECLARE @ObjectID INT</p>
<p>SET NOCOUNT ON</p>
<p>SET @ObjectID = OBJECT_ID(‘[‘ + @ObjectOwnerOrSchema + ‘].[‘ + @ObjectName + ‘]’)</p>
<p>– Check that the provided object exists in the database.<br />
IF @ObjectID IS NULL<br />
BEGIN<br />
RAISERROR(‘The object name or schema provided does not exist in the database’, 16, 1)<br />
RETURN<br />
END</p>
<p>– Check that the provided object is encrypted.<br />
IF NOT EXISTS(SELECT TOP 1 * FROM syscomments WHERE id = @ObjectID AND encrypted = 1)<br />
BEGIN<br />
RAISERROR(‘The object provided exists however it is not encrypted. Aborting.’, 16, 1)<br />
RETURN<br />
END</p>
<p>– Determine the type of the object<br />
IF OBJECT_ID(‘[‘ + @ObjectOwnerOrSchema + ‘].[‘ + @ObjectName + ‘]’, ‘PROCEDURE’) IS NOT NULL<br />
SET @ObjectType = ‘PROCEDURE’<br />
ELSE<br />
IF OBJECT_ID(‘[‘ + @ObjectOwnerOrSchema + ‘].[‘ + @ObjectName + ‘]’, ‘TRIGGER’) IS NOT NULL<br />
SET @ObjectType = ‘TRIGGER’<br />
ELSE<br />
IF OBJECT_ID(‘[‘ + @ObjectOwnerOrSchema + ‘].[‘ + @ObjectName + ‘]’, ‘VIEW’) IS NOT NULL<br />
SET @ObjectType = ‘VIEW’<br />
ELSE<br />
SET @ObjectType = ‘FUNCTION’</p>
<p>– Get the binary representation of the object- syscomments no longer holds<br />
— the content of encrypted object.<br />
SELECT TOP 1 @ContentOfEncryptedObject = imageval<br />
FROM sys.sysobjvalues<br />
WHERE objid = OBJECT_ID(‘[‘ + @ObjectOwnerOrSchema + ‘].[‘ + @ObjectName + ‘]’)<br />
AND valclass = 1 and subobjid = 1</p>
<p>SET @ObjectDataLength = DATALENGTH(@ContentOfEncryptedObject)/2</p>
<p>– We need to alter the existing object and make it into a dummy object<br />
— in order to decrypt its content. This is done in a transaction<br />
— (which is later rolled back) to ensure that all changes have a minimal<br />
— impact on the database.<br />
SET @ContentOfFakeObject = N’ALTER ‘ + @ObjectType + N’ [‘ + @ObjectOwnerOrSchema + N’].[‘ + @ObjectName + N’] WITH ENCRYPTION AS’</p>
<p>WHILE DATALENGTH(@ContentOfFakeObject)/2 &amp;lt; @ObjectDataLength<br />
BEGIN<br />
IF DATALENGTH(@ContentOfFakeObject)/2 + 4000 &amp;lt; @ObjectDataLength<br />
SET @ContentOfFakeObject = @ContentOfFakeObject + REPLICATE(N’-‘, 4000)<br />
ELSE<br />
SET @ContentOfFakeObject = @ContentOfFakeObject + REPLICATE(N’-‘, @ObjectDataLength – (DATALENGTH(@ContentOfFakeObject)/2))<br />
END</p>
<p>– Since we need to alter the object in order to decrypt it, this is done<br />
— in a transaction<br />
SET XACT_ABORT OFF<br />
BEGIN TRAN</p>
<p>EXEC(@ContentOfFakeObject)</p>
<p>IF @@ERROR &amp;lt;&amp;gt; 0<br />
ROLLBACK TRAN</p>
<p>– Get the encrypted content of the new &amp;quot;fake&amp;quot; object.<br />
SELECT TOP 1 @ContentOfFakeEncryptedObject = imageval<br />
FROM sys.sysobjvalues<br />
WHERE objid = OBJECT_ID(‘[‘ + @ObjectOwnerOrSchema + ‘].[‘ + @ObjectName + ‘]’)<br />
AND valclass = 1 and subobjid = 1</p>
<p>IF @@TRANCOUNT &amp;gt; 0<br />
ROLLBACK TRAN</p>
<p>– Generate a CREATE script for the dummy object text.<br />
SET @ContentOfFakeObject = N’CREATE ‘ + @ObjectType + N’ [‘ + @ObjectOwnerOrSchema + N’].[‘ + @ObjectName + N’] WITH ENCRYPTION AS’</p>
<p>WHILE DATALENGTH(@ContentOfFakeObject)/2 &amp;lt; @ObjectDataLength<br />
BEGIN<br />
IF DATALENGTH(@ContentOfFakeObject)/2 + 4000 &amp;lt; @ObjectDataLength<br />
SET @ContentOfFakeObject = @ContentOfFakeObject + REPLICATE(N’-‘, 4000)<br />
ELSE<br />
SET @ContentOfFakeObject = @ContentOfFakeObject + REPLICATE(N’-‘, @ObjectDataLength – (DATALENGTH(@ContentOfFakeObject)/2))<br />
END</p>
<p>SET @i = 1</p>
<p>–Fill the variable that holds the decrypted data with a filler character<br />
SET @ContentOfDecryptedObject = N”</p>
<p>WHILE DATALENGTH(@ContentOfDecryptedObject)/2 &amp;lt; @ObjectDataLength<br />
BEGIN<br />
IF DATALENGTH(@ContentOfDecryptedObject)/2 + 4000 &amp;lt; @ObjectDataLength<br />
SET @ContentOfDecryptedObject = @ContentOfDecryptedObject + REPLICATE(N’A’, 4000)<br />
ELSE<br />
SET @ContentOfDecryptedObject = @ContentOfDecryptedObject + REPLICATE(N’A’, @ObjectDataLength – (DATALENGTH(@ContentOfDecryptedObject)/2))<br />
END</p>
<p>WHILE @i &amp;lt;= @ObjectDataLength<br />
BEGIN<br />
–xor real &amp;amp; fake &amp;amp; fake encrypted<br />
SET @ContentOfDecryptedObject = STUFF(@ContentOfDecryptedObject, @i, 1,<br />
NCHAR(<br />
UNICODE(SUBSTRING(@ContentOfEncryptedObject, @i, 1)) ^<br />
(<br />
UNICODE(SUBSTRING(@ContentOfFakeObject, @i, 1)) ^<br />
UNICODE(SUBSTRING(@ContentOfFakeEncryptedObject, @i, 1))<br />
)))</p>
<p>SET @i = @i + 1<br />
END</p>
<p>– PRINT the content of the decrypted object in chunks of 2K characters</p>
<p>SET @i = 0</p>
<p>WHILE DATALENGTH(@ContentOfDecryptedObject)/2 &amp;gt; (@i + 1)*2000<br />
BEGIN<br />
PRINT(SUBSTRING(@ContentOfDecryptedObject, 1 + 2000*@i, 2000*(@i + 1)))<br />
SET @i = @i + 1<br />
END</p>
<p>– Now print the last chunk, or the only chunk<br />
— (if @ContentOfDecryptedObject does not exceed 2K characters)<br />
PRINT(SUBSTRING(@ContentOfDecryptedObject, 1 + 2000*@i, 2000*(@i + 1)))</p>
<p>[/sql]
Cette procédure, développée par Omri Bahat et publiée le 01/01/2007 est toujours fonctionnelle pour les dernières versions de Microsoft SQL Server (2005, 2008 et 2012).
Il est nécessaire de se connecter sous un compte d’administration au client SSMS pour exécuter cette procédure, via une connexion DAC (Dedicated Administrator Connection). Une telle connexion est possible bien que d’autres connexions soient déjà établies. Seule une connexion DAC est possible à la fois. Par défaut, les connexions DAC sont autorisées qu’en local ; pour les activer à distance, exécuter les commandes suivantes dans un terminal “en tant qu’administrateur” sur la machine accueillant le serveur SQL :
[bash]<br />
sqlcmd<br />
sp_configure ‘remote admin connections’, 1;<br />
GO<br />
RECONFIGURE;<br />
GO<br />
[/bash]
Pour ouvrir une connexion via le client SSMS en tant qu’administrateur DAC, faire “File > New > Database Engine Query”. Renseigner les crédentiels nécessaires à la connexion tout en préfixant le “Server name” de “admin:” (la casse n’a pas d’importance) :
Un nouvel onglet d’exécution de requête apparaît. Celui-ci dispose des privilèges d’administration DAC. Copier/coller la procédure de déchiffrement d’objet MSSQL précédente, et modifier en amont de cette procédure le nom de l’objet à déchiffrer (fonction, procédure, trigger ou vue). Enfin, cliquer sur “Execute” et le code en clair de l’objet apparaît en tant que résultat :
L’objet déchiffré contient toujours les mots clés “WITH ENCRYPTION”. Il suffit de supprimer ces mots clé et de ré-exécuter le code d’altération (ALTER) de l’objet pour le ré-enregistrer en clair dans la base.
L’archive suivante [Stored_procedures_to_decrypt_MSSQL_object_2000-2005-2008-2012], issue initialement de l’article très complet de Omri Bahat, comprend tous les fichiers .SQL des procédures de déchiffrement quelque soit la version MSSQL en place.
Analyse de l’algorithme de déchiffrement
Le stockage des différents objets MSSQL (dans les versions supérieures ou égales à 2005) se fait dans la colonne “imageval” de la table “sys.sysobjvalues”. Cette table est protégée lors d’une connexion simple via SSMS à la base, même sous un compte d’administration. C’est pourquoi il est nécessaire de faire une connexion DAC.
L’algorithme de chiffrement effectue un simple XOR sur le code SQL de l’objet à protéger avant de le stocker dans cette table.
La technique de déchiffrement qui est utilisée dans la procédure précédente est la suivante :
- Récupération des données chiffrées de définition de l’objet (à partir de son identifiant unique), dans la colonne “imageval” de la table “sys.sysobjvalues” et stockage de cette valeur dans une variable “@ContentOfEncryptedObject”.
- Calcul de la taille de l’objet dans “@ContentDataLength” via la fonction “DATALENGTH(@ContentOfEncryptedObject)/2” (stockage en hexadécimal, d’où le /2).
- Création d’une instruction “ALTER PROCEDURE” complétée par le caractère de commentaire MSSQL “-” jusqu’à la taille de l’objet définie précédemment. Exemple : “ALTER PROCEDURE [dbo].[helloWorld] WITH ENCRYPTION AS———–[…]”
- Exécution de l’instruction d’altération de la procédure. Ceci à pour effet de supprimer la procédure effective précédente, que l’on cherche à déchiffrer. Cette procédure a été sauvegardée dans “@ContentOfEncryptedObject”.
- Récupération de la nouvelle procédure redéfinie (complétée avec des “-“) dans la variable “@ContentOfFakeEncryptedObject”.
- Annulation de la modification de la procédure (RollBack), pour restaurer la version chiffrée ciblée (toutefois la fausse procédure a été stockée dans “@ContentOfFakeEncryptedObject”).
- Création d’une instruction “CREATE PROCEDURE” complétée par le caractère de commentaire MSSQL “-” jusqu’à la taille de l’objet “@ContentDataLength”. Exemple : “CREATE PROCEDURE [dbo].[helloWorld] WITH ENCRYPTION AS—————[…]” et stockage de cette instruction dans la variable “@ContentFakeObject”
Une fois les différentes variables initialisées, l’algorithme de décodage via un “OU exclusif” (XOR) peut être appliqué. Celui-ci est réalisé caractère par caractère en fonction de la taille de objet définie (@i = 1 to @ObjectDataLength), entre les données de la procédure chiffrée cible, les données de la fausse procédure chiffrée et les données de l’instruction de création de la procédure (en clair). Soit :
[sql]</p>
<p>NCHAR(</p>
<p>UNICODE(SUBSTRING(@ContentOfEncryptedObject, @i, 1)) ^</p>
<p>(</p>
<p>UNICODE(SUBSTRING(@ContentOfFakeObject, @i, 1)) ^</p>
<p>UNICODE(SUBSTRING(@ContentOfFakeEncryptedObject, @i, 1))</p>
<p>)</p>
<p>)<br />
[/sql]
L’idée est de définir la clé du XOR à partir des deux “fausses” procédures ; puis d’utiliser cette clé sur la véritable procédure cible. Le résultat est le code (commenté) de l’objet en clair.
Renforcement de sécurité
Pour renforcer les mécanismes de protection intégrés à MSSQL, différentes solutions existent. On peut citer l’une d’entre elle, nommée “{3S} SQL Smart Security”.
Cette solution est un add-in de “Microsoft SQL Server Management Studio” (SSMS) pour les versions 2005, 2008, 2008 R2, 20012 et leurs versions EXPRESS respectives. La version actuelle de ce produit (1.1) ne traite que les procédures stockées. L’éditeur prévoit à l’avenir de l’étendre aux fonctions, déclencheurs et aux vues.
L’idée de cette solution est d’enrichir et de renforcer le mécanisme intrinsèque à MSSQL “WITH ENCRYPTION” via de nouveaux mécanismes cryptographiques. A surveiller pour l’avenir 😉 !
Sources & ressources :
- SQLMag article par Omri Bahat
- Procédures de Omri Bahat
- Article de StackOverflow
- Decrypt SQL Server dabatase objects par Greg Robidoux
- {3S} SQL Smart Security de Marian Placko
- {3S} SQL Smart Security Manual de Marian Placko