Статьи

Безопасность базы данных и веб-приложения

Модель угрозы

Это обсуждение безопасности баз данных и веб-приложений, основанное на краткой справочной странице на моем сайте . Эта страница становится громоздкой и не позволяет читателям легко взаимодействовать со мной или другими.

Модель угрозы

Весь анализ безопасности должен начинаться с изучения модели угроз . Модель угрозы требует, чтобы вы ответили на четыре вопроса:

  • что я пытаюсь защитить?
  • от кого?
  • На сколько долго?
  • и по какой (чистой) стоимости?

Что я пытаюсь защитить?

Это очевидное место для начала … и ваш первый ответ, вероятно, неправильный! Я имею в виду, что вы можете ответить «пароль базы данных», но это не совсем верно. На самом деле вы хотите защитить доступ к базе данных от имени этого пользователя — злоумышленник может найти путь к базе данных без пароля, например, с помощью SQL-инъекции.

Но подождите, это тоже не совсем верно! Наша настоящая задача — не дать злоумышленнику использовать этот доступ для нанесения ущерба, изучения конфиденциальной информации и т. Д. На данный момент мы должны перечислить наши реальные проблемы, например, наша база данных может содержать

  • пользовательский контент
  • финансовая информация
  • аутентификация и авторизация пользователей
  • бревна
  • статический контент

Способ доступа и использования этой информации различен

  • пользовательский контент — нужен постоянный доступ для чтения / записи
  • финансовая информация — нужен оракул (для утверждения) и может оставить детали для выполнения процесса
  • аутентификация и авторизация пользователя — нужен оракул (для одобрения и авторизации), когда пользователь входит в систему, но никогда после этого (оракул)
  • логи — нужен постоянный доступ только для добавления (оракул)
  • статический контент — нужен доступ только для чтения при запуске (оракул)

(Весь доступ по модулю требует технического обслуживания.)

Оракул — это автономный метод, который принимает (необязательные) значения и возвращает либо true, либо false . В более общем смысле он может возвращать любой автономный неизменный объект. Хорошим вариантом реализации для оракула является хранимая процедура в базе данных, лучшим выбором будет вызов REST другому веб-приложению, использующему независимую базу данных.

Два примера оракулов:

Аутентификация пользователя: используйте оракула, который принимает имя пользователя и пароль и возвращает логическое значение, указывающее, было ли оно действительным или нет. (Альтернатива: вернуть полную структуру authn / authz при успешном завершении.) Подход без оракула заключается в том, что приложение выполняет запрос к таблицам пользователей и паролей и сравнивает сами пароли.

Аутентификация кредитной карты: используйте оракула, который берет информацию о кредитной карте и сумму покупки и возвращает либо номер подтверждения, либо указание на ошибку. Приложение может полагаться на оракула, хранящего копию ранее предоставленных значений (но не CVV!), Чтобы пользователю не приходилось каждый раз заполнять одну и ту же информацию. , Подход без оракула для приложения, чтобы связать саму информацию.

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

Наконец, что не менее важно, есть еще одна вещь, которую следует защищать: ваша репутация. Не компания — разработчик. Что вы скажете, когда вам позвонит президент компании с требованием узнать, почему компания будет главной новостью в ночных новостях? Вы не можете защитить от всех атак, но не хотите оставаться в безмолвии, когда кто-то требует знать, почему вы не предприняли основных шагов для защиты системы.

От кого?

Все.

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

  • нащупал сотрудников. Мы все сделали это. У них уже есть законный доступ.
  • недовольные сотрудники, особенно бывшие сотрудники. У них уже есть законный доступ и мотивация.
  • Сценарий детишек. Мы склонны думать о них как о неискушенных, но они могут использовать инструменты взлома, написанные экспертами. Вероятно, они перейдут к более простым целям, если ваш сайт достаточно защищен.
  • продвинутые постоянные угрозы (ATP). Это люди, которые имеют сильную мотивацию и сильные технические навыки. Предположим, они войдут.

Этот список далеко не исчерпывающий, и перечисление дополнительных «потенциальных злоумышленников» оставлено в качестве упражнения для читателя.

