Содержание статьи
Как же ошибаются те люди, которые доверяют защиту данных исключительно самой
СУБД. Мол, если пароль на подключение хороший и версия демона – самая последняя,
то все будет нормально. Ничего подобного. Базы как сливали, так и будут сливать.
А наша задача – сделать их нечитаемыми для тех, кому они не предназначены.
Актуальная проблема из мира информационной безопасности – обеспечить
сохранность данных. Есть ситуации, в которых даже при наличии серьезной защиты
системы, сохранность данных оказывается под большим вопросом. Как так? Могу
привести пример из личного опыта, когда в разглашении информации был виновен
засланный сотрудник конкурирующей компании. Находясь на рабочем местом и будучи
технически подкованным, он взламывал сервера баз данных банальным брутфорсом
через терминальное соединение. Базы с клиентами "засланец" перепродавал другим
компаниям, а "интересная" информация о ведении бизнеса отправлялась сотрудникам
силовых структур. Что из этого вышло, объяснять излишне.
Вообще, имея физический доступ к локальной сети, инсайдер мог поступить
гораздо проще: атаковать программу, которая работает с базой данных. Нередко
сценарий взлома сводится к тому, что из программы разными способами извлекаются
конфиги для подключения к базе. Захватив ту же 1С, которая хранит в себе конфиги
подключения к базе (в том числе, шифрованный обычным XOR'ом пароль),
злоумышленник получает доступ к самой базе. Особо не стесняясь, он может ее
выкачать, модифицировать или просто удалить. Такая брешь в защите способна
сыграть злую шутку, особенно в корпоративной среде.
В статье я как раз хочу рассказать о том, как обезопасить информацию в
обычной базе данных. Даже если СУБД будет взломана или левый человек скопирует
данные, утечки конфиденциальной информации не произойдет!
Шифрованию – быть!
Общий подход прост до гениального: раз злоумышленник гипотетически сможет
извлечь данные, надо сделать так, чтобы он их не смог прочитать. Информацию все
равно придется хранить в базе, но… ничего не мешает хранить ее в каком угодно
виде, в том числе зашифрованном! Главное, чтобы мы сами потом смогли
расшифровать :).
Компания Spelabs (www.spellabs.ru/spellabsCrypto1C.htm)
как-то анонсировала продукт, организующий дополнительную безопасность
бухгалтерских 1С на уровне шифрования данных, причем на полностью прозрачном
уровне. Пользовательские приложения, не подозревая о надстройке, работали в
обычном режиме. Увы, компания прекратила разработку этого направления. Но
реально обойтись и без подобных инструментов, ведь для шифрования сгодятся даже
штатные средства СУБД!
Любая современная СУБД, если это, конечно, не собранная на коленке курсовая,
может похвастаться достаточно надежными механизмами шифрования данных. В той же
самой MySQL я по памяти насчитал около 14 соответствующих функций, которые тебе
наверняка хорошо известны:
- AES_ENCRYPT() - шифрование AES
- AES_DECRYPT() - расшифровка AES
- COMPRESS() - возвращение результата в бинарном виде
- DES_ENCRYPT() - шифрование DES
- DES_DECRYPT() - дешифрование DES
- ENCODE() - шифрование строки поверхностным паролем (на выходе
получается шифрованное слово первоначальной "plaintext" длины - DECODE() - расшифровка текста, обработанного функцией ENCODE()
- ENCRYPT() - шифрование с помощью Unix’ового системного вызова crypt
- MD5() - подсчет MD-5 суммы
- SHA1(), SHA() - подсчет SHA-1 (160-бит)
Для их применения надо лишь чуть изменить свои SQL-запросы, добавив в нужном
месте функции AES_ENCRYPT() или DES_ENCRYPT(), которые считаются наиболее
надежными в MySQL на текущий момент. Например, так:
INSERT INTO t VALUES (1,AES_ENCRYPT('text','password'));
Приятно признать, что хорошие программисты эти функции используют. Часто во
время проведения SQL-инжекции мне приходилось ломать голову и определять
функцию, которую использовал кодер для крипточки данных. В результате, требуется
производить те же AES_DECRYPT(AES_ENCRYPT()) наряду с unhex(hex()).
T-SQL
Помимо симметричного шифрования, когда упаковка и распаковка текста
производятся одним и тем же ключом (общим для двух участников обмена
сообщениями), поддерживается и ассиметричное криптование. Идея ассиметричных
алгоритмов подразумевает наличие двух ключей – открытого и закрытого
(секретного). Один из них используется для шифрования информации, а другой — для
дешифрования. Если кодирование осуществляется с помощью открытого ключа, то
расшифровать такие данные можно только с помощью парного ему закрытого.
Предлагаю разобраться с этим на примере Microsoft SQL Server, который часто
используется в корпоративных порталах и сложных приложениях.
Для шифрования применяются функции T-SQL, представляющие собой специальное
дополнение языка SQL. Оно поддерживает управляющие операторы, локальные
переменные и различные дополнительные функции.
Одна из таких функций - EncryptByCert(), используемая для ассиметричного
шифрования данных с помощью сертификатов. Открытым ключом тут выступает
сертификат. Только откуда этот сертификат взять? Ответ прост – сгенерировать с
помощью другой специальной функции. Покажу на примере, как можно сгенерировать
сертификат с именем для andrej базы "Bank" с помощью хранимой процедуры:
USE Bank;
CREATE CERTIFICATE andrej
ENCRYPTION BY PASSWORD = 'pGFD4bb925DGvbd2439587y'
# Для генерации с использованием подгрузки из файла
# FROM FILE = 'c:\Shipping\Certs\Shipping11.cer'
# WITH PRIVATE KEY (FILE = 'c:\Shipping\Certs\Shipping11.pvk',
WITH SUBJECT = 'Employers Access',
EXPIRY_DATE = '10/31/2009';
GO
У нас создался сертификат! Теперь его можно без проблем использовать для
размещения в таблице зашифрованных записей, выполняя привычные SQL-запросы:
INSERT INTO [БАЗА].[ТАБЛИЦА]
values( N'ДАННЫЕ ДЛЯ ЗАШИФРОВКИ’,
EncryptByCert(Cert_ID('andrej'), @cleartext) );
GO
В этом примере неформатированный текст из переменной @cleartext шифруется
сертификатом с именем "andrej". Зашифрованные данные помещаются в таблицу
"ТАБЛИЦА". Уточню, что данные могут быть расшифрованы только с помощью
соответствующего закрытого ключа (как уже было сказано, "приватного").
Имя функции для обратного преобразования угадать несложно: DecryptByCert(). А
вот синтаксис более хитер, и с ним все чуть сложнее. Дело в том, что на
приватный ключ, как правило, закладывается дополнительный пароль (passphrase).
Его необходимо ввести, и по этой причине он обязательно будет присутствовать в
коде запроса или процедуры. Это не очень хорошо, потому что в этом случае его
можно быстро увести. Но с этим мы разберемся позже, когда поговорим о
безопасности хранимых процедур. А пока – код для извлечения данных из
шифрованной базы данных:
SELECT convert(nvarchar(max), DecryptByCert(Cert_Id('andrej'),
ProtectedData, N'pGFD4bb925DGvbd2439587y'))
FROM [БАЗА].[ТАБЛИЦА]
WHERE Description
= N'Employers Access’;
GO
В этом примере производится выборка строк из таблицы [БАЗА].[ТАБЛИЦА],
помеченных как "Employers Access". Пример дешифрует зашифрованный текст с
помощью закрытого ключа сертификата "Andrej" и дополнительного пароля
pGFD4bb925DGvbd2439587y. Расшифрованные данные преобразуются из типа varbinary в
тип nvarchar.
Надо сказать, что ассиметричные преобразования гораздо более накладны, чем
шифрование и дешифрование с использованием симметричного ключа. Поэтому
использование ассиметричного шифрования не рекомендуется при работе с большими
объемами данных, например, таблицами пользовательских данных. Это важно
учитывать при особо больших базах, а также базах, структура которых не приведена
к одной из нормальных форм.
Прячем хранимые процедуры!
Если ты не заметил, многое упирается в то, что вся конфиденциальность и
целостность завязана на использование хранимых процедур или функций. Получается,
что, получив к ним доступ и грамотно проанализировав их код, любой опытный хакер
сможет преобразовать шифрованную белиберду в исходный текст. Конечно, добраться
до кода процедур далеко не всегда реально, потому здесь всплывает вопрос об
утечке программной начинки производства, а именно – логике ПО. Но именно по этой
причине необходимо прибегать к шифрованию процедур и функций в базе. Одно из
самых популярных и удачных средств для таких действий – это программа SQL Shield
(www.sql-shield.com).
После несложной установки приложения не забудь указывать дополнительный флаг
/*sqlshield*/ с выражением "WITH ENCRYPTION" всякий раз, когда будешь создавать
защищенную процедуру. Вот пример функции, которая исполняет простейший запрос к
базе:
CREATE PROCEDURE MyTest
WITH /*sqlshield*/ ENCRYPTION
AS
SELECT 2+2
Исполняем процедуру и получаем:
MyTest
> 4
Проверим, в каком виде хранится процедура, с помощью специального средства
для ковыряния в файлах базы. Подойдет SQL Server Syscomments Decryptor (www.geocities.com/d0mn4r/dSQLSRVD.html),
который при отображении текста процедуры показывает одни лишь знаки вопроса.
Все работает!
Как облегчить себе жизнь?
В заключение – не менее интересный продукт XP_CRYPT (www.xpcrypt.com).
Это средство осуществляет весь тот геморрой, который мы только что проделали
вручную. Все, что от тебя требуется, – скачать дистрибутив проги, установить ее
на сервер (к сожалению, есть версия только для Винды), обозначить свою базу
данных и начать химию с таблицами с помощью удобного GUI-интерфейса.
Организуем знакомство на примере из практики. Предположим, у нас есть
интернет-магазин, где каким-то образом хранятся данные о кредитных картах
клиентов (распространенная ситуация, хотя это категорически запрещено!). Наша
задача - зашифровать конкретные данные о клиентах, т.е. поля с паролем, номером
кредитной карточки и т.п. Пока мы ничего не делали, при запросе SELECT * FROM
tbl_CCards, СУБД возвращает все в открытом виде:
Username Password CredCardNum
james god 1234567890123456
lucas sex 2894787650102827
anna love 3234563638716434
Напишем внешнюю функцию UDF (расшифровывается, как "User-Defined-Function",
подробности) для преобразования строки в SHA-хеш:
CREATE FUNCTION ud_MakeSHA1 (@clearpass VARCHAR (8000) )
RETURNS VARCHAR (40)
AS
BEGIN
DECLARE @ret as VARCHAR(40)
EXEC master..xp_sha1 @clearpass,@ret OUTPUT
RETURN @ret
END
Отдаем команду: UPDATE tbl_CCards SET password = dbo.ud_MakeSHA1(Password).
После чего еще раз проверяем содержимое базы той же командой. Что видим? Все
зашифровано, все пароли захешировались!
Теперь нужно не забыть о том, что мы используем шифрование при обращении к
базе. В нашем случае, когда ты будешь делать авторизацию пользователей,
процедура проверки пароля по хешу будет выглядеть примерно так:
CREATE FUNCTION ud_CheckUser (@username VARCHAR(16),@clear_pass VARCHAR
(16))
RETURNS INTEGER
AS BEGIN
DECLARE @res INTEGER
SELECT @res = count(*) FROM tbl_CCards where username=@username AND
password=dbo.ud_MakeSHA1(@clear_pass)
IF @res > 1 SELECT @res= 0
RETURN @res
END
Проверяем исполнением команды:
SELECT dbo.ud_CheckUser ('anna','kolbaska')
>1 (неправильно)
SELECT dbo.ud_CheckUser ('anna','love')
>0 (окейно!)
К шифрованию номера кредитной карты надо подойти более серьезно. Впрочем, мы
не будем вникать в различного рода стандарты по информационной безопасности в
платежно-карточной сфере (вроде PCI; хотя организации, работающие с кредитными
картами, безусловно обязаны это делать). В бесплатной версии XP_CRYPT существует
возможность генерации только 256-битного ключа RSA. Вот этим и можно
воспользоваться (пусть упомянутый стандарт и требует, как минимум, 768-битного
ключа). Я бы мог сейчас привести код по генерации публичного и приватного ключа,
но… поступим проще. Все действия можно выполнить из понятного графического
интерфейса, и во многих случаях оставить все на совести программы, не написав ни
строчки кода. И поверь, будет работать!
Пример из личного опыта
Сотрудниками одной из компаний платежно-карточного сектора велась база данных
для выдачи зарплат. Для простого понимания, – пусть это будет примерно следующая
структура:
ID LastName FirstName Emp
Sum
354 Somov Oleg
IT-Manager M0x8900f56543
643 Antipova Alexandra Director
4343Lax#dsdsss
411 Timurov Valeriy Technical Dep.
0x2322322222
Выборку из базы я привел абсолютно произвольную, а теперь суть идеи.
"Безопасниками" компании было предпринято вполне благородное решение – шифровать
данные о зарплатах, которые очень часто бывают "серыми". Инсайдер получил доступ
к базе и сильно обломался, заимев информацию в таком виде. Однако, он не
отчаялся и проделал следующий фокус. Резонно предположив, что человек с
должностью директора должен получать больше, чем простой менеджер, он поменял
соответствующие поля со значениями зарплат одно на другое, тем самым – подложив
бухгалтерскому отделу абсолютно левую информацию. В благополучный день такая
вещь прокатила, и все безопасники были уволены. Для справки: этим сотрудником
был не я, как впрочем, и не безопасником.
Так делать не стоит
В SQL Server можно создавать четыре типа объектов (хранимые процедуры,
представления, пользовательские функции и триггеры) с параметром WITH
ENCRYPTION. Этот параметр позволяет зашифровать определение объекта таким
образом, что тот можно будет использовать, но получить его определение
стандартными способами станет невозможно. Это средство рекомендуется и
Microsoft. К сожалению, на практике никакой защиты применение стандартных
средств шифрования не обеспечивает! Алгоритм, используемый при шифровании
определений объектов, выглядит так:
- SQL Server берет GUID той базы данных, в которой создается объект, и
значение столбца colid таблицы syscomments для создаваемого объекта (чаще
всего, его значение – 1 или 2) и производит их конкатенацию; - Из полученного значения генерируется ключ при помощи алгоритма SHA;
- Этот хеш используется в качестве входящего значения при применении еще
одного алгоритма хеширования – RSA. С его помощью генерируется набор символов,
равный по длине шифруемому определению объекта; - С этим набором символов и с реальным определением объекта производится
операция XOR. В результате получаются данные, которые помещаются в столбец
ctext таблицы syscomments.
У этой схемы есть два слабых места:
- Нам ничего не мешает заиметь исходные значения (GUID и colid) для
выполнения тех же самых операций и получить ключ на расшифровку. Это можно
сделать, например, использовав утилиту dSQLSRVD. Правда, для получения GUID
базы данных (и для запуска этой утилиты) нам нужны права системного
администратора; - Если у нас есть права на создание объектов в базе данных, можно
сгенерировать точно такой же ключ для объекта, определение которого нам уже
известно (путем сравнения шифрованного определения с незашифрованным). Ну и –
использовать его для расшифровки значения другого объекта.
Как можно расшифровать зашифрованные объекты на SQL Server? Есть два
варианта:
- Использовать утилиту dSQLSRVD. Она позволяет выбрать любой зашифрованный
объект на сервере и записать его определение в текстовый файл; - Использовать хранимую процедуру DECRYPT2K. Код на создание данных хранимых
процедур (в разных вариантах и с разными объяснениями), которые расшифровывают
определение зашифрованных объектов, легко найти через Google.