OWASP TOP-10: практический взгляд на безопасность веб-приложений: №1 — инъекции

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

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

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

Все эти объекты, и не только эти, хранятся в таблицах, где каждая строка – 1 объект.

Например, объект «Клиент» может иметь следующий набор полей: id, имя, фамилия, e-mail, мобильный телефон, и храниться в таблице вида:

Таблица clients (Клиенты)
id
(идентификатор)
cl_name
(имя)
cl_sur_name (фамилия) e-mail cell
(мобильный)
other
(и т.п.)
1 Ivan Ivanov ivan@mail +70000000000 42
Суть взаимодействия пользователя с Web-приложением заключается в обмене запросами между браузером и сервером, где браузер отправляет серверу различные параметры и получает от сервера ответ.

Чтобы получить или изменить объекты (например, получить выписку по счету или обновить свою анкету) необходимо запросить ту или иную страницу на сервере (далее по тексту – скрипт). Web-приложения с этой целью используют формы или вызовы непосредственно URL скрипта .

Для обмена данными между браузером и сервером был разработан протокол HTTP. Данные передаются в виде так называемых HTTP-запросов, каждый из которых состоит из заголовка и тела запроса.

Для передачи данных из браузера пользователя на сервер по HTTP-протоколу в основном используются два метода — GET и POST (существуют еще методы PUT и DELETE, но они используются преимущественно в API).

При передаче данных методом GET, все параметры запроса передаются в URL страницы в заголовке HTTP-запроса, например:

http://simplethreats.ru/get_order?order=150

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

Обращения через GET или POST могут производиться к любым объектам, будь то записи в базе данных или же чтение файла (скажем, картинки или скрипта с диска). А так как объектов бизнес-логики в системе может быть очень много и они могут иметь самые различные типы, то и обращений по идентификаторам в рамках одного, даже простого Web-приложения может происходить множество.

С тем, каким образом данные попадают в Web-приложение мы определились, а что же происходит внутри?

Что происходит с параметрами из запроса в приложении


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

Web-приложения, как правило, строят SQL запросы, сочетающие код написанный разработчиком приложения, с параметрами переданными пользователем. Рассмотрим пример:

SELECT  title, text FROM news WHERE id=$id

В нашем примере, $id – параметр передаваемый пользователем (переменная часть запроса), в то время как остальная часть запроса статическая и была заложена разработчиком приложения. Наличие переменных пользовательских данных в статическом SQL запросе, делает весь запрос динамическим.

В чем суть инъекции?


В случае с SQL, вся суть инъекции в том, чтобы модифицировать параметры HTTP-запроса таким образом, чтобы исказить SQL запрос к базе данных и «подсунуть» его серверу под видом нормального. Это позволит злоумышленнику получить несанкционированный доступ к данным.

Предположим, у нас в базе данных есть таблица «transactions» с данными о транзакциях пользователей. Она содержит следующие поля:

  • «user_id» — Уникальный идентификатор пользователя
  • «date» — Дата
  • «amount» — Сумма
  • «description» — Назначение

Таблица transactions
user_id date amount description
10 2015-05-26 1000
11 2015-05-26 1500
12 2015-05-26 1300
n 2015-05-26 x
Предположим, что наш аккаунт, под которым мы находимся в приложении, имеет идентификатор 10 (user_id=10) и эти данные хранятся в сессии приложения (были записаны туда при авторизации пользователя).

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

Для того чтобы нам получить данные о своих транзакциях за 26 мая 2015г. в кабинете пользователя может использоваться страница со следующим адресом:

http://mybank.simplethreats.ru/transactions.jsp?date=2015-05-26

При вызове которого дата «2015-05-26» будет получена из GET-параметра в URL, а значение user_id будет получено из сессии приложения. На основе этих данных, для получения информации о транзакциях пользователя, приложение сформирует SQL-запрос:

SELECT * FROM transactions WHERE date = "2015-05-26" AND user_id = 10

Для тех, кто не знаком с синтаксисом SQL для лучшего понимания, разберем этот запрос.

Что в запросе