На сколько долго?

На риск быть очевидным, есть три широкие категории

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

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

Последняя категория сложна, поскольку мы знаем, что не можем предсказать будущие атаки. Некоторые вещи, которые были невозможны 10 лет назад, теперь заурядны. Один хороший совет: вещи, которые мы не храним, — это вещи, которые нам не нужно защищать. Держите как можно меньше, но не меньше.

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

По какой (чистой) стоимости?

«Стоимость» является гибкой концепцией, поскольку существует очень много косвенных и предполагаемых затрат. Например, какова стоимость того, чтобы людям было труднее выполнять свою работу … или это дешевле, поскольку система не будет работать в течение нескольких дней после нарушения? Какова стоимость людей, покидающих сайт в отчаянии, по сравнению с преимуществами людей, не покидающих сайт в массовом порядке после взлома вашего сайта, сделанного национальными новостями?

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

Положить его вместе

Суть в том, что модель угрозы в конечном итоге является бизнес-решением. Мы можем предоставить анализ и рекомендации, но окончательное решение должно прийти сверху.

Тем не менее, есть много вещей, которые мы можем сделать по собственной инициативе. Эта дискуссия будет касаться некоторых из них.

SQL-инъекция

Что такое SQL-инъекция?

SQL-инъекция — это способность злоумышленников вставлять произвольные команды SQL в вашу систему.

Образец атаки

Посмотрите на следующий код:

1
2
3
ResultSet rs = stmt.execute(
   'select * from users where username='' + username +
   '' and password='' + password + ''');

Что может пойти не так? Допустим, мы используем следующие значения:

1
2
String username = 'bob' or 1=1; --';
String password = 'dont care';

Когда мы вызываем предыдущий код, сгенерированный код

1
2
select * from users where username='bob' or 1=1; --'
   and password='dont care'

Это будет список всех пользователей. Некоторые веб-фреймворки будут перечислять всех пользователей в системе. Более тщательно написанные приложения вызовут тревогу, если будет возвращено более одной записи. Это легко исправить

1
2
String username = 'bob' or 1=1 order by userid limit 1; --';
String password = 'dont care';

производить

1
2
select * from users where username='bob' or 1=1
  order by userid limit 1; --' and password='dont care'

Раздел ‘order by’ гарантирует, что мы увидим первого пользователя в системе. Это обычно администратор — что-то злоумышленники не забывают.

Неправильный подход

Многие неопытные программисты пытаются обойти эту проблему путем явной очистки введенного пользователем ввода.

1
2
3
4
ResultSet rs = stmt.execute(
  'select * from users where username='' +
  username.replaceAll(''',   '''') +
  '' and password='' + password.replaceAll(''', '''') + ''');

Это могло бы сработать в 1980-х, но сегодня мир использует больше, чем ASCII. Правильное определение символов кавычек — нетривиальная проблема, и ее следует оставить другим. Авторы JDBC часто имеют для этого специфичные для базы данных методы, но они могут быть не синхронизированы с базой данных и, разумеется, специфичны для базы данных.

Подготовленные заявления и заполнители

Стандартное решение этой проблемы заключается в использовании подготовленных операторов и заполнителей. Это заменяет код

