Administrator programu SQL Server może dość łatwo odzyskać tekst procedur składowanych, widoków, funkcji i wyzwalaczy chronionych za pomocą WITH ENCRYPTION
. Napisano na ten temat wiele artykułów i dostępnych jest kilka narzędzi komercyjnych. Podstawowy zarys popularnej metody to:
- Uzyskaj zaszyfrowany formularz (A) za pomocą dedykowanego połączenia administratora.
- Rozpocznij transakcję.
- Zastąp definicję obiektu znanym tekstem (B) o co najmniej tej samej długości co oryginał.
- Uzyskaj zaszyfrowaną formę znanego tekstu (C).
- Wycofaj transakcję, aby pozostawić obiekt docelowy w stanie początkowym.
- Uzyskaj niezaszyfrowany oryginał, stosując znak wyłączności lub do każdego znaku:
A XOR (B XOR C)
To wszystko jest całkiem proste, ale wydaje się trochę magiczne:nie wyjaśnia zbyt wiele o tym, jak i dlaczego to działa . Ten artykuł obejmuje ten aspekt dla tych z Was, dla których tego rodzaju szczegóły są interesujące, i zapewnia alternatywną metodę odszyfrowywania, która lepiej ilustruje proces.
Szyfr strumienia
Bazowy algorytm szyfrowania, którego SQL Server używa do szyfrowania modułów jest szyfrem strumieniowym RC4™. Zarys procesu szyfrowania to:
- Zainicjuj szyfr RC4 za pomocą klucza kryptograficznego.
- Generuj pseudolosowy strumień bajtów.
- Połącz zwykły tekst modułu ze strumieniem bajtów za pomocą exclusive-lub.
Możemy zaobserwować ten proces za pomocą debuggera i symboli publicznych. Na przykład poniższy zrzut stosu pokazuje, że SQL Server inicjuje klucz RC4 podczas przygotowywania do zaszyfrowania tekstu modułu:
Ten następny pokazuje SQL Server szyfrujący tekst przy użyciu pseudolosowego strumienia bajtów RC4:
Podobnie jak większość szyfrów strumieniowych, proces odszyfrowywania jest taki sam jak szyfrowanie, wykorzystując fakt, że exclusive-or jest odwracalny (A XOR B XOR B = A
).
Użycie szyfru strumieniowego jest powodem exclusive-lub jest stosowany w metodzie opisanej na początku artykułu. Nie ma nic z natury niebezpiecznego w korzystaniu z wyłączności lub, pod warunkiem, że używana jest bezpieczna metoda szyfrowania, klucz inicjujący jest utrzymywany w tajemnicy i nie jest ponownie używany.
RC4 nie jest szczególnie mocny, ale to nie jest tutaj główny problem. To powiedziawszy, warto zauważyć, że szyfrowanie przy użyciu RC4 jest stopniowo usuwane z SQL Server i jest przestarzałe (lub wyłączone, w zależności od wersji i poziomu zgodności bazy danych) dla operacji użytkownika, takich jak tworzenie klucza symetrycznego.
Klucz inicjujący RC4
SQL Server wykorzystuje trzy informacje do wygenerowania klucza użytego do zainicjowania szyfru strumieniowego RC4:
- Identyfikator GUID rodziny baz danych.
Najłatwiej to uzyskać, wysyłając zapytanie sys.database_recovery_status . Jest również widoczny w nieudokumentowanych poleceniach, takich jak
DBCC DBINFO
iDBCC DBTABLE
. - Identyfikator obiektu modułu docelowego.
To tylko znany identyfikator obiektu. Należy zauważyć, że nie wszystkie moduły umożliwiające szyfrowanie są objęte zakresem schematu. Będziesz musiał użyć widoków metadanych (sys.triggers lub sys.server_triggers ), aby uzyskać identyfikator obiektu dla wyzwalaczy DDL i o zasięgu serwera, a nie sys.objects lub
OBJECT_ID
, ponieważ działają one tylko z obiektami o zakresie schematu. - Identyfikator podobiektu modułu docelowego.
Jest to numer procedury dla numerowanych procedur składowanych. Jest to 1 dla nienumerowanej procedury składowanej i zero we wszystkich innych przypadkach.
Używając ponownie debuggera, widzimy, że identyfikator GUID rodziny jest pobierany podczas inicjalizacji klucza:
Identyfikator GUID rodziny baz danych jest wpisany uniqueidentifier , identyfikator obiektu to liczba całkowita , a identyfikator podobiektu jest mały .
Każda część klucza musi być przekonwertowane na określony format binarny. W przypadku identyfikatora GUID rodziny baz danych, konwertowanie unikalnego identyfikatora wpisz do binary(16) tworzy poprawną reprezentację binarną. Dwa identyfikatory muszą zostać przekonwertowane na binarne w reprezentacji little-endian (najpierw najmniej znaczący bajt).
Uwaga: Uważaj, aby przypadkowo nie podać identyfikatora GUID jako ciągu! Musi być wpisany uniqueidentifier .
Poniższy fragment kodu pokazuje prawidłowe operacje konwersji dla niektórych przykładowych wartości:
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)));
Ostatnim krokiem do wygenerowania klucza inicjalizacji RC4 jest połączenie trzech powyższych wartości binarnych w jeden binarny(22) i obliczenie skrótu SHA-1 wyniku:
DECLARE @RC4key binary(20) = HASHBYTES('SHA1', @family_guid + @objid + @subobjid);
Dla przykładowych danych podanych powyżej ostatecznym kluczem inicjującym jest:
0x6C914908E041A08DD8766A0CFEDC113585D69AF8
Wkład identyfikatora obiektu i podobiektu modułu docelowego do skrótu SHA-1 jest trudny do zauważenia na pojedynczym zrzucie ekranu debugera, ale zainteresowany czytelnik może odnieść się do demontażu części initspkey poniżej:
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)
SHAInit i SHAUpdate wywołania dodają komponenty do skrótu SHA, który jest ostatecznie obliczany przez wywołanie SHAFinal .
SHAInit wywołanie dostarcza 10h bajtów (16 dziesiętnych) przechowywanych w [rsp+40h], który jest rodzinnym identyfikatorem GUID . Pierwsza SHAUpdate wywołanie dodaje 4 bajty (jak wskazano w rejestrze r8d), przechowywane w [rsp+24h], który jest obiektem ID. Druga SHAUpdate wywołanie dodaje 2 bajty, przechowywane w [rsp+20h], który jest subobjidem .
Ostateczne instrukcje przekazują obliczony skrót SHA-1 do procedury inicjalizacji klucza RC4 rc4_key . Długość skrótu jest przechowywana w rejestrze edx:14h (20 dziesiętnie) bajtów, co jest zdefiniowaną długością skrótu dla SHA i SHA-1 (160 bitów).
Wdrożenie RC4
Podstawowy algorytm RC4 jest dobrze znany i stosunkowo prosty. Byłoby lepiej zaimplementowane w języku .Net ze względu na wydajność i wydajność, ale poniżej znajduje się implementacja T-SQL.
Te dwie funkcje T-SQL implementują algorytm planowania kluczy RC4 i generator liczb pseudolosowych i zostały pierwotnie napisane przez Petera Larssona, MVP SQL Server. Wprowadziłem kilka drobnych modyfikacji, aby nieco poprawić wydajność i umożliwić kodowanie i dekodowanie plików binarnych o długości LOB. Ta część procesu może zostać zastąpiona dowolną standardową implementacją RC4.
/* ** 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
Zaszyfrowany tekst modułu
Najłatwiejszym sposobem uzyskania tego przez administratora SQL Server jest przeczytanie varbinary(max) wartość przechowywana w imageval kolumna sys.sysobjvalues , który jest dostępny tylko przez dedykowane połączenie administratora (DAC).
Jest to ten sam pomysł, co rutynowa metoda opisana we wstępie, chociaż dodajemy filtr valclass =1. Ta wewnętrzna tabela jest również wygodnym miejscem do pobrania subobjid . W przeciwnym razie musielibyśmy sprawdzić sys.numbered_procedures gdy obiekt docelowy jest procedurą, użyj 1 dla procedury nienumerowanej lub zero dla czegokolwiek innego, jak opisano wcześniej.
Można unikać używania przetwornika cyfrowo-analogowego czytając imageval z sys.sysobjvalues bezpośrednio, używając wielu DBCC PAGE
wzywa. Wymaga to trochę więcej pracy, aby zlokalizować strony na podstawie metadanych, postępuj zgodnie z imageval łańcucha LOB i odczytuj docelowe dane binarne z każdej strony. Ten ostatni krok jest o wiele łatwiejszy do wykonania w języku programowania innym niż T-SQL. Zauważ, że DBCC PAGE
będzie działać, nawet jeśli obiekt podstawowy nie jest normalnie czytelny z połączenia innego niż DAC. Jeśli strona nie znajduje się w pamięci, zostanie normalnie odczytana z pamięci trwałej.
Dodatkowy wysiłek, aby uniknąć wymogu DAC, opłaca się, umożliwiając wielu użytkownikom jednoczesne korzystanie z procesu odszyfrowywania. W tym artykule użyję podejścia DAC ze względu na prostotę i przestrzeń.
Przykład praktyczny
Poniższy kod tworzy testową zaszyfrowaną funkcję skalarną:
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;
Pełna implementacja deszyfrowania znajduje się poniżej. Jedynym parametrem, który wymaga zmiany, aby działał z innymi obiektami, jest wartość początkowa @objectid
ustawić w pierwszym DECLARE
oświadczenie.
-- *** 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 ) );
Zwróć uwagę na ostateczną konwersję do nvarchar ponieważ tekst modułu jest wpisywany jako nvarchar(max) .
Dane wyjściowe to:
Wniosek
Powody, dla których metoda opisana we wstępie działa:
- SQL Server używa szyfru strumieniowego RC4 do odwracalnie wyłącznego lub tekstu źródłowego.
- Klucz RC4 zależy tylko od identyfikatora rodziny bazy danych, identyfikatora obiektu i podobjidu.
- Tymczasowe zastąpienie tekstu modułu oznacza wygenerowanie tego samego (zaszyfrowanego SHA-1) klucza RC4.
- Z tym samym kluczem, ten sam strumień RC4 jest generowany, umożliwiając wyłączność lub odszyfrowanie.
Użytkownicy, którzy nie mają dostępu do tabel systemowych, plików baz danych lub innego dostępu na poziomie administratora, nie mogą pobierać zaszyfrowanego tekstu modułu. Ponieważ sam SQL Server musi być w stanie odszyfrować moduł, nie ma sposobu, aby uniemożliwić odpowiednio uprzywilejowanym użytkownikom robienie tego samego.