Последние несколько лет постоянно увеличивается число атак, основанных на внедрении SQL кода. Рост количества приложений, работающих с базами данных, как и числа различных публикаций (как в электронных, так и в печатных изданиях), которые описывают эту проблему и пути ее использования, приводит к осуществлению многочисленных атак подобного рода.
Авторы: Офер Маор Амичай Шалман Перевод: Альберт Умеров
Последние несколько лет постоянно увеличивается число атак, основанных на внедрении SQL кода. Рост количества приложений, работающих с базами данных, как и числа различных публикаций (как в электронных, так и в печатных изданиях), которые описывают эту проблему и пути ее использования, приводит к осуществлению многочисленных атак подобного рода.
Увеличение числа атак, основанных на внедрении SQL кода, приводит к тому, что делаются попытки найти решение этой проблемы. Самым очевидным решением, которое только может быть, является написание программ, основываясь на принципах безопасности. Множество опубликованных документов рассматривают разработку надежных веб-приложений с упором на доступ к базам данных, который до сих пор не претерпел значительных изменений. Обычно разработчики веб-приложений спокойны, не думают о безопасности, и поэтому проблемы возникают снова.
В результате, эксперты в области безопасности ищут меры, которые могут быть предприняты в отношении этой проблемы. К сожалению, общее решение этой проблемы сводится к подавлению вывода подробных сообщений об ошибках. С тех пор, как документы, описывающие внедрение SQL кода, основываются на извлечении информации из сообщений об ошибках (некоторые даже заявляют, что определенные задачи не могут быть решены без подробных сообщений об ошибках), эксперты в области безопасности веб-приложений пришли к выводу, что внедрение SQL кода не может быть осуществлено без анализа подробных сообщений об ошибках (или без непосредственно исходного кода).
Скрытие сообщений об ошибках является просто другой реализацией “Политики запутывания” (“Security of Obscurity”), которая за все время существования доказала свою некорректность. Цель этого документа – опровергнуть заблуждение о том, что внедрение SQL кода можно осуществить только при выводе подробных сообщений об ошибках, и показать простые способы, используемые нападающей стороной, если вывод подробных сообщений об ошибках не осуществляется. Эти способы можно объединить под общим названием “Внедрение SQL кода с завязанными глазами” (“Blindfolded SQL Injection”). Такое название отражает сознание того, что любой может попытаться использовать уязвимость внедрения SQL кода без подробных сообщений об ошибках. Оно также отражает то, что при правильном подходе легко осуществить внедрение SQL кода, даже если подробные сообщения об ошибках не выводятся.
Для того чтобы понять, с помощью чего это достигается, сначала мы покажем, как просто определить возможность внедрения SQL кода, основываясь на минимальной ответной реакции сервера. Затем мы перейдем к путям конструирования работающего SQL запроса, который впоследствии может быть заменен любым корректным SQL запросом. В конечном итоге, мы рассмотрим возможность использования SQL предложения UNION SELECT (которому часто придают большое значение при изучении атак, основанных на внедрении SQL кода), при отсутствии вывода подробных сообщений об ошибках. Использование способов описанных в документе основывается на нулевом знании исследуемого приложения, типа базы данных, структуры таблиц и т.д. Все эти знания могут быть получены нами в процессе внедрения SQL кода.
Мы надеемся прояснить то, что уязвимости уровня приложения должны быть обработаны соответствующим образом, и что бесполезно полагаться на защиту от внедрения SQL кода путем простого подавления вывода сообщений об ошибках.
Два важных замечания: во-первых, этот документ не является ни руководством по использованию уязвимостей, основанных на внедрении SQL кода, ни руководством по языку SQL. Этот документ не будет подробно рассматривать атаки, основанные на внедрении SQL кода, также предполагается, что читатель имеет общее представление о том, что представляют собой атаки, основанные на внедрении SQL кода, и хочет понять, как атаки подобного рода могут представлять опасность, если подробный вывод сообщений об ошибках не осуществляется. Второе замечание касается примеров, используемых во всем документе. Несмотря на то, что примеры относятся к серверам баз данных MS SQL и Oracle, эти же методы могут быть с успехом применены к другим серверам баз данных.
Для осуществления внедрения SQL кода, первым делом, очевидно, следует его распознать. Чтобы выполнить это, атакующий должен в первую очередь определить для себя относительный тип отображения ошибок в системе. Несмотря на то, что сами сообщения об ошибках не отображаются, само приложение должно обладать способностью отличать правильный запрос от неправильного. Этим может воспользоваться атакующий, и он без труда научится определять внешние проявления ошибок, находить их, и определять, связаны ли они с SQL или нет.
Во-первых, мы должны понять, с ошибками каких типов атакующий может столкнуться. Веб-приложения могут генерировать ошибки двух основных типов. Ошибки первого типа генерируются веб-сервером, в результате некоторого программного исключения. Если отображение таких ошибок не изменено, такие исключения сопровождаются сообщения вроде “500: Внутренняя ошибка сервера” (“500: Internal Server Error”). Обычно, внедрение синтаксически неправильного кода (например, незакрытые кавычки), должно заставить приложение вернуть ошибку такого типа, хотя и другие ошибки могут привести к такому же сообщению об ошибке. Простая техника подавления ошибок заключается в замене текста ошибки по умолчанию на пользовательскую HTML страницу. Но, если наблюдать непосредственно за ответом сервера, то можно обнаружить, что произошла скрытая ошибка сервера. В других случаях прилагается больше усилий при подавлении ошибок, и ошибочный ответ сервера может быть перенаправлением на главную/предыдущую страницу, или общим сообщением об ошибке, которое абсолютно бесполезно.
Ошибки второго типа генерируются кодом приложения, что обычно говорит о лучшем стиле программирования. Приложение готово к определенным некорректным ситуациям и генерирует для них специальные сообщения об ошибках. Обычно ошибки такого типа должны передаваться как часть правильного (200 ОК) ответа сервера, они также могут быть заменены перенаправлениями на другие страницы или сопровождаться другими методами сокрытия, как и “Внутренняя ошибка сервера”.
В качестве простого примера рассмотрим различия между двумя похожими приложениями электронной коммерции, назовем их А и Б. Оба приложения используют страницу, названную proddetails.asp. Этой странице передается параметр, названный ProdID. Она принимает ProdId и извлекает из базы данных подробную информацию о продукте, затем выполняет некоторые действия над записью и возвращает результат. Оба приложения вызывают proddetails.asp, используя ссылку, следовательно, ProdId должен все время принимать корректные значения. Приложение А довольствуется этим и не выполняет дополнительных проверок. Когда атакующий подменяет ProdId, вставляя значение ProdId, которому не соответствует ни одна строка в таблице, возвращается пустой набор данных. В то время, как приложение А не ожидает пустой набор данных, оно пытается обработать данные хранящиеся в записи, и вероятнее всего возникнет исключение, генерирующие сообщение об ошибке “500: Внутренняя ошибка сервера”. Приложение Б, тем не менее, проверяет, что размерность набора данных больше 0, перед выполнением любых операций с ним. Если это не так, появляется сообщение об ошибке “Такого продукта не существует”. Если же разработчик хочет скрыть ошибку, пользователь будет просто возвращен к списку продуктов.
Атакующий, пытающийся выполнить “Внедрение SQL кода с завязанными глазами”, в первую очередь попытается сформировать несколько некорректных запросов и определить, как приложение обрабатывает ошибки, и чего следует ожидать при возникновении ошибки SQL.
Вторая часть атаки заключается в нахождении местоположения ошибок. Теперь, зная приложение, атакующий может подменять входные данные и осуществить эту часть атаки. Для этого могут быть применены обычные техники внедрения SQL кода, такие как: добавление ключевых слов SQL (OR, AND и т.д.) и МЕТА символов (таких как ; или ‘). Каждый параметр проверяется в отдельности, при этом ответ сервера внимательно исследуется, для того чтобы обнаружить, когда же возникает ошибка. Используя перехватывающий прокси-сервер или любой другой из множества доступных инструментов, легко определить перенаправления и другие способы сокрытия ошибок. Каждый параметр, возвращающий ошибку, является подозрительным и может быть использован для внедрения SQL кода.
Как всегда, все параметры проверяются в отдельности, с упором на то, чтобы запрос оставался корректным. В этом случае очень важно как этот процесс должен нейтрализовать любую возможную причину ошибки, которая отлична от внедрения кода. Результатом этого процесса обычно является длинный список подозрительных параметров. Некоторые из этих параметров могут быть действительно уязвимыми к внедрению SQL кода, и их можно будет использовать. Другие параметры вызывают ошибки, которые не связаны с SQL, и поэтому могут быть отброшены. Поэтому следующим шагом для атакующего является определение параметров, которые можно отбросить, и оставить для рассмотрения параметры, действительно уязвимые к внедрению SQL кода,
Для того, чтобы лучше усвоить все это, важно понять основные типы данных, которые применяются в SQL. SQL поля обычно определяются одним из 3-х базовых типов: число, строка или дата. У каждого базового типа может быть много подтипов, но это не так важно для осуществления внедрения кода. Каждый параметр, передаваемый веб-приложением в SQL запрос, считается принадлежащим к одному из этих типов. Обычно очень просто определить тип параметра (‘abc’, очевидно, является строкой, в то время как 4 – числом, однако он с таким же успехом может считаться строкой).
В языке SQL числовые параметры передаются серверу без изменений, в то время как строковые или темпоральные выражения окружаются кавычками с обеих сторон. Например:
SELECT * FROM Products WHERE ProdID = 4
вместо
SELECT * FROM Products WHERE ProdName = 'Book'
Сервер SQL, однако, не беспокоится о типе передаваемых ему выражений, до тех пор, пока они соответствуют ожидаемому типу. Такое поведение представляет атакующему замечательную возможность определения, когда ошибка в действительности является ошибкой SQL. Для числового выражения простейшим путем определения этого является применение основных арифметических операций. Например, рассмотрим следующий запрос:
/myecommercesite/proddetails.asp?ProdID=4
Проверить этот запрос на возможность внедрения SQL кода очень просто. Первый способ – использовать 4’ в качестве параметра. Другой способ – использовать 3+1 в качестве параметра. Полагая, что этот параметр действительно передается в SQL запрос, результатом этих двух проверок будут следующие SQL запросы:
Первый запрос по определению должен сгенерировать ошибку, так как он синтаксически неправилен. Второй запрос, однако, выполниться без ошибок, возвращая такой же продукт, как оригинальный запрос (c ProdId=4) . Тем самым мы показали, что этот параметр действительно уязвим к внедрению SQL кода.
Похожая техника может быть использована для замены строкового SQL параметра. Но все же есть два различия. Во-первых, строковые параметры заключаются в кавычки, и необходимо их разорвать. Во-вторых, различные серверы SQL используют различный синтаксис для конкатенации строк. Например, Microsoft SQL Server использует знак + для конкатенации строк, в то время, как Oracle использует || для той же цели. После чего применяется похожая техника. Например: /myecommercesite/proddetails.asp?ProdName=Book
Проверим это запрос на возможность внедрения SQL кода, заменяя параметр ProdName первый раз синтаксически некорректной строкой, такой как B’, второй раз, создав правильное строковое выражение, такое как B’ + ‘ook (или B’ || ‘ook для Oracle). В результате получим следующие запросы:
И опять, первый запрос вызывает ошибку SQL, в то время как второй, вероятно, вернет такой же продукт, как оригинальный запрос со значением Book.
Так же можно использовать любое другое выражение для замены оригинальных параметров. Специфические системные функции могут быть использованы для возвращения числового, строкового или темпорального выражения. (Например, в Oracle функция sysdate возвращает выражение темпорального типа, в то время как в MS SQL Server используется функция getdate() для этой же цели). Подобным образом могут быть использованы и другие методики для определения возможности внедрения SQL кода.
Итак, мы показали, что определение возможности внедрения SQL кода является простой задачей, даже если подобные сообщения об ошибках не выводятся, что позволяет атакующему легко продолжить свою атаку.
После того, как атакующий определит возможность внедрения SQL кода, следующим его шагом будет попытка использовать эту уязвимость. Для этого атакующий должен суметь составить синтаксически корректное выражение, определить используемый сервер базы данных и создать требуемый ему эксплоит (эксплоит – программа, использующая существующую уязвимость).
Эта часть “Внедрения SQL кода с завязанными глазами” обычно требует наибольшего проявления хитрости и сноровки. Если исходные запросы просты, то получить запрос с корректным синтаксисом также просто. Однако, если исходный запрос является сложным, то для того чтобы разорвать его, потребуется совершить много проб и ошибок. В любом случае, для осуществления этого потребуется знание только нескольких базовых методик.
Основной процесс определения синтаксиса начинается с исследования стандартного предложения SELECT … WHERE, полагая, что уязвимые параметры находятся в предложении WHERE. Для того, чтобы надлежащим образом составить корректное выражение, у атакующего должна быть возможность добавить данные к исходному предложению WHERE так, чтобы были возвращены данные, отличные от предполагаемых. В простом приложении, эта “хитрость” обычно заключается в добавлении OR 1=1. Однако, во многих случаях этого недостаточно для создания успешного эксплоита. Так, часто требуется закрыть кавычки, если они были открыты в исходном запросе. Дополнительной проблемой может быть подделанный запрос, который будет причиной ошибки приложения, и которую будет невозможно отличить от ошибки SQL (например, если ожидается одна запись, то добавление выражения OR 1=1, заставит базу данных вернуть 1000 записей, что может быть причиной ошибки приложения).
Так как каждое предложение WHERE обычно является набором выражений, вычисляемых как истина (True) или ложь (False), объединенных логическими операторами OR, AND и круглыми скобками, то изучение возможности получения корректного синтаксиса, который разорвал бы скобки, и должным образом прервал бы запрос, осуществляется путем проб различных комбинаций. Например, добавление ‘AND 1=2‘ превратит все выражение в ложное, тогда как добавление ‘AND 1=2‘ не повлечет никакого эффекта, но только в случае, если оператор старшинства не применяется.
С внедрениями такого типа, простое изменение условия WHERE может быть достаточным. С другими видами внедрения SQL кода, такими как UNION SELECT внедрение и внедрение хранимых процедур, недостаточно просто изменить предложение WHERE. Выражение SQL в целом должно быть должным образом прервано, таким образом, чтобы можно было добавить дополнительный синтаксис, для чего может быть использована очень простая методика. После установления атакующим правильной комбинации выражений AND, OR, 1=2, и 1=1 может быть использован знак комментария SQL.
Этот знак, представляющий из себя два последовательных дефиса (--), указывает серверу SQL игнорировать остаток входной строки. Например, посмотрим на простую станицу аутентификации, принимающей имя пользователя (Username) и пароль(Password) в запрос, как показано ниже:
SELECT Username, UserID, Password FROM Users
WHERE Username = 'user' AND Password = 'pass'
Отправляя johndoe’ -- как имя пользователя, будет сгенерировано следующее условие WHERE:
WHERE Username = 'johndoe' --' AND Password = 'pass'
В этом случае не только синтаксис выражения правильный, но и аутентификация будет успешно пройдена. Однако, посмотрим на слегка измененное предложение WHERE:
WHERE (Username = 'user' AND Password = 'pass')
Обратите внимание на окружающие круглые скобки. С таким же внедрением кода ('johndoe' --), запрос не выполнится:
WHERE (Username = 'johndoe' --'AND Password = 'pass')
Этот запрос содержит незакрытые круглые скобки, и поэтому, он не может быть выполнен.
Этот пример показывает, как знак комментария может быть использован для определения того, правильно ли был разорван SQL запрос. Если после добавления знака комментария ошибки не возникает, это значит, что запрос разорван синтаксически корректно до знака комментария. В противном случае, потребуется осуществить дополнительную попытку.
Продолжение следуюет...
В Матрице безопасности выбор очевиден