1
2
3
ResultSet rs = stmt.execute(
  'select * from users where username='' +
  username + '' and password='' + password + ''');

с участием

1
2
3
4
5
PreparedStatment stmt = conn.prepareStatement(
  'select * from users where username=? and password=? limit 1');
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.execute()

Ограничения

Есть моменты, когда подготовленные заявления неуместны. Одним из распространенных примеров являются операторы с несколькими вставками. Это может быть значительно быстрее, чем несколько подготовленных вызовов операторов.

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

1
2
3
4
5
6
insert into squares(x, y)
   values (1, 1),
          (2, 4),
          (3, 9),
          (4, 16),
          (5, 25);

Как правило, это не должно использоваться с данными, предоставленными пользователем. Если это абсолютно необходимо, используйте метод для конкретной базы данных, предоставленный вашим провайдером JDBC, а не свое собственное решение.

Анализ выгоды и затрат

Анализ затрат / выгод при использовании подготовленных заполнителей выписок не имеет значения — это одна из тех вещей, которые вам просто нужно сделать.

SQL-инъекция в хранимых процедурах

Что такое хранимые процедуры и CallableStatements?

Хранимые процедуры — это биты кода, хранящиеся в базе данных. Наиболее распространенной формой является SQL-подобный язык сценариев, но поддерживаются дополнительные языки — PERL, tcl, ruby, java и т. Д.

Важно помнить, что хранимые процедуры используются в триггерах базы данных — вы должны знать о них, даже если вы все работаете с Hibernate.

Неправильный подход

Неправильный подход заключается в создании динамического SQL-запроса без очистки.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
DELIMITER $$
DROP PROCEDURE IF EXISTS SP_AUTHENTICATE$$
CREATE PROCEDURE SP_AUTHENTICATE(IN username VARCHAR(20),
                                 IN password VARCHAR(20),
                                 OUT success INT)
BEGIN
  SET @query = CONCAT('SELECT COUNT(credentials.username) INTO @succ
     FROM credentials
     WHERE credentials.username = \'', username,
        '\' AND credentials.password = \'', password, '\'');
   PREPARE stmt FROM @query;
   EXECUTE stmt;
   SELECT @succ;
   SET success = @succ;
END;
$$
DELIMITER ;

(Примечание: этот фрагмент кода является примером из ссылки ниже.)

Эта хранимая процедура не имеет преимуществ перед «неправильным ответом», который мы видели ранее, за исключением очень скромной инкапсуляции.

Sidenote: Это пример оракула . Он возвращает минимальный объем информации об аутентификации пользователя — «большие пальцы» или «большие пальцы вниз». В этой реализации нет утечки информации, поскольку вызывающая сторона уже знает имя пользователя и пароль, но более надежная реализация также может проверить, что учетная запись пользователя не отключена и т. Д.

Хранимые процедуры и параметризация

Первый безопасный подход — это непосредственное выполнение SQL вместо создания динамического SQL.

Второй безопасный подход — параметризация в хранимой процедуре. Это прямо эквивалентно подготовленным Java операторам и заполнителям.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
DELIMITER $$
DROP PROCEDURE IF EXISTS SP_AUTHENTICATE$$
CREATE PROCEDURE SP_AUTHENTICATE(IN username VARCHAR(20),
                                 IN password VARCHAR(20),
                                 OUT success INT)
BEGIN
  SET @query = 'SELECT COUNT(credentials.username) INTO @succ
     FROM credentials
     WHERE credentials.username = ? AND credentials.password = ?';
   PREPARE stmt FROM @query;
   SET @usernm = username;
   SET @pass = password;
   EXECUTE stmt USING @usernm, @pass;
   SELECT @succ;
   SET success = @succ;
END;
$$
DELIMITER ;

PLPSQL Санитарная обработка

Существует другая альтернатива, если вы хотите быть привязанным к конкретному поставщику базы данных. На практике это обычно не проблема — гибернация обеспечивает некоторую прозрачность базы данных, но хранимые процедуры всегда будут тесно связаны с базой данных.

В plpsql (PostgreSQL) есть две команды, которые можно использовать для очистки ввода: quote_ident и quote_literal . Несомненно, есть подобные команды в других языках хранимых процедур.

Обновляя неправильный ответ выше, мы имеем:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
DELIMITER $$
DROP PROCEDURE IF EXISTS SP_AUTHENTICATE$$
CREATE PROCEDURE SP_AUTHENTICATE(IN username VARCHAR(20),
                                 IN password VARCHAR(20),
                                 OUT success INT)
BEGIN
  SET @query = CONCAT('SELECT COUNT(credentials.username) INTO @succ
     FROM credentials
     WHERE credentials.username = ', quote_literal(username),
        'AND credentials.password = ', quote_literal(password));
   PREPARE stmt FROM @query;
   EXECUTE stmt;
   SELECT @succ;
   SET success = @succ;
END;
$$
DELIMITER ;

Прямой SQL

Последний безопасный подход — использовать прямые вызовы SQL с минимальным размером параметра. Это упомянуто на веб-сайте CERT, но я не хотел бы использовать его, так как было бы легко ввести небезопасный код случайно.

01
02
03
04
05
06
07
08
09
10
11
12
DELIMITER $$
DROP PROCEDURE IF EXISTS SP_AUTHENTICATE$$
CREATE PROCEDURE SP_AUTHENTICATE(IN username VARCHAR(8),
                                 IN password VARCHAR(20),
                                 OUT success INT)
BEGIN
  SELECT COUNT(credentials.username) INTO success
     FROM credentials
     WHERE credentials.username = username AND credentials.password = password;
END;
$$
DELIMITER ;

Анализ выгоды и затрат

Хранимые процедуры сложнее использовать, чем голые запросы SQL, но это часто дает людям ложное чувство безопасности. Это следует считать обязательным для конфиденциальной информации (аутентификация пользователя, ведение журнала аудита) и настоятельно рекомендуется во всех других случаях.

Схема владения

Что такое DDL, DML, DCL и TCL?

SQL содержит четыре различных типа операторов.

Язык определения данных

Операторы языка определения данных (DDL) определяют структуру базы данных. Думайте об этом как о хозяине, который строит склад, но передает ключи арендатору.

Заявления:

  • создавать — создавать таблицы, представления, индексы и т. д.
  • изменить — изменить таблицы, представления, индексы, столбцы и т. д.
  • drop — удалить таблицы, представления, индексы и т. д.
  • усечь — удалить все записи из таблицы
  • comment — добавлять комментарии к таблицам, столбцам, представлениям и т. д.
  • переименовать — переименовать таблицу, просмотреть и т. д.

Язык манипулирования данными

Операторы Data Manipulation Language (DML) управляют данными в структуре, созданной DDL. Думайте об этом как о владельце склада — он может использовать склад, но не может разрушить стены.

Заявления:

  • выбрать — получить данные
  • вставить — вставить новые данные в таблицу
  • обновить — обновить существующие данные в таблице
  • удалить — удалить данные из таблицы
  • call — вызвать PL / SQL или другую хранимую процедуру
  • объяснить план — объяснить, как будет выполняться запрос
  • заблокировать таблицу — заблокировать таблицу для ограничения параллелизма

Язык управления данными

Операторы языка управления данными (CDL) управляют правами доступа к данным и схеме. Думайте об этом как о замках на дверях, разрешении перемещать стены внутри склада и т. Д.

Заявления:

  • Грант — дать пользователю дополнительные привилегии
  • отменить — удалить привилегии пользователя

Язык управления транзакциями

Оператор языка управления транзакциями (TCL) используется для управления транзакциями.

Заявления:

  • совершить — сохранить выполненную работу
  • откат — отменить выполненную работу
  • точка сохранения — отметьте точку, к которой мы можем вернуться позже, не обязательно откатывая всю транзакцию
  • установить транзакцию — установить параметры транзакции

Использовать другого пользователя базы данных для схемы и владения данными

Схема должна принадлежать одному пользователю базы данных, например app_owner, а данные должны принадлежать другому пользователю базы данных, например app_user .

Владелец должен:

  • иметь возможность запускать операторы DDL и DCL
  • возможно, не имеют возможности запускать операторы DML
  • никогда не будет доступен через веб-приложение

Пользователь должен

  • иметь возможность запускать операторы DML и TCL
  • не может запускать операторы DDL или DCL
  • быть доступным через веб-приложение

Анализ выгоды и затрат

Существует очень выгодное соотношение затрат и выгод для разделения владельца схемы и базы данных. Существует несколько более высокая стоимость при создании и обслуживании базы данных, но это существенно устраняет возможность веб-злоумышленника уничтожить саму схему базы данных. Данные, с другой стороны, все еще могут быть обнулены.

Информация об аутентификации и авторизации пользователя

Аутентификация пользователя ( authn ) — это то, как мы знаем, что пользователь — это тот, кем он себя считает. Как минимум, это имя пользователя и пароль, но они могут включать гораздо больше, если используется двухфакторная аутентификация.

Авторизация пользователя ( authz ) — это то, что мы разрешаем пользователю делать.

Это очень разные вопросы и должны рассматриваться как таковые. Некоторые архитектуры для этого, например, если сайт использует siteminder или аналогичный инструмент, он вообще не имеет доступа к информации authn — он может только добавить authz.

Что такое информация пользователя authn / authz? это

  • имя пользователя и / или адрес электронной почты
  • пароль
  • идентификаторы единого входа (SSO)
  • токены безопасности (для двухфакторной аутентификации)
  • защитные изображения / фразы (используются для подтверждения того, что ваш сайт является законным для пользователя)
  • группы и роли

Что не является информацией пользователя authn / authz?

  • контакты
  • подписка на контент
  • или что-либо еще, что не требуется для аутентификации или авторизации пользователя.

Неправильный подход

Поместите все — пользовательский auth / authz, статический контент и динамический контент — в единую схему базы данных.

Это быстро

Это просто.

Это поведение по умолчанию для инструментов автоматической генерации.

И это очень, очень неправильно, так как любой, кто взломает ваше веб-приложение, также взломал ваши данные пользователя auth / authz. В лучшем случае у вас будет отказ в обслуживании. В худшем случае они могут выдавать себя за других пользователей, могут добавлять свои собственные привилегированные учетные записи и т. Д.

Отдельные схемы и пулы соединений

Самое быстрое решение — создать отдельную схему для пользовательских данных authn / authz и использовать выделенный источник данных (или сеанс Hibernate) при доступе к этим данным. Эта схема должна быть нечитаемой из стандартного источника данных (или сеанса Hibernate). Это дает вам хороший брандмауэр от мира, но не идеален.

На первый взгляд более надежное решение — использовать отдельную базу данных, а не отдельную схему, для пользовательских данных authn / auhtz. Это может защитить вас от неверно настроенных прав, которые позволят источнику данных динамического содержимого получить доступ к источнику данных пользователя.

К сожалению, в некоторых RDMBS нет четкого различия между схемами и базами данных, и соединение с одной «базой данных» все еще может обращаться к другой «базе данных», если предоставлены необходимые права. Вы не можете быть уверены, если у вас нет отдельного экземпляра базы данных для пользователя authn / authz и динамического содержимого. Это не может быть чрезмерным бременем, если в вашей архитектуре есть сервер, выделенный для пользователя authn / authz. Это не лишено смысла с виртуальными серверами или облачным дизайном.

Контейнерная аутентификация

Лучшее решение — аутентификация на основе контейнеров. Полностью удалите пользователя authn / authz из веб-приложения — к тому времени, когда ваше веб-приложение получит запрос, в HttpServletRequest уже будет заполнена вся необходимая информация. Ваше веб-приложение не имеет доступа к информации аутентификации контейнера. (По модулю примечаний выше — вы ничего не получите, если контейнер смотрит на ту же схему, что и ваш динамический контент.)

Вариант этого — фильтры аутентификации, помещенные перед веб-приложением, например, из Spring Security. Это другой механизм, но он служит той же цели — проводить четкое различие между пользовательскими данными и динамическим контентом.

The Glitch — Добавление и обновление пользователей

Здесь есть один большой сбой — как вы добавляете или обновляете информацию о пользователе, если ваше веб-приложение не может получить доступ к таблицам authn / authz пользователя?

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

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

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

Ссылки по теме

https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=70288108

Справка: Безопасность баз данных и веб-приложений, часть 1: Модель угроз , безопасность баз данных и веб-приложений, часть 2: внедрение SQL , безопасность баз данных и веб-приложений, часть 3: внедрение SQL в хранимых процедурах , безопасность баз данных и веб-приложений, часть 4: владение схемой , база данных И Безопасность Webapp, Часть 5: Аутентификация пользователя от нашего партнера JCG Bear Giles в блоге Invariant Properties .