Угроза, вызванная внедрением SQL, сильно недооценивается даже многими старшими разработчиками и архитекторами программного обеспечения. Большинство людей не знают о том факте, что целый сервер может подвергаться риску из-за одной уязвимости даже в самой отдаленной части логики. Эта статья даст пугающее понимание потенциальной серьезности уязвимостей SQL-инъекций.
Что такое SQL-инъекция?
Статья в Википедии об SQL-инъекциях гласит:
SQL-инъекция — это метод внедрения кода, используемый для атаки на приложения, управляемые данными, в которых вредоносные операторы SQL вставляются в поле ввода для выполнения.
Другими словами, если веб-сайт или какой-либо другой программный объект имеет уязвимость, злоумышленник может «внедрить» произвольные фрагменты кода SQL для выполнения на сервере. Прямой и популярный пример такой уязвимости — это когда динамический оператор SQL напрямую внедряет пользовательский ввод, такой как следующий код Java:
Переменная title может быть введена пользователем, например, в поле HTML. Ничто не препятствует тому, чтобы этот пользовательский ввод был фрагментом кода SQL, который имел бы синтаксический смысл. Например:
Легко видеть, что при подстановке в предыдущий оператор SQL этот «заголовок» всегда будет выбирать все фильмы. Другой известный пример того, как это может пойти не так, это знаменитая полоска Little Bobby Tables от xkcd .
Автоматизация поиска уязвимостей
Приведенное выше введение показывает, что внедрение SQL возможно и довольно просто использовать вручную, если исходный код на стороне сервера хорошо известен. Однако найти такую уязвимость в огромном приложении с тысячами операторов SQL — это большая работа. Кроме того, есть гораздо более тонкие уязвимости, чем очевидный поиск по шаблону. sqlmap — это лицензированный инструмент с открытым исходным кодом, GPLv2, для автоматизации таких поисков.
Давайте предположим, что у нас есть простое приложение RESTful, написанное на Java с использованием Jetty и JAX-RS ( исходный код доступен на GitHub ), запрашивающее базу данных примера MySQL Sakila , которая раскрывает вышеуказанную уязвимость по этому URL:
1
|
http: //localhost:8080/sql-injection-examples/vulnerable-services/films/alien |
Alien — это значение @PathParam, переданное этому методу, который возвращает все фильмы с названием% alien%:
Теперь давайте загрузим sqlmap и дадим ему возможность работать с вышеуказанным URL:
1
2
|
python sqlmap.py -u localhost:8080 /sql-injection-examples/vulnerable-services/films/ |
Обратите внимание, что sql Карта реализована в Python 2.x. Когда мы позволяем это выполнить, мои журналы SQL на стороне сервера показывают мне, что выполняется несколько интересных запросов SQL:
sqlmap пытается внедрить всевозможные фрагменты кода, которые помогут ему определить, является ли уязвимый запрос детерминированным, является ли URL-адрес стабильным, какой это тип сервера базы данных, если уязвимость находится внутри подзапроса, можно ли добавлять предложения UNION и т. д. Это также хорошо отображается в выходных данных журнала sqlmap stdout:
В общей сложности 59 HTTP-запросов (из которых 41 привел к ошибкам HTTP 500), sqlmap была способна обнаружить природу уязвимости моего оператора SQL, а также выяснила сервер базы данных и версию.
Сброс данных
Это пока не так впечатляет. С большим знанием SQL и креативностью, я мог бы понять это сам. Но sqlmap предлагает много других интересных режимов работы, которые можно увидеть в руководстве пользователя . Давайте использовать инструменты навигации по базе данных. Для этого мы добавим параметр -dbs в тот же URL:
1
2
3
|
python sqlmap.py -u localhost:8080 /sql-injection-examples/vulnerable-services/films/ --dbs |
Это немедленно сбросит следующие базы данных:
Обратите внимание, что sqlmap регистрирует все, что изучает, в локальной базе данных SQLite, так что он может поддерживать минимальное количество HTTP-запросов. Это важно для злоумышленника, поскольку частые состояния HTTP 404 или 500 в конечном итоге привлекут внимание обслуживающего персонала на стороне сервера.
Но как он нашел реальные имена баз данных через мой уязвимый оператор SQL? В три этапа:
- Он отправил первое заявление, чтобы проверить, существует ли уязвимость
- Он отправил второе заявление, чтобы увидеть, сколько существует баз данных (8)
- Он отправил третье заявление, чтобы узнать имя каждой базы данных
Давайте посмотрим на шаг 2. Выполнен следующий запрос:
Поскольку ранее sqlmap обнаружил, что моя наивная серверная реализация просто сбрасывает в стек трассировки ошибок HTTP 500, он знал, что может сгенерировать следующее сообщение об ошибке MySQL и передать интересующую информацию, скрытую внутри этого сообщения:
1
2
|
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'qnfwq8qdmiq1' for key 'group_key' |
Функция concat SQL заключает число 8 в две уникально идентифицируемые строки. С подобным запросом и предложением MySQL LIMIT имена баз данных могут быть извлечены одно за другим, например, база данных Sakila:
1
2
|
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'qnfwqsakilaqdmiq1' for key 'group_key' |
Опять же, информация, представляющая интерес, заключена в разделители.
Сброс данных без следов стека
Очевидно, что сбросить следы стека в продуктивной системе — это действительно плохая идея, так как вы никогда не должны давать злоумышленнику никаких подсказок о том, как работает ваша система. Давайте попробуем изменить наше приложение так, чтобы оно отображало простое сообщение 500 Internal Server Error:
Нет проблем для sqlmap. Когда я повторно запускаю свою предыдущую команду дампа базы данных, sqlmap генерирует все имена баз данных по буквам. Теперь, когда у него нет способа генерировать сообщения об ошибках в HTTP-ответах, он может только выполнить двоичный поиск по каждой букве схемы и посмотреть, производит ли какой-либо заданный поиск обычный список фильмов Сакилы или пустой список:
Приведенный выше запрос проверяет, имеет ли первая схема (LIMIT 0, 1) букву выше, чем код ASCII 120 (ORD) в позиции 13 (MID). Это намного медленнее и более заметно в журналах сервера, чем раньше, но все же может дать результат.
Дамп данных без отображения вывода интерфейса
Что если уязвимость глубоко в нашем приложении и никогда не приводит к выводу пользовательского интерфейса? Давайте снова изменим наш сервис, выполняя уязвимый запрос, но не возвращая никаких данных:
Мой файл журнала показывает, что sqlmap выполняет гораздо больше операторов SQL, и даже подтверждение наличия уязвимости по указанному URL-адресу занимает больше времени, поскольку sqlmap теперь использует бинарный поиск с использованием задержек. Вот как sqlmap подтверждает наличие уязвимости:
Как можно видеть, вышеупомянутые запросы выполняются в течение N или N + 5 секунд, где N — количество времени, которое занимает фактический запрос. Если N достаточно стабильно, двоичные поиски на основе задержек могут выполняться для обнаружения имен базы данных буква за буквой.
Сброс конфиденциальных данных
Предыдущие разделы показали, что уязвимость почти всегда может быть использована автоматически. У злоумышленников есть время. Они могут запустить свой скрипт в течение недели, чтобы sqlmap выгрузил всю вашу схему базы данных. Но на этом угроза не заканчивается. Как только имена таблиц и столбцов станут доступны, все эти таблицы также могут быть выгружены. Давайте свалим стол с фильмами:
1
2
3
|
python sqlmap.py -u localhost:8080 /sql-injection-examples/vulnerable-services/films/ --dump –T film |
Это занимает очень много времени, поскольку каждая строка и каждый столбец выбираются по крайней мере с помощью одного запроса. Но в итоге я получаю хороший CSV-файл с полным содержимым. Нет необходимости говорить, что это может быть информация о кредитной карте вашего приложения или другие конфиденциальные или секретные данные.
Произвольный запрос
Вы думаете, вы заметите, если кто-то сбросит всю вашу схему? Это не обязательно для злоумышленника. Помните, они уже смогли извлечь схемы, имена таблиц и столбцов. Теперь они могут выборочно выполнять произвольные запросы как таковые:
1
2
3
4
5
6
7
8
|
python sqlmap.py -u localhost:8080/sql-injection-examples/vulnerable-services/films/ --sql-query=”SELECT a.first_name, a.last_name, count(*) FROM film f JOIN film_actor fa USING (film_id) JOIN actor a USING (actor_id) GROUP BY actor_id, a.first_name, a.last_name ORDER BY count (*) DESC LIMIT 1” |
Приведенный выше запрос найдет актера, сыгравшего в большинстве фильмов:
Не все запросы синтаксически внедряются во все уязвимости во всех СУБД. Но пример Little Bobby Tables — это не то, что нужно большинству злоумышленников: нанесение ущерба вашей системе. Большинство злоумышленников не хотят, чтобы их видели, чтобы иметь возможность незаметно украсть ваши данные.
Другие возможности sqlmap
sqlmap на этом не останавливается. sqlmap позволяет переключать пользователей базы данных, вычисляя пароли root или DBA с помощью грубой силы. Как только вы получите доступ к пользователю или роли с более высокими грантами и в зависимости от реальной СУБД, sqlmap также позволит вам:
- Загрузите и выполните сценарий SQL
- Внедрение и выполнение пользовательских функций, определенных пользователем
- Чтение или запись файлов в файловой системе сервера базы данных
- Откройте оболочку в операционной системе сервера базы данных
- Управление реестром Windows сервера базы данных
На менее серьезном замечании, sqlmap может быть отличным инструментом администрирования сервера баз данных, если вы забыли учетные данные своей локальной среды разработки баз данных!
противодействия
В принципе, практически невозможно полностью предотвратить уязвимости, связанные с SQL. SQL — это динамически интерпретируемый язык, и поэтому он гораздо более уязвим для внедрения кода, чем статические языки, такие как Java. В частности, он чрезвычайно уязвим, поскольку обычной практикой является выполнение динамического SQL на основе критериев пользовательского ввода, таких как критерии поиска. Очевидно, что другие интерпретируемые на стороне сервера языки одинаково уязвимы, но SQL также оказывается одним из самых популярных.
Однако принятие правильных мер очень затруднит злоумышленнику фактическое обнаружение уязвимости и ее использование без предварительного уведомления об их действиях. Помните, что чем меньше информации вы отображаете, тем дольше выполняется sqlmap и тем больше трассировок он оставляет в журналах вашего сервера, чтобы даже проверить наличие уязвимости.
Вот несколько мер, которые вы должны соблюдать и применять в своей команде:
Никогда не доверяйте пользовательскому вводу
Первое и самое важное: никогда не доверяйте пользовательскому вводу. Даже если ваше приложение используют только 10 пользователей из одной компании через интрасеть, ваша база данных может содержать зарплаты или другие конфиденциальные данные, и злоумышленник может сбросить такие данные.
Обратите внимание, что пользовательский ввод не следует доверять и в других сценариях, например, при доступе к файловой системе.
Используйте как можно меньше пользовательского ввода
Вы должны не только не доверять пользовательскому вводу, вы также должны использовать как можно меньше пользовательского ввода непосредственно в своем SQL. Например, если пользователи могут сделать выбор в раскрывающемся списке HTML. Вместо простого встраивания значения параметра HTTP POST в оператор SQL, сначала проанализируйте его и инкапсулируйте в соответствующий предопределенный тип. В Java хорошим соответствием для параметров HTML может быть тип enum.
Вы лучше знаете свои данные, и, следовательно, вы должны проверить все пользовательские данные на сервере, сразу после их получения.
Используйте переменные связывания
Постарайтесь выражать свои операторы SQL как можно более статически. Конкатенация строк SQL позволяет начинающим разработчикам легко совершать ошибки. Если вы руководитель команды инженеров, вы обязаны помочь членам вашей команды избежать подобных ошибок.
Самый простой способ предотвратить внедрение SQL — это использовать переменные связывания. Драйверы JDBC (если вы работаете с Java) и базы данных имеют очень мало ошибок в этой области, так что потоковое связывание переменных с базой данных не вызовет какой-либо легко уязвимой уязвимости.
Используйте инструменты статического анализа кода
Если вы используете Java и JDBC напрямую, вы можете обнаружить некоторые уязвимости с помощью инструментов статического анализа кода, таких как FindBugs ™ или Alvor . Очевидно, что такие инструменты могут обнаруживать внедрение SQL, только когда операторы SQL являются «относительно статичными». Если вы распределяете генерацию строки критериев SQL по нескольким модулям кода, инструмент статического анализа кода не сможет обнаружить уязвимости.
Используйте sqlmap
sqlmap не обязательно является инструментом для вредоносных действий. Это бесплатно и с открытым исходным кодом, доступным по лицензии GPLv2. Вы можете использовать его в своих тестах непрерывной интеграции для обнаружения регрессий внедрения SQL. Поскольку вы очень хорошо знаете свое собственное программное обеспечение, вы можете настроить sqlmap с различными параметрами, которые обеспечат ему «преимущество», поскольку вам не нужно будет выяснять, работаете ли вы на MySQL, PostgreSQL, Oracle и т. Д.
Применить абстракцию SQL
В дополнение к очень низкоуровневому API (JDBC в Java, нативным функциям в PHP и т. Д.), Большинство платформ также имеют различные высокоуровневые API, которые абстрагируют язык SQL не на основе строк. Однако не думайте, что абстракция языка SQL сама защищает вас от внедрения кода. Популярные платформы, такие как Hibernate или JPA, используют языки запросов на основе строк, такие как HQL или JPQL. Эти языки менее уязвимы, чем SQL, потому что они менее выразительны. Но они все еще уязвимы!
В .NET примером нестроковой абстракции SQL является LINQ-to-SQL . В Java примерами являются SQLJ , JPA CriteriaQuery или jOOQ . Наш предыдущий пример запроса будет переводиться на эти:
Помимо того, что использование SQL-инъекций намного безопаснее за счет принудительного использования переменных связывания, статически типизированные внутренние специфичные для домена языки также помогают предотвратить синтаксические ошибки. В случае jOOQ это дополнительно поддерживается генератором исходного кода jOOQ, который будет генерировать литералы Java для таблиц и столбцов.
Тщательно продумайте базу данных ГРАНТЫ и политики безопасности
Не просто слепо используйте root-пользователя MySQL без разумного пароля для всего (или пользователя postgres в PostgreSQL, или пользователя dbo в SQL Server и т. Д.). Создайте хорошо разработанную стратегию безопасности, в которой пользователям предоставляется доступ только к тем элементам, к которым им действительно необходим доступ.
Часто хорошей идеей также является добавление дополнительного уровня косвенной защиты в вашей базе данных через представления базы данных. Например, веб-пользователям предоставляется доступ к нескольким представлениям только для чтения, а не к таблицам напрямую. Это позволит вам динамически ограничивать, кто может манипулировать какими данными.
Эта мера не предотвратит внедрение SQL, но минимизирует возможный ущерб, который может нанести злоумышленник, проникнув в вашу систему.
Используйте брандмауэры
Если вы разрабатываете веб-приложение, вы можете выбрать один из множества брандмауэров, которые имеют некоторые функции защиты от SQL-инъекций. Помимо простого сопоставления с шаблоном на основе регулярных выражений (которое на самом деле не является надежным или полезным), такой сервер ввода также должен поддерживать две очень мощные функции, которые помогут вам избежать ошибок при приеме ввода пользователя через параметры HTTP GET и POST:
- Шифрование URL-адресов. Все URL-адреса приложений зашифрованы и никогда не передаются клиенту. Таким образом, невозможно изменить параметры GET. Даже невозможно распознать параметры GET. Недостатком этой функции является тот факт, что может быть довольно сложно реализовать современное многофункциональное интернет-приложение на основе JavaScript, поскольку … вы не можете изменять параметры GET.
- Защита формы: все допустимые значения, которые можно выбрать в форме, известны серверу ввода и проверены с использованием зашифрованного хэша. Это делает невозможным вмешательство в значения HTML <select>, <input type = ”hidden”>, <input type = ”checkbox”>, <input type = ”radio”>. Очевидно, что «угрожающий» пользовательский ввод все еще может происходить из обычных значений <input type = ”text”> или <textarea>.
Пример сервера входа, реализующего вышеупомянутое, является Airlock швейцарской компанией Ergon Informatik AG .
Другие меры
Много исследований было сделано на тему контрмер против внедрения SQL. Интересные публикации для чтения:
- «Классификация атак и контрмер SQL-инъекций» Уильяма Дж. Дж. Халфона, Джереми Виегаса и Алессандро Орсо из Технологического института Джорджии
- «Повышение безопасности веб-приложений: угрозы и меры противодействия» Дж. Д. Мейера, Алекса Макмана, Майкла Даннера, Срината Васиредди, Рэя Эскамильи и Ананды Мурукана из корпорации Microsoft
- «Шпаргалка по предотвращению инъекций SQL» OWASP
- «SQL-инъекция» Консорциума по безопасности веб-приложений
- «SQL-инъекция» из Википедии (с множеством ссылок)
Вывод
Даже с недавними альтернативными моделями хранения и доступа к данным, которые стали называться NoSQL, SQL как язык запросов и взаимодействия с базами данных по-прежнему остается трудным. Крупные поставщики SQL внедряют все более и более качественные функции в стандарт SQL. SQL здесь, чтобы остаться.
Тем не менее, многие команды разработчиков не знают о масштабах этой угрозы. В миллионах строк кода приложения может быть достаточно, если вредоносная сущность, работающая с автоматизированными инструментами, такими как sqlmap, обнаружит одну уязвимость SQL. Такое лицо может быть в состоянии извлечь все данные из вашей базы данных и / или выполнить вредоносный код на ваших серверах. Это может привести к захвату сервера.
Ответственность за минимизацию риска создания уязвимостей SQL-инъекций несет архитектор или технический руководитель, так как даже опытные разработчики могут случайно создать такую уязвимость. Самый простой и эффективный способ — использовать везде, где это возможно, переменные связывания и статический SQL. Более сложные контрмеры могут быть достигнуты путем большого количества обучения, проверки, тестирования и использования серверов входа.