Запрос состоит из оператора SELECT (дословно с англ. – «ВЫБРАТЬ»), который используется для выбора данных из таблицы. Существуют еще операторы UPDATE, INSERT, DELETE, которые, как нетрудно догадаться из их названий, выполняют операции обновления, вставки и удаления строк соответственно.

Символ «*» означает, что мы выбираем все столбцы таблицы. Далее следует ключевое слово FROM (дословно – «ИЗ») с указанием имени таблицы, из которой производится выборка.

Далее идет ключевое слово WHERE (дословно с англ. «ГДЕ»), за которым следует часть, определяющая непосредственно условия выборки из таблицы.

После этого идут условия вида «название_поля = значение» разделенные ключевыми словами AND или OR, означающими «И» и «ИЛИ» соответственно.

Таким образом, переводя запрос с SQL на русский мы получим:
ВЫБРАТЬ все поля ИЗ таблицы transactions, ГДЕ поле date = "2015-05-26" И поле user_id = 10
При недостаточной проверке данных от пользователя, злоумышленник может внедрить в форму web-интерфейса приложения специальный код, содержащий кусок запроса к базе данных.  Эта опаснейшая уязвимость, позволит злоумышленнику читать/изменять/удалять информацию, которая для него не предназначена.

Эксплуатация


Как это работает? В рассмотренном примере, значение даты "2015-05-26" попадает в SQL-запрос из URL скрипта, и в целях рассматриваемого примера, никак не фильтруется приложением.

Если передать в параметр date вместе с датой 2015-05-26 еще немного символов:

2015-05-26" AND user_id=11 --

То вместо корректного:

SELECT * FROM transactions WHERE date ="2015-05-26" AND user_id = 10

Указанный запрос сформирует следующий SQL:

SELECT * FROM transactions WHERE date = "2015-05-26" AND user_id = 11 -- " AND user_id = 10 

Два минуса «--» в синтаксисе SQL означают начало комментария, поэтому все символы после «--» интерпретатором восприниматься не будут, и часть запроса, в которой осуществлялась проверка идентификатора пользователя будет отсечена сервером базы данных.

Фактически будет выполнен запрос:

SELECT * FROM transactions WHERE date = "2015-05-26" AND user_id = 11

В результате которого, мы получим информацию о транзакциях другого пользователя. А перебирая id – любого другого пользователя.

Какие бывают техники атак при SQL-инъекциях


Сообществом OWASP были описаны пять основных методов (техник) атак при SQL-инъекциях.

  • Оператор Union: подход может быть использован при наличии уязвимости в запросе SELECT, позволяющей объединить два запроса в один результат или набор результатов.

  • Логический метод: предполагает использование логического условия, либо условий, позволяющих достоверно определить истинность или ложность некоего предположения.

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

  • Метод с альтернативным каналом передачи данных: метод предполагает использование альтернативного канала передачи извлеченных данных (например через исходящее HTTP соединение с web-сервером)

  • Time delay: метод использует команды базы данных, например sleep для того чтобы определить задержу по условным запросам. Метод эффективен, когда нет возможности получить ответ от web-приложения (результат, ошибка)

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

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

  • Связанный: данные в результате инжектированного SQL запроса извлекаются тем же путем, которым был передан сам инжектированный запрос. Это самый прямолинейный вид атаки, в результате которого, запрошенные модифицированным запросом данные, отображаются непосредственно на странице web-приложения.

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

  • Дедуктивный или слепой: В результате SQL инъекции фактического извлечения данных не происходит, но злоумышленник может получить информацию, наблюдая за поведением web-сервера, в результате отправки серии специфических инжектированных SQL запросов.


Как защититься?


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

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

Как перейти к имплементации данной рекомендации, если у вас уже есть большое web-приложение, имеющее много различных точек входа?

Мы приведем некоторый системный подход к реализации защиты от инъекций баз данных. Чтобы свести к минимуму потенциальную угрозу от данного типа атак, нужно последовательно выполнить следующие шаги:

  • Определить все точки получения данных web-приложением извне, выписать все точки входа (URL), названия передаваемых параметров, протокол (HTTP/HTTPS) и метод их передачи – GET, POST, PUT или DELETE.

  • Определить тип данных для каждого параметра, ответив на вопрос — значение какого типа мы должны получить?

  • Реализовать проверку каждого параметра на соответствие типу.

  • Реализовать экранирование кавычек и других спецсимволов при помощи символа обратного слеша “\”. При этом если параметр в запросе заключен в одинарные кавычки, то именно одинарные кавычки должны быть экранированы и заменены на «\’», аналогично с двойными кавычками. При этом экранировать одинарные кавычки внутри двойных не надо и наоборот. Это в теории, на практике же, почти во всех популярных фреймворках и скриптовых языках есть встроенные функции для экранирования спецсимволов, как например mysqli_real_escape_string в php.

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

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

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

Какие еще бывают инъекции?


Помимо инъекций баз данных, атаке типа «инъекция» может быть подвержена любая другая среда, которая получает необработанные данные извне. Еще один распространенный случай – это инъекция командного интерпретатора операционной системы, так называемые «OS injections».

Рассмотрим такой пример из описания «Command injections» OWASP.

Есть некая социальная сеть, предоставляющая пользователям функционал загрузки фотографий и их последующего удаления. В ней есть некий скрипт, написанный на языке PHP, и он отвечает за удаление фотографий:

 <?php
    $file=$_GET['filename'];
    system("rm /var/www/user_photos/$file");
?>

 Типичный его вызов выглядит так:

http://mysocnet.simplethreats.ru/user_file_delete.php?filename=1246.jpg

И влечет за собой исполнение команды:

rm /var/www/user_photos/1246.jpg

Очень легко очистить всю директорию с приложением, передав такой запрос:

user_file_delete.php?filename=../ -rf

(на самом деле пробелы и некие символы закодируются при передаче в URL в нечто вида user_file_delete.php?filename=..%2F+-rf — но для наглядности, мы будем писать с пробелами и другими символами, не заворачивая их)

что вызовет исполнение:

rm /var/www/user_photos/.. –rf

и будет равносильно

rm /var/www/ -rf

то есть удалит (команда rf) все содержимое директории www, где хранятся файлы приложения, без подтверждения (параметр –f) и рекурсивно (параметр -r), т.е. со всеми вложенными директориями и их файлами. Участь — врагу не пожелаешь.

А можно не удалять файловую систему, и передав запрос вида:

user_file_delete.php?filename=12346.jpg && adduser ghost && echo ghostpass | passwd ghost –stdin

Злоумышленник выполнит команду:

rm /var/www/user_photos/12346.jpg && adduser ghost && echo ghostpass | passwd ghost –stdin 


И создаст для себя учетную запись для доступа на сервер.

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

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

Первая особенность — это конвейеры (pipelines, pipes, «пайпы»). Суть конвейеров заключается в возможности передачи вывода одной команды на ввод другой, если они разделены оператором «|».

Вторая особенность – возможность запускать комбинацию команд через логические операторы «&&» и «||». Оператор «&&» выполнит следующую указанную команду, в случае если предыдущая исполнена успешно и является неким аналогом логического «И» — выполнить команду один И команду два. Оператор «||» является аналогом логического «ИЛИ» и выполнит вторую команду только в случае если не была выполнена первая — выполнить команду один ИЛИ команду два.

Что дальше?


Разумеется, нами была рассмотрена лишь малая толика того, что можно отнести к разряду инъекций. Огромный пласт занимают так называемые DOM- или HTML-инъекции, благодаря которым становится возможным проведение такого вида атак как XSS.

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

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

Будьте бдительны!

Немного о SimplePay и авторах
Мы — Иван Притула и Дмитрий Агапитов, занимаемся разработкой решений, которые делают жизнь людей проще и комфортнее. Сегодня мы хотим представить один из наших новых сервисов – это платежный агрегатор SimplePay. Все что мы делаем продиктовано мучительной невозможностью мириться с несовершенством в целом, и несовершенством конкретных программных решений в частности. Именно в погоне за совершенством и рождаются наши продукты.

SimplePay — это современный высокотехнологичный агрегатор платежей. Компания создана в 2014г., зарегистрирована в г. Москве и ведет свою деятельность в соответствии с законодательством Российской Федерации. Наша основная задача, это обеспечение простой, удобной возможности организации приема платежей на интернет-сайтах компаний, вне зависимости от сферы деятельности, масштаба бизнеса и наличия подготовленного технического персонала.

Мы предлагаем следующие услуги:

  • Организацию приема платежей на Вашем сайте
  • Возврат средств покупателю
  • Выставление произвольного счета покупателю
  • Уведомления о платежах как на URL, так и по e-mail
  • Рекуррентные платежи
  • Псевдорекуррентные платежи во всех популярных платежных системах с кошельками

Короткая справка:

  • Банк эквайер: Промсвязьбанк
  • Платежи в пользу третьих лиц: РНКО РИБ
  • Юрисдикция: РФ, 161-ФЗ
  • Работа с нерезидентами: Нет
  • Собственный API: Да
  • Совместимые API: Да
  • CMS-модули: Да
  • Встроенные модули в сторонних системах: BG Billing, WP-shop
  • Переадресация сразу на ПС без промежуточной страницы: Да
  • API на возвраты: Да

SimplePay
16.69
Company
Share post

Comments 15

    +2
    На дворе точно 2015й год? Фигово вы пиарите свою контору, инфе миллион лет
      +3
      Уважаемый Scratch, мы «контору» не «пиарим» а делимся проблематикой.
      На дворе может быть хоть 2050 год, а целая масса сайтов по прежнему узявимы, как с этим быть?
        +1
        На самом деле в данной статье очень подробно и понятно описана проблема уязвимости SQL Injection. На мой взгляд для начинающих самое то. Хорошо было бы еще упомяннуть о подготавливаемых запросах, которые поддерживаются mysqli. А вот пиар в конце и правда лишний.
          0
          Серьёзно?

          Да статей тысячи!
          ТЫЦ
          На одном хабре их десятки
        +1
        Вы действительно считаете что экранировать кавычки это правильный совет? Сколько раз доводилось эксплуатировать инъекцию, где все параметры проходили через фильтры и все кавычки в них экранировались. Использование фреймворка спасает только при условии, что в механизме экранирования фреймворка нет уязвимостей и разработчик умеет пользоваться фреймворком, в противном же случае из одной проблемы в несколько строк можно сделать проблему в сотню тысяч строк. Тот же mysqli умеет prepared statement, но вы про них ни слова не сказали.
          +1
            0
            Ну, мы рассматриваем как это работает в общем виде. Безусловно, prepared statement — хороший и правильный вариант при проектировании новой системы. Если же говорить о приведении в соответствие уже имеющейся, дырявой системы — придется много кода переписать. Экранирование работает нормально, если сами параметры обрамлены в кавычки, как миниум. Также мы сказали, что важно проверять на соответствие типа, и в те SQL-выражения, где значение возможно использовать без кавычек (числа), стринги проходить просто не должны — ну или как минимум abs(intval($val)) должен будет превратить их в ноль.
              0
              Заменить обычный запрос на prepared в готовом проекте настолько же затратно по времени, как и дописать функции escape_string к параметрам, но зато меньше возможностей для ошибки и лучшая читаемость кода для аудиторов. Недостаточно экранировать только кавычки, ведь внутри них бэкслеш продолжает выполнять свою функцию и в случае, если пользователю будет подконтрольно два параметра из запроса, инъекция пройдёт.
                0
                В статье было сказано буквально: «Реализовать экранирование кавычек и других спецсимволов».
            +1
            Неправильно утверждать, что какой-то один способ борьбы с уязвимостями инъекций является единственно верным. Это зависит от класса уязвимости, типа прерываемого токена, семантики как кода приложения, так и запроса.

            Способов борьбы с инъекциями, как и со всеми остальными классами уязвимостей к атакам, основанными на контроле аргументов потенциально опасной операции извне, ровно три (в порядке предпочтения их использования):

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

            2. Санитизация. Удаление или экранирование символов, которые могут привести к успешной атаке (в случае с инъекциями — к выходу за пределы токена в интерпретируемом выражении). В плане инъекций требует точного знания грамматики островного языка (чтобы знать _как_ экранировать) и учета типа точки инъекции (чтобы знать _что_ экранировать или удалять), поэтому менее предпочтительна, чем типизация.

            3. Валидация. Проверка соответствия аргумента точки входа синтаксическим или семантическим правилам. Чем дальше от точки входа она реализована, тем сильнее ее влияние на поток управления, что делает ее менее предпочтительной.

            Для некоторых типов атак тот или иной способ борьбы может просто не существовать. Например, в случае переполнений буфера, если основной язык не поддерживает зависимые типы, из этих трех способов возможна только валидация. В том, что касается SQL-инъекций, типизация (т.е. параметризация запросов) никак не спасет от вставки в строковые литералы wildcard-символов (% и? в MSSQL), что может привести к возникновению логических уязвимостей (подробно рассказывал от этом на вебинаре: vkochetkov.blogspot.com/2013/07/blog-post.html). Типизация также ничем не поможет, если с помощью такого параметра планируется пробросить токен, не являющийся литералом и не всегда спасет от SQL-инъекции в хранимке, если там используется конкатенация ее аргументов с другим запросом. С помощью же санитизации это вполне можно решить, если нет возможности влезать в код хранимок.

            Как-то так =/
            0
              –1
              Рекомендация крайне проста – фильтровать входящие данные.… Иными словами, данные на входе нужно проверять на соответствие формату и выдать пользователю ошибку.

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

              Дорогие товарищи новички! Пожалуйста, не читайте эту статью — выкиньте советы из нее в корзину. Именно из-за таких вот неправильных «советов» потом и пишутся дырявые системы.

              С проблемами безопасности НИКОГДА нельзя бороться высокоэнтропийными средствами. Единственный способ по-настоящему защиться — это использовать автоматические инструменты, которым для своей работы не требуется информация о контексте (т.е. низкнтропийные). В данном случае к таким инструментам относится только один: использование placeholder-ов в запросе и передача переметров запроса отдельно:

              $result = query(«select * from tbl where id=?», $id);

              Через prepared statement это реализуется или же путем строкового квотинга + вклеивания параметров непосредственно внутрь запроса вместо "?" самой функцией query() — не так важно.

              Этого, конечно, недостаточно: нужно еще обеспечить, чтобы sql-запросы в коде перестали восприниматься внутри кода, как строковые литералы (по крайней мере, логически), и начали восприниматься, как литералы специального типа, не допускающие вклейку туда переменных методом «интерполяции». Как? Например, настроить code sniffer, который не позволил бы коммитить код, если в нем параметр query() поступает не в виде литерала в апострофах и без оператора конкатенации, а как-то еще. (Если уровень инженеров позволяет, то можно и на словах договориться, без code sniffer-а.)

              Ну или использовать ORM, в котором проблемы sql injection может не быть по определению (в хороших ORM так и есть).
                –1
                … да, почему я написал «смешалось»: потому что вопрос валидации данных + вывода пользователю внятных сообщений об ошибках и вопрос безопасности — это два совершенно разных и не связанных между собой вопроса. Первый — вероятностный, вопрос юзабилити, удобства пользования, интерфейса. Второй — вопрос жизнестойкости системы. Даже если вы на каждую попытку вместо числа вставить строку будете реагировать 500-й ошибкой, это не уменьшит безопасность системы. А вот если вы хотите эти 500-е ошибки скрыть за красивыми сообщениями пользователю, вот тут уже и используйте валидацию.

                Валидация не имеет никакого, я подчеркиваю, НИКАКОГО отношения к защите от инъекций (не важно, sql-инъекций, shell-инъекций, XSS и т.д. — это все едино). Поэтому статья, делающая из валидации и фильтрации инструмент защиты, вредна для новичков.

              Only users with full accounts can post comments. Log in, please.