Как надёжно стереть секретную информацию из базы данных

TL; DR

Если это СУБД с гитхаба, то никак.

Зачем вообще «надёжно» стирать данные? Главное же, чтобы пользователь через интерфейс СУБД не мог их достать. Мало ли, что там за остатки данных в файлах болтаются, это же не проблема. Или нет?

Вообще утечки данных через память и файлы иногда встречаются. Из-за этого есть требование затирать переменные, в которых хранились пароли или ключи. В таких ситуациях оптимизирующие иногда компиляторы добавляют багов, удаляя memset для буфера, который больше не используется.

А для баз данных, к примеру, есть требование ФСТЭК:

13.1. Система управления базами данных 6, 5 классов защиты самостоятельно или с применением сертифицированной операционной системы должна обеспечивать удаление баз данных и журналов, используемых системой управления базами данных, путем многократной перезаписи уничтожаемых (стираемых) объектов файловой системы специальными битовыми последовательностями.

Получается, что (сертифицированные) СУБД должны не просто помечать записи, как удалённые, но и перезаписывать удаляемые данные во всех местах, куда они были сохранины.

Нас заинтересовала эта тема, и мы решили потестировать публично доступные СУБД на предмет того, удаляют ли она данные, или что-то остаётся. И так как мы разрабатываем Natch — инструмент поиска поверхности атаки, то все проблемы выглядят как гвозди решили с помощью него исследовать потоки данных в СУБД.

Ведь работа с чувствительными данными и поиск утечек мало отличается от работы с недоверенными данными и поиска поверхности атаки.

Идея тестирования была такая:

  1. Создаём базу данных.

  2. Записываем туда «чувствительные» данные.

  3. Отслеживаем поток этих данных в системе.

  4. Удаляем данные средствами СУБД.

  5. Проверяем те места, куда в п.3 данные попали, по-прежнему они там или нет.

  6. Пишем статью на хабр.

PostgreSQL

Для того, чтобы проверить, удаляются ли данные, нужно понять, где они вообще хранятся. Конечно же, сначала надо создать базу данных:

CREATE DATABASE my;
USE my;
CREATE TABLE mytable (col text);

Natch умеет отслеживать «чувствительные» данные, поступающие из сети, или считанные из файла. Тогда можно либо подключаться к СУБД удалённо, помечая сетевой трафик (который и будет содержать добавляемые в базу данные). Либо записать команду INSERT в файл, и помечать для отслеживание уже содержимое файла.

Мы применяли второй способ, поэтому запускали PostgreSQL под контролем Natch, передавая в СУБД файл pg_insert.sql со следующим запросом:

INSERT INTO mytable VALUES (‘dhfgkjhewdrtg;kljhwekjthekjthkjwehtkjhertkjhewrkjthekjthkjwerhtkjhertkjhekljthekjgkjdsngkjnikjuh43kiujhtkj3ntkj3j3ntkj34hnrtkjh 3kjhtk3hrlkj oruq97g98weytg nui3ht k3h4trkhglkeoi;tru 8o934urowjrh qwtuikh3489tuyoiktghiljpoqwro;iwotrhyt’);

Добавляемая строка не очень случайная, но нам повезло, и она не сильно подверглась компрессии в хранилище. В итоге, Natch определил, что вставленные в таблицу данные осели в файлах data/base/16389/16390 и data/pg_wal/000000010000000000000001.

Куда попали данные из SQL-запроса?

Куда попали данные из SQL-запроса?

В первом файле хранятся сами данные строк таблицы, а второй — это write-ahead log (WAL). Это журнал изменений, который используется, по меньшей мере, для восстановления БД после сбоев.

Теперь, когда мы будем удалять данные, нужно убедиться, что они пропали из обоих файлов. Ведь это чувствительные данные, не хочется их разбрасывать где попало.

Тут уже понадобится небольшая хитрость. Ведь Natch отслеживает данные, пока они куда-то перемещаются или где-то обрабатываются. Но если данных больше нет, что он может показать? Для решения этой проблемы можно просто обратиться к содержимому файлов, где хранились чувствительные данные (с помощью find, grep или даже md5sum). И тогда либо мы увидим нужный паттерн в логе файловых транзакций, либо на диаграмме процессов появится стрелочка с передачей чувствительных данных в новый процесс.

Почему стрелочка может не появиться?

Дело в том, что Natch пока что не отслеживает распространение помеченных данных на дисковых накопителях. Поэтому если что-то записать на диск, а потом прочитать заново, то пометки будут потеряны.

Но если ОС оптимизирует этот процесс, и после записи сохранит данные в кэше в ОЗУ, то и пометки для них тоже сохранятся. В таких ситуациях поток данных Natch отслеживает успешно.

Итак, попробуем удалть данные из таблицы, а потом посчитать md5 для интересующих нас файлов. Утилита md5sum вынуждена будет прочитать все данные из файлов, а нам только это и нужно.

DELETE FROM mytable;

Команда DELETE наверняка не удаляет строки физически. Для полного удаления придётся сделать что-то ещё. Проверим эту гипотезу.

Утилита md5sum получила доступ к помеченным данным.

Утилита md5sum получила доступ к помеченным данным.

Файловые транзакции даже не пришлось смотреть, все данные оставались в кэше ОС. И действительно, из таблицы удалённые строчки не пропали. Но есть ещё команда VACUUM. Она-то точно удалит всё лишнее.

Потоки данных после использования связки DELETE + VACUUM.

Потоки данных после использования связки DELETE + VACUUM.

Данные всё ещё там, в WAL. Правда, уже в новом файле. А непосредственно из таблицы действительно всё удалилось.

Как ещё можно повлиять на БД? Удалить её целиком. Уж теперь-то точно всё должно пропасть.

DROP DATABASE my;
VACUUM FULL;

Для верности остановим СУБД, чтобы точно ничего не осталось в памяти. Ну и проверим, на месте ли добавленные данные, только в этот раз с помощью grep, а не md5sum:

systemctl stop postgresql
cd /usr/local/pgsql/data/base
grep -r oruq97g98

Поток данных от INSERT сначала попал в WAL, а потом дошёл до grep.

Поток данных от INSERT сначала попал в WAL, а потом дошёл до grep.

Итак, СУБД была остановлена, вся база удалена, а «чувствительные» данные не только остались в файле, но ещё и висели в файловом кэше ОС.

MySQL

Следующая СУБД для исследования — MySQL. С ней будем делать примерно то же самое. Сначала выясним, в какие файлы попадут наши данные после INSERT:

Файлы с данными, полученными из команды INSERT.

Файлы с данными, полученными из команды INSERT.

Тут, как и в postgres, кроме файла с таблицей, есть и дополнительные файлы с транзакциями — binlog и redo log.

Проверим, что будет после удаления данных из таблицы:

DELETE FROM mytable;
FLUSH TABLES;
FLUSH BINARY LOGS;
FLUSH ENGINE LOGS;

Где остались данные после выполнения DELETE?

Где остались данные после выполнения DELETE?

Данные ожидаемо сохранились в логах транзакций. Оказывается, binlog можно подчистить с помощью команды RESET BINARY LOGS AND GTIDS.

После DELETE и очистки логов данные остались только в файле транзакций.

После DELETE и очистки логов данные остались только в файле транзакций.

Остался redo log. Удаляем БД.

Лог транзакций сохраняет

Лог транзакций сохраняет «чувствительные» данные даже после удаления всей БД.

И опять из лога изменений данные стереть не удалось.

MariaDB

Третий наш испытуемый — СУБД MariaDB. Сначала выясняем, где хранятся добавляемые в таблицу данные.

Кажется, что MariaDB записывает всё в один файл.

Кажется, что MariaDB записывает всё в один файл.

Natch показывает запись только в logfile. Это довольно странно, потому что должен же ещё быть файл с таблицей. Погрепаем нашу строку-паттерн в /var/lib/mysql, где лежит и этот logfile, и другие файлы mariadb.

С помощью grep удалось найти, куда были записаны данные.

С помощью grep удалось найти, куда были записаны данные.

Действительно, есть и таблица, и ещё какой-то дополнительный файл. Что там прочитал grep, можно проверить с помощью Natch:

502bf66a013f2bb224ca3013daba866f.png

Особенность Natch в том, что потоки данных отслеживаются в первую очередь внутри программ и между ними. Данные на диске же напрямую пока не отслеживаются, поэтому если они записались не через системный вызов write, а например, через fsync, то стрелочка на диаграмме не появится. Тем не менее, исследовать работу СУБД всё же получается, хоть и применяя вспомогательные утилиты.

Теперь попробуем удалить данные из БД с помощью команды DELETE. После удаления, останавливаем сервис СУБД и делаем grep. Поток данных из ib_logfile0 в grep не обнаружился, значит MariaDB умеет делать так, чтобы ОС не кэшировала данные этого файла в памяти.

После удаления данных картинка не изменилась, помеченные данные были удалены из файлового кэша.

После удаления данных картинка не изменилась, помеченные данные были удалены из файлового кэша.

Так как Natch не видит grep на графе, посмотрим логи операций с файлами.

Обычные read и write (и их варианты) для файлов Natch перехватывает хорошо.

Обычные read и write (и их варианты) для файлов Natch перехватывает хорошо.

Удивительно, что данные из таблицы пропали после обычного DELETE. Теперь попробуем удалить всю БД с помощью команды DROP DATABASE.

Получилось всё то же самое. Файлов с таблицами уже нет на диске, а данные в ib_logfile0 остались. Всё, как и в остальных СУБД, «чувствительные» данные хоть где-то, да сохраняются. Так что у MariaDB отличается только стратегия работы с файлами таблиц.

Заключение

Оказывается, сертифицированные и enterprise-версии открытых продуктов существуют не просто так. Ни одна из проверенных свободных СУБД не позволяет вычистить удаляемые данные изо всех файлов. Поэтому, если вы строите сертифицируемую систему, просто так взять и установить СУБД из репозитория не выйдет.

Ссылки

  1. Руководство пользователя Natch

  2. Телеграм-канал поддержки Natch

  3. Кейсы реального использования Natch

© Habrahabr.ru