Этот документ является итогом исследований относительно возможности использования внедрения SQL кода, если вывод подробных сообщений об ошибках не осуществляется. Это исследование было выполнено для того, чтобы доказать клиентам, что простое подавление вывода сообщений об ошибках возвращает их к подходу “Политики запутывания”, не является достаточным. Используя методики, описанные в этом документе можно доказать уязвимость многих приложений, несмотря на все попытки скрыть информацию, возвращаемую клиенту приложения.
Авторы: Офер Маор Амичай Шалман Перевод: Альберт Умеров
Определение сервера базы данных
Непосредственно перед осуществлением внедрения SQL кода, атакующий должен предпринять следующие шаги, для того чтобы определить тип используемой базы данных. К счастью (для атакующего) это является более простой задачей, чем получение корректного синтаксиса.
Несколько простых уловок позволят атакующему определить тип используемой базы данных. Все эти уловки построены на определении различий, которые существуют между конкретными реализациями СУБД. Нижеследующие примеры обращают внимание на различие между СУБД Oracle и Microsoft SQL Server. Похожие методики, однако, легко можно использовать для определения других типов серверов баз данных.
Очень простой уловкой, упоминавшейся раннее, является различие в синтаксисе, используемом при конкатенации строк. Полагая, что синтаксис известен, и атакующий имеет возможность добавлять дополнительные выражения к условию WHERE, простое сравнение строк можно выполнить, используя, например, такую конкатенацию:
AND ’xxx’ = ’x’ + ’xxx’
Заменив + на || мы легко сможем отличить Oracle от MS SQL Server или другой базы данных.
Другая методика основана на применении символа точки с запятой. В SQL, точка с запятой используется для объединения нескольких различных SQL выражений в одной строке. При внедрении SQL кода это используется непосредственно для внедрения кода, но драйверы СУБД Oracle не позволяют таким образом использовать точку с запятой. Полагая, что комментарий корректно внедрен (т.е. не вызывает ошибки), добавление точки с запятой перед ним не окажет никакого влияния на MS SQL Server, но тем не менее вызовет ошибку при использовании Oracle. Также, мы можем проверить возможность выполнения дополнительных команд, записанных после точки с запятой, просто добавив оператор COMMIT (например, внедряя строку xxx’ ; COMMIT--). Выполнение запроса без ошибок, говорит о возможности внедрения дополнительных команд.
В конечном счете, некоторые выражения могут быть заменены системными функциями, которые возвращают корректное значение. Так, каждый сервер баз данных использует немного различающиеся функции, что позволяет легко определить тип используемого сервера баз данных этим способом (как, например, использование вышеупомянутых темпоральных функций getdate() в MS SQL Server вместо sysdate в Oracle).
Осуществление внедрения
Теперь, имея на руках всю необходимую информацию, атакующий может перейти непосредственно к внедрению SQL кода. В процессе построения кода эксплоита, атакующий больше не нуждается в информации сообщений об ошибках, и он может конструировать эксплоиты, основываясь на всех известных методиках внедрения SQL кода.
Этот документ не обсуждает обычные техники внедрения SQL кода, так как они уже подробно обсуждались в других документах. Далее в этом документе будут рассмотрены только методики внедрения предложения UNION SELECT, которые описаны в следующем разделе.
Внедрение выражения UNION SELECT
Хотя подделка предложений SELECT … WHERE может быть очень желанной во многих приложениях, атакующие часто хотят иметь возможность осуществить UNION SELECT внедрение. В отличие от подделки предложения WHERE, успешное осуществление UNION SELECT внедрения открывает атакующему доступ ко всем таблицам БД в системе, доступ к которым иначе был бы невозможен.
Так как выполнение предложения UNION SELECT требует как знания количества полей в запросе, так и типа каждого поля, поэтому часто считается невозможным осуществить его без подробных сообщений об ошибках, особенно когда число полей в исходном запросе велико. Нижеследующий раздел призван опровергнуть это заблуждение, путем приведения набора очень простых методик, решающих эту проблему.
Очевидно, что перед осуществлением этого, атакующий должен получить корректный синтаксис выражения. Предыдущие разделы, однако, уже сделали это осуществление возможным, и показали возможность его осуществления. Одно важное замечание относительно синтаксиса — определение синтаксиса предложения UNION SELECT должно быть на таком уровне, чтобы были расставлены все скобки, и можно было свободно внедрить UNION предложение или другие команды. (Как бы описано ранее, это может быть проверено путем добавления знака комментария к внедрению).
После получения корректного синтаксиса, предложение UNION SELECT может быть добавлено к исходному запросу. В предложении UNION SELECT должно присутствовать такое же число столбцов, и все типы столбцов должны быть такого же типа, как и в исходном запросе, в противном случае возникнет ошибка.
Подсчет количества столбцов
При использовании обычной методики внедрения SQL кода ‘UNION SELECT’ подсчет количества столбцов может показаться почти невыполнимой задачей. Это шаг не требуется выполнять, если выводятся подробные сообщения об ошибках — мы просто создаем ‘UNION SELECT’ внедрение и пробуем разное количество полей (на одно больше в каждой следующей попытке). Появление ошибки ‘column type mismatch’ вместо ‘column number mismatch’ свидетельствует о том, что количество полей определено и этот шаг выполнен. Когда вывод подробных сообщений об ошибках отсутствует, у нас нет абсолютно ни каких сведений о типе ошибки, и поэтому этот способ является полностью бесполезным.
Вместо этого должны быть использованы другие методики для определения количества столбцов, и эта техника основана на форме условия ORDER BY. Добавление условия ORDER BY в конец предложения SELECT меняет порядок вывода результатов запроса в наборе данных. Обычно это осуществляется путем указания имени столбца для сортировки (или имен нескольких столбцов для сортировки).
Рассмотрим на пример финансового запроса, синтаксически корректном внедрением в параметр содержащий номер кредитной карты будет 11223344) ORDER BY CCNum --, в результате получим следующий запрос:
SELECT CCNum FROM CreditCards
WHERE (AccNum=11223344) ORDER BY CCNum --
AND CardState='Active') AND UserName='johndoe'
Обычно выпускают из вида факт, что условие ORDER BY может быть задано в числовой форме. В этом случае выражение указывает на номер столбца вместо указания его имени. Это значит, что внедрение 11223344) ORDER BY 1 -- будет также синтаксически корректным, и будет в точности таким же, так как поле CCnum является первым полем в результате запроса. Внедрение 11223344) ORDER BY 2 -- вызовет ошибку, так как в запросе присутствует только одно поле и, значит, результаты запроса не могут быть отсортированы по второму полю.
Следовательно, использование условия ORDER BY может быть очень полезным при подсчете количества полей в запросе. Сперва атакующий добавляет условие ORDER BY 1 к определенному базовому синтаксису. Так как каждый SELECT запрос должен содержать по меньшей мере одно поле, он должен корректно выполниться. Если на этом этапе возникает ошибка, синтаксис выражения должен быть скорректирован, чтобы ошибка больше не возникала. (Несмотря на то, что это маловероятно, условие сортировки может вызывать ошибку приложения. В этом случае добавление ASC или DESC может решить эту проблему). Получив корректный синтаксис содержащий условие ORDER BY, не вызывающее ошибки, атакующий меняет порядок сортировки со столбца 1 до столбца 100 (или 1000, или любого другого числа, так точное указание числа будет некорректым). На данном этапе должна генерироваться ошибка, указывающая, что установление количества полей в запросе осуществлено.
Теперь у атакующего есть методика определения какие столбцы существуют, а какие — нет, и он легко может определить точное количество столбцов. Атакующему просто необходимо увеличивать это число, до тех пор, пока не возникнет ошибка (так, как поля некоторого типа не могут быть отсортированы, всегда полезно проверить одно или два дополнительных числа, чтобы удостоверится, что ошибки действительно возникают). Использование этой методики позволяет легко установить количество столбцов в запросе, и при этом не требуется вывод сообщений об ошибках.
Определение типов столбцов
Итак, с известным корректным синтаксисом, производителем СУБД и количеством столбцов в запросе, атакующий может определить типы всей полей.
Правильное определение типов полей также может быть трудным. Они должны соответствовать всем полям исходного запроса. Если в запросе мало полей, это можно осуществить путем прямого перебора, но если полей много — возникает серьезная проблема. Как уже было упомянуто, существует 3 возможных базовых типа (число, строка, дата), так наличие 10 полей соответствует 310 (около 60000) комбинаций. При выполнении 20 запросов в секунду, это займет почти час. Наличие большего числа полей делает этот процесс в целом почти неосуществимым.
Поэтому должна использоваться простая методика, если подробные сообщения об ошибках не выводятся. Она основана на использовании ключевого слова NULL, используемого в языке SQL. В отличии от внедрения статических полей, которые относятся к определенному типу (такому как строка или число), NULL может быть совместимым с любым типом. Это делает возможным внедрить предложение UNION SELECT с полями имеющими значение NULL, и при этом не возникнет ошибки несоответствия типов. Посмотрим на запрос, похожий на упомянутый в предыдущий примере:
SELECT CCNum, CCType,CCExp,CCName FROM CreditCards
WHERE (AccNum=11223344 AND CardState='Active') AND UserName='johndoe'
Его отличие состоит в том, что одиночное поле CCnum заменено несколькими полями, и их может быть еще больше. Полагая, что атакующий успешно определил количество столбцов в результате запроса (4 в нашем примере), можно легко внедрить предложение UNION со всеми значениями содержащими NULL и получить условие FROM, которое не вызывает ошибок связанных с разрешениями (и опять, пытаемся отделить каждую задачу, так и результаты, вызванные особыми разрешениями будут обработаны позже). При использовании MS SQL, условие FROM может быть просто опущено. И такой синтаксис будет являться правильным. При использовании Oracle полезно использовать таблицу с именем dual. Добавление предложения WHERE, которое всегда ложно (например, WHERE 1=2) гарантирует, что не будет возвращен набор данных содержащий только NULL-значения, что исключит в дальнейшем возможные ошибки (некоторые приложения могут некорректно обрабатывать NULL-значения).
Теперь давайте посмотрим на пример составленный для MS SQL Server, хотя такой же будет применим и к Oracle. Внедряя 11223344) UNION SELECT NULL, NULL, NULL, NULL WHERE 1=2 --, получим в результате следующий запрос:
SELECT CCNum, CCType,CCExp,CCName FROM CreditCards
WHERE (AccNum=11223344) UNION SELECT NULL,NULL,NULL,NULL
WHERE 1=2 --AND CardState='Active') AND UserName='johndoe'
Такой тип внедрения NULL-значений преследует две цели. Главной целью является получение работающего предложения UNION, не содержащего ошибок. Хотя этот UNION не извлекает никаких реальных данных, он обеспечивает отображение того, что это предложение действительно является корректным. Другой целью этого пустого UNION предложения является 100% определение типа используемого сервера баз данных (используя специфичные для разработчика СУБД имена таблиц в условии FROM).
После того как основанное на NULL значениях предложение UNION работает, определение типа каждого столбца становится тривиальной задачей. В каждой итерации отдельное поле проверяется на соответствие его типу. Все три типа (число, строка, дата) проверяются на соответствие типу поля — одно из них должно подойти. Этот способ требует количества проверок не больше утроенного количества столбцов, вместо трех в степени количества столбцов проверок. Полагая что CCNum является числовым выражением, а все остальные поля являются строковыми выражениями, ряд нижеприведенных UNION предложений определит тип всех полей:
·
11223344) UNION SELECT NULL,NULL,NULL,NULL WHERE 1=2 --
Ошибка не возникает – выражение синтаксически правильное. Используется MS
SQL Server. Продолжаем.
·
11223344) UNION SELECT 1,NULL,NULL,NULL WHERE 1=2 --
Ошибка не возникает — первый столбец является числом.
·
11223344) UNION SELECT 1,2,NULL,NULL WHERE 1=2 --
Ошибка! — второй столбец не является числом.
·
11223344) UNION SELECT 1,'2',NULL,NULL WHERE 1=2 --
Ошибка не возникает — второй столбец является строкой.
·
11223344) UNION SELECT 1,'2',3,NULL WHERE 1=2 --
Ошибка! — третий столбец не является числом.
·
11223344) UNION SELECT 1,'2','3',NULL WHERE 1=2 --
Ошибка не возникает — третий столбец является строкой.
·
11223344) UNION SELECT 1,'2','3',4 WHERE 1=2 --
Ошибка! - четвертый столбец не является числом.
·
11223344) UNION SELECT 1,'2','3','4' WHERE 1=2 --
Ошибка не возникает — четвертый столбец является строкой.
Теперь атакующий установил действительное, корректное UNION выражение. Используя увеличивающиеся номера полей атакующий, конечно, определит тип каждого поля. Теперь, когда все это завершено, можно использовать полученный эксплоит в целях атаки. Для осуществления этого, внедрение должно использоваться для получения данных из системных таблиц (например из тех, которые содержат список таблиц и их столбцов), затем для получения фактических данных приложения. Однако, этот документ не будет углубляться в эти детали, так как они подробно описаны в нескольких существующих документах, посвященных внедрению SQL кода.
Заключение
Этот документ является итогом исследований, проведенных компанией WebCohort относительно возможности использования внедрения SQL кода, если вывод подробных сообщений об ошибках не осуществляется. Это исследование было выполнено для того, чтобы доказать клиентам, что простое подавление вывода сообщений об ошибках возвращает их к подходу “Политики запутывания”, не является достаточным. Используя методики, описанные в этом документе можно доказать уязвимость многих приложений, несмотря на все попытки скрыть информацию, возвращаемую клиенту приложения.
Будем надеяться, что после прочтения этого документа читатель поймет, из-за чего хорошо сформированное внедрение SQL кода представляет опасность для любой системы, вне зависимости от того, отображаются ли подробные сообщения об ошибках или нет, и почему не совсем безопасно полагаться на сокрытие вывода сообщений об ошибках.
Более того, этот документ показывает, почему решения уровня инфраструктуры (например, подавление вывода сообщений об ошибках, хотя и любые другие решения уровня инфраструктуры могут иметь схожие проблемы) не могут обеспечить надлежащего решения для снижения риска эксплуатации приложения. Защиту приложения можно обеспечить только одним способом — путем принятия мер на уровне безопасности приложения.
Для получения дополнительной информации, свяжитесь с отделом исследований WebCohort Research: research@webcohort.com.
Одно найти легче, чем другое. Спойлер: это не темная материя