§5.5. Внутри сервера
Внутреннее устройство веб-серверов может различаться в зависимости от их предназначения: одни отдают файлы и простые странички, другие — формируют профили в социальной сети или даже оформляют результаты сложных вычислений. Мы рассмотрим самые популярные случаи и выясним, какие уязвимости у них бывают.
Просто файлы
С момента создания веба очень долгое время он существовал как набор статичных страничек на разных серверах. Большие компании разрабатывали сложные интерактивные сайты, но большинство пользователей, если и создавали свои страницы, просто писали на HTML и выкладывали полученное на сервер своего интернет-провайдера (да-да, интернет-провайдеры часто предоставляли почту и хостинг). Кроме того, HTTP используется для передачи изображений, видео и другого контента. Такой контент мало отличается по своей сути от статических HTML-страниц. Для раздачи файлов, а также первичной обработки HTTP-запросов существуют программы, называемые веб-серверами. В простейшей реализации веб-сервер просто отдаёт файлы, хранящиеся в указанной директории. Конечно, современные веб-серверы умеют гораздо больше — кэшировать данные, отдавать файлы частично, проверять логины и пароли пользователей. Пример такого сервера — nginx.
Уже здесь могут возникать уязвимости. Например, предположим, что сервер был настроен на выдачу файлов из директории /srv/http/root
. Мы уже знаем, что с помощью ..
в пути обозначается переход на уровень выше. Например, /home/test/../other
— это другой способ сослаться на /home/other
. Сервер берёт путь, который прислал пользователь (например, для http://example.com/images/cat.jpg
это images/cat.jpg
), и отдаёт файл по этому пути. В таком случае мы можем отправить запрос с путём ../../../etc/passwd
. Сервер соберёт путь /srv/http/root/../../../etc/passwd
, и мы получим файл /etc/passwd
. В этом файле традиционно хранятся записи о пользователях в UNIX. Такая уязвимость называется AFR (англ. Arbitrary File Reading — произвольное чтение файлов). Этот класс уязвимостей известен уже много лет, и тем не менее в 2018 году в nginx была найдена именно такая проблема.
Обратите внимание, что браузер может ещё до отправки запроса убирать ../
из пути. Чтобы отправить такой запрос, можно воспользоваться curl. Ему нужно передать флаг --path-as-is
, чтобы путь не исправлялся.
Страницы в динамике
Веб-серверы обычно берут часть забот разработчиков приложения на себя — они принимают запросы, в случае HTTPS расшифровывают их, проверяют на корректность, самостоятельно передают статические файлы, а все остальные запросы перенаправляют другому, внутреннему серверу. При этом внутренний сервер может генерировать HTML-страницу по запросу клиента — таким образом, разным пользователям может демонстрироваться разное содержимое. Кроме того, веб-сервер может не перенаправлять запрос, а напрямую запускать программу, которая сгенерирует веб-страницу для пользователя.
Из-за неправильной настройки сервера приложениий могут возникать различные проблемы — потеря производительности, неэкономное использования ресурсов сервера, внезапно остановленные экземпляры приложения. Мы не будем подробно останавливаться на этих особенностях: они зависят от конкретных технологий, используемых в приложении.
Современные веб-сервисы нередко вообще не используют генерацию веб-страниц. Вместо этого страница, используя JavaScript, сама запрашивает нужные данные с сервера и меняет собственный вид. Для передачи данных с сервера на браузер чаще всего используется формат данных JSON (англ. JavaScript Object Notation, язык объектов JavaScript) — это простой формат для передачи структурированных записей. Вот пример:
{
"first name": "John",
"last name": "Smith",
"age": 25,
"address": {
"street address": "21 2nd Street",
"city": "New York",
"state": "NY",
"postal code": "10021"
},
"phone numbers": [
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "fax",
"number": "646 555-4567"
}
],
"sex": {
"type": "male"
}
}
SQL и маленькая кавычка
Так уж повелось, что практически любой веб-сервис можно разделить на одинаковые составные части.
Веб-приложение делится на
фронтенд (англ. frontend) — совокупность HTML, CSS и JavaScript, то есть то, что отображает ваш браузер;
бэкенд (англ. backend) — приложение, формирующее веб-страницу, или отвечающее JSON-объектами на запросы;
базу данных — хранилище, в котором содержат записи о пользователях, их сообщения, настройки, заказы — в общем, всё, что сохраняет веб-приложение.
Детали этого разделения могут быть гораздо сложнее, но в целом такая структура интерактивных веб-приложений не менялась уже десятки лет. Представьте себе старомодный сервис заказа еды по телефону. Телефон, по которому вы заказываете пиццу, и курьер, отдающий заказ — это фронтенд, с которым вы взаимодействуете. Инфраструктура же поваров, ресторанов и автомобилей доставки — это бэкенд, то, что обеспечивает работу системы внутри.
Вся ценная информация, конечно же, хранится в базе данных, которая является целью любого злоумышленника. На протяжении многих лет создания приложений выделился целый класс атак, которые разработчики игнорируют с завидной регулярностью — инъекции. О них мы поговорим подробнее.
Уязвимости на основе инъекций возникают там, где какая-либо команда составляется простым сложением строк. Предположим, что вы составляете фразу на русском языке «Получить текст всех сообщений, где содержится слово X.» Вы позволяете пользователю передавать то, что записывается в X (например, «погода»). Однако, злоумышленник может запросить нечто другое, передав строку «„погода“, и удалить все данные». После подстановки выйдет: «Получить текст всех сообщений, где содержится слово „погода“, и удалить все данные.». Уже не так безобидно, правда?
Самым популярным видом баз данных остаются реляционные базы данных, первые реализации которых появились ещё в 70-х годах XX века. В таких базах все данные хранятся в таблицах с заданными колонками, и связываются друг с другом ссылками. Для запросов в такие базы создали язык запросов SQL. Вот пример выражения на нём:
SELECT first_name, last_name, password FROM users WHERE id = 4;
Это означает: «верни поля first_name
, last_name
и password
из таблицы users
для всех записей, у которых поле id
равно 4». В языке SQL есть классические арифметические операторы +
, -
, *
и /
, логические операторы AND
и OR
и многое другое — в каком-то смысле это настоящий язык программирования. Приведём пример запроса посложнее:
SELECT name FROM customers WHERE city = 'Moscow' AND debit + credit > 0;
Простота использования SQL привела к его популярности среди разработчиков. Особо нерадивые из них составляют SQL-запросы простым сложением строк, оставляя уязвимость — SQL-инъекцию. Предположим что есть сайт, поиск на котором реализован внутри следующим образом:
# Получить строку, которую хочет найти пользователь.
search_string = request.get_parameter("search_string")
# Получить содержимое постов, в которых содержится фраза из запроса пользователя.
posts_content = run_query("SELECT content FROM posts WHERE content ~ '" + search_string + "'")
# Выдать результат пользователю.
return posts_content
Где же именно скрыта уязвимость? Заметьте, что строка search_string
приходит от пользователя, и никак не проверяется. Сначала исследователь находит такую уязвимость, пробуя писать в текстовые поля одинарную кавычку '
. В таком случае конечный запрос примет вид: SELECT content FROM posts WHERE content ~ '''
. Три одинарные кавычки подряд — это синтаксическая ошибка в языке SQL
, и сервер скорее всего вернёт ошибку.
Найдя уязвимость, нужно построить такой запрос, который вернёт искомые данные. Конкретный пример будет зависеть от используемой базы данных и исходного запроса внутри приложения, которые нам неизвестны. Однако существует несколько универсальных хитростей, которые следует помнить.
Первая из них — это комментарий. В запросах на языке SQL можно оставлять комментарии с помощью конструкции --
. Она помечает весь текст, идущий от неё и до конца строки, как комментарий, который игнорируется базой. Например:
-- Вот такой вот запрос:
SELECT "user_name" FROM "users" WHERE "is_active"; -- вернуть имена активных пользователей
Обычно комментарии направлены на благое дело — документировать написанные запросы. Однако мы также можем использовать их в инъекциях. Заметим, что в запросе выше после строки пользователя будет добавлена одинарная кавычка. Как избавиться от неё? Можно добавить комментарий в конце строки, например погода' --
. Тогда весь запрос получится такой: SELECT content FROM posts WHERE content ~ 'погода' -- '
, и успешно выполнится. Таким образом мы можем дописывать произвольный код в конце запроса, не беспокоясь о проблемах из-за кавычки.
Вторая хитрость — это конструкции LIMIT
и UNION
. Вместе они позволяют нам полностью поменять смысл запроса. Конструкция LIMIT
устанавливает ограничение на количество строк, возвращаемых запросом. Eсли же поставить LIMIT 0
, то ни одна запись из таблицы возвращена не будет. Например, если злоумышленник напишет в строке поиска ' LIMIT 0 --
, то результат будет такой: SELECT content FROM posts WHERE content ~ 'погода' LIMIT 0 -- '
. Этот запрос будет успешным, однако не вернёт результата — как будто мы ничего не нашли. Отключить так исходный запрос бывает необходимо, если результат ожидается лишь один, и просто полезно чтобы просматривать результат было проще.
Конструкция UNION
позволяет объединять два несвязанных запроса и возвращать результаты обоих вместе. Например:
SELECT city FROM customers -- получить города всех клиентов
UNION
SELECT City FROM suppliers; -- и вместе с ними города всех поставщиков
С помощью этой конструкции мы заставим веб-приложение выполнить вредоносное действие — вернуть в качестве текста поста пароль администратора сайта. Для этого в строке поиска нужно ввести ' LIMIT 0 UNION SELECT password FROM users WHERE is_admin = 1 --
. Итоговый вредоносный запрос будет выглядеть так:
SELECT content FROM posts WHERE content ~ '' LIMIT 0 UNION SELECT password FROM users WHERE is_admin = 1 -- '
Вуаля, мы получаем секретную информацию прямо там, где обычно находится текст свежих статей ведущих исследователей погодных явлений!
Мы рассмотрели самое простое применение SQL-инъекций. К сожалению, эта тема очень обширна и всё рассказать в рамках этого курса тяжело. Например, мы не рассмотрели, как получить список всех таблиц в базе данных. Также бывает полезным узнать, какая именно база данных используется — это облегчает дальшейшее построение запроса. Чтобы лучше разбираться в этом, стоит начать с изучения самого языка SQL.
Наконец, кроме реляционных баз данных используются и другие. Документные базы, колоночные, графовые — у них есть свои преимущества и недостатки, и их выбирают исходя из задачи. Такое же многообразие наблюдается и среди языков программирования, на которых пишут веб-приложения. В них тоже бывают свои инъекции.
Выводы
Файлы и статические веб-страницы браузеру отдаёт веб-сервер.
Веб-сервера могут быть уязвимы. Одна из известных возможных уязвимостей — недостаточная проверка пути, который запрашивает пользователь. Ваши пароли могут утечь благодаря специальной директории
..
.Веб-приложение состоит из фронтенда (самой страницы), бэкенда (приложения на сервере) и базы данных (хранилища информации).
Самый распространённый вид базы данных — SQL-совместимые. Такие базы хранят всё в табличках со ссылками друг на друга.
Инъекции — это класс уязвимостей, возникающих из-за выполнения команд, которые собираются сложением строк.
SQL-инъекции в частности — это инъекции кода на SQL через приложение в базу данных. Кавычка
'
помогает взламывать целые сайты.