Нередко путем логического вывода можно извлечь из базы данных информацию, на получение которой стандартными средствами у пользователя не хватает привилегий.
Рассмотрим больничную базу данных, состоящую из двух таблиц. В первой хранится информация о пациентах (анкетные данные, диагноз, назначения и т.п.), во второй - сведения о докторах (расписание мероприятий, перечень пациентов и т.д.). Если пользователь имеет право доступа только к таблице докторов, он, тем не менее, может получить косвенную информацию о диагнозах пациентов, поскольку, как правило, врачи специализируются на лечении определенных болезней. Еще один пример - выяснение набора первичных ключей таблицы при наличии только привилегии INSERT (без привилегии SELECT). Если набор возможных значений ключей примерно известен, можно пытаться вставлять новые строки с "интересными" ключами и анализировать коды завершения SQL-операторов. Как мы видели из предыдущего примера, сам факт присутствия определенного ключа в таблице может быть весьма информативным.
Если для реализации контроля доступа используются представления, и эти представления допускают модификацию, с помощью операций модификации/вставки можно получить информацию о содержимом базовых таблиц, не располагая прямым доступом к ним.
Основным средством борьбы с подобными угрозами, помимо тщательно проектирования модели данных, является механизм размножения строк. Суть его в том, что в состав первичного ключа, явно или неявно, включается метка безопасности, за счет чего появляется возможность хранить в таблице несколько экземпляров строк с одинаковыми значениями "содержательных" ключевых полей. Наиболее естественно размножение строк реализуется в СУБД, поддерживающих метки безопасности (например, в INGRES/Enhanced Security), однако и стандартными SQL-средствами можно получить удовлетворительное решение. Продолжая медицинскую тематику, рассмотрим базу данных, состоящую из одной таблицы с двумя столбцами: имя пациента и диагноз. Предполагается, что имя является первичным ключом. Каждая из строк таблицы относится к одному из двух уровней секретности - высокому (HIGH) и низкому (LOW). Соответственно, и пользователи подразделяются на два уровня благонадежности, которые мы также будем называть высоким и низким. К высокому уровню секретности относятся сведения о пациентах, находящихся под надзором правоохранительных органов или страдающих специфическими заболеваниями. На низком уровне располагаются данные о прочих пациентах, а также информация о некоторых "секретных" пациентах с "маскировочным" диагнозом. Части таблицы могут выглядеть примерно так:
Табл. 1. Данные с высоким уровнем секретности
Имя
Диагноз
Иванов
СПИД
Петров
Сифилис
Сидоров
Стреляная рана
Табл. 2. Данные с низким уровнем секретности
Имя
Диагноз
Ивлев
Рак легких
Иванов
Пневмония
Ярцев
Ожог второй степени
Суворов
Микроинфаркт
Обратим внимание на то, что сведения о пациенте по фамилии Иванов присутствуют на обоих уровнях, но содержат разные диагнозы. Мы хотим реализовать такую дисциплину доступа, чтобы пользователи с низким уровнем благонадежности могли манипулировать только данными на своем уровне и не имели возможности сделать какие-либо выводы о присутствии в секретной половине сведений о конкретных пациентах. Пользователи с высоким уровнем благонадежности должны иметь доступ к секретной половине таблицы, а также к информации о прочих пациентах. Дезинформирующих строк о секретных пациентах они видеть не должны:
Табл. 3. Данные, доступные пользователю с высоким уровнем секретности
Имя
Диагноз
Иванов
СПИД
Ивлев
Рак легких
Иванов
Пневмония
Петров
Сифилис
Сидоров
Стреляная рана
Ярцев
Ожог второй степени
Суворов
Микроинфаркт
(Обратим внимание на то, что строка "Иванов Пневмония" здесь отсутствует.)
Размножение строк, обеспечивающее необходимую дисциплину доступа, стандартными средствами SQL можно реализовать следующим образом:
CREATE TABLE BaseTable1 (PatientName char (20),
Disease char (20),
Level char (10)) WITH PRIMARY KEY (PatientName, Level);
CREATE TABLE BaseTable2 (UserName char (20),
SecurityLevel char (10)) WITH PRIMARY KEY (UserName);
CREATE VIEW PatientInfo (PatientName, Disease) AS SELECT PatientName, Disease
FROM TABLE BaseTable1
WHERE BaseTable1.Level = (SELECT SecurityLevel FROM BaseTable2
WHERE UserName = username ( )) OR (BaseTable1.Level = "LOW"
AND NOT EXISTS (SELECT * FROM BaseTable1 AS X
WHERE X.PatientName = BaseTable1.PatientName
AND X.Level = "HIGH"));
Всем пользователям предоставляется доступ только к представлению PatientInfo. Пользователи с низким уровнем благонадежности увидят только информацию, выдаваемую первой конструкцией WHERE:
WHERE BaseTable1.Level = (
SELECT SecurityLevel FROM BaseTable2
WHERE UserName = username () )
поскольку для них поле SecurityLevel имеет значение "LOW". В формирование представления для благонадежных пользователей внесут вклад обе конструкции WHERE, причем в случае совпадения имен менее секретные записи будут заслонены более секретными (конструкция NOT EXISTS).
Мы видим, что в отличие от систем с меточной безопасностью, стандартные SQL-серверы предоставляют довольно тяжеловесные средства для реализации механизма размножения строк. Тем не менее, эти средства не так плохи, как может показаться на первый взгляд. Можно надеяться, что оптимизатор SQL-запросов, входящий в комплект любой современной СУБД, сделает время доступа к представлению PatientInfo сравнимым с временем извлечения строк из базовых таблиц. Нетрудно понять, что борьба с получением информации путем логического вывода актуальна не только для медицинских баз данных и что она (борьба) требует кропотливого труда при проектировании модели данных и иерархии привилегий, а также при реализации видимых пользователям представлений.