Введение:
Всех приветствую. В прошлой статье я рассказывал о Server Side Request Forgery (SSRF) – подделке запроса на серверной стороне. Эта уязвимость помогала разведать внутрений периметр компаний. Там же я упомянул еще две уязвимости – XXE, и что у SSRF есть родственник на клиентской стороне. Ну, о XML-иньекциях в следующей статье, а сегодня же поговорим о “клиентской” стороне подделок запроса – Client Side Request Forgery, то есть, уже подделке на стороне клиента. В отличии от старшего брата это уязвимость попроще, да и в целом, если с серверной подделкой очень много ситуативных моментов, а профит носит исключительной информативный характер, то здесь все куда более осязаемо. Клиенсткая подделка запроса используется для атак уже на пользователей приложения и выполнения каких-либо действий в приложении от их имени. Причем, атакованый пользователь даже не будет об этом подозревать. Как именно все это работает, как использовать, искать, и какие методы защиты используются – все это постараюсь разобрать и показать. Статья в основном для новичков, так как рассказывать буду в основном про базовые моменты, но затрону и более сложные ситуации по ходу дела. Так как уязвимость даже сейчас достаточно популярна, знать о ней, особенно если ваша деятельность связана с веббезом просто необходимо. Поехали!
Теория:
Итак, снова теоретическое обьяснение. Выше я упомянул, что уязвимость позволяет выполнять какие-либо действия от имени ничего не подозревающего пользователя. Давайте сразу на примере и рассмотрим как это происходит. У нас есть:
1 – Пользователь. Ничего не подозревающая жертва.
2 – Хакер. Непосредственно взломщик, который хочет получить доступ к аккаунту пользователя.
3 – Уязвимый к CSRF сайт. Приложение, в котором пользователь авторизирован и которым пользуется.
4 – Сайт хакера, который тот использует для своих “хакерских” делишек =)
Схема классической CSRF атаки
Итак, у нас есть пользователь, который авторизирован на уязвимом сайте – скажем социальной сети, блоге, магазине или чем-то похожем. И на этом сайте есть форма смены пароля, которая POST-запросом принимает на ввод два параметра – новый пароль и его подтверждение. Причем запрос старого пароля не производится. Задача хакера в данной ситуации – изменить пароль на свой.
Каким образом происходит атака ? Сначала хакер каким-либо образом заманивает пользователя на подконтрольный себе сайт. Пользователь ничего не подозревая переходит на какую-либо страницу подконтрольного хакеру сайта, а сайт тем временем, по переходу пользователя отправляет форму с двумя параметрами, которые контролирует уже хакер. Форма эта отправляется на уязвимый сайт, а так как с точки зрения протокола HTTP пользователь сам отправил эту форму ( и не важно что с другого сайта), cookies пользователя автоматически прикрепляются к запросу. Как итог – пароль успешно изменяется. Уязвимость сайта засключается в том, что отсутсвует проверка подлиности запроса, то есть нет проверки на то, что пользователь хотел отправить этот запрос на смену пароля. А так как для реализации атаки достаточно чтобы пользователь просто перешел по ссылке, причем на сторонний сайт, шанс обмануть пользователя очень высок. Ведь мало кто из простых пользователей может подумать, что просто от перехода на сайт А, тебя могут взломать на сайте B.
Практика:
От теории переходим к практике. Для наглядного показа я буду использовать то же уязвимое приложение, что и в прошлой статье – XVWA.
Если кто пропустил
Исходники уязвимой машины – тут:
https://github.com/s4n7h0/xvwa
Живой образ iso – тут
https://www.vulnhub.com/entry/xtreme-vulnerable-web-application-xvwa-1,209/
Сразу ищем раздел CSRF. При открытии страницы видим, что для реализации атаки необходима авторизация:
Авторизируемся через кнопку login, при помощи дефолтных данных admin/admin:
И теперь внимательнее смотрим на саму страницу. Мы видим форму смены пароля – как я и говорил в примере. Запрашивается новый пароль и его подтверждение. Старый пароль от нас не требуют. Чтож, попробуем поменять пароль администратора на “1234”. После отправки формы видим что пароль был успешно изменен:
А теперь обратите внимение на URL – оказывается наш новый пароль передается прямо в GET запросе, абсолютно никак при этом не защищенный. То есть, здесь все еще хуже чем в примере, который я приводил в теории. Для реализации атаки на эту страницу будет достаточно сформировать ссылку, поменять значения параметров “passwd” и “confirm” на нужные нам и отправить ссылку жертве. Просто при переходе по ней пароль будет изменен. А чтобы жертва ничего не заподозрила, передаваемые параметры можно полностью закодировать в URL, превратив таким образом ссылку:
http://10.0.2.7/xvwa/vulnerabilities/csrf?passwd=1234&confirm=1234&submit=submit
в
http://10.0.2.7/xvwa/vulnerabilities/csrf?%70%61%73%73%77%64%3d%31%32%33%34%26%63%6f%6e%66%69%72%6d%3d%31%32%33%34%26%73%75%62%6d%69%74%3d%73%75%62%6d%69%74
Теперь ссылка выглядит более безопасно, благодаря тому что в ней не светятся имена параметров типа “passwd” и “confirm”. Для простого пользователя этого вполне хватит.
Но, встретить такой пример, в котором все чувствительные данные передаются GET-запросом будет большой редкостью, так как согласно стандартам, методы GET, OPTIONS, TRACE и HEAD не должны использоваться для передачи информации, которая может что-либо изменить. Такая информация должна передаваться в других методах – POST, PATH, DELETE, PUT. Такой кейс с GET-запросом можно найти разве что на каких-то старых сайтах, которые делал индуский студент на коленке, тренировки ради. А вот в случае с POST запросом такие кейсы имеют место быть. Как правило, они, конечно, более сложные, но суть примерно одна. Поэтому, я слегка переписал код, чтобы данные принимались POST-запросом, и предлагаю разобрать уже такой кейс. Здесь уже одним переходом по ссылке дело не обойдется. Точнее, для жертвы все так и будет выглядеть – просто переход на сайт, но уже сторонний. А вот нам придется провернуть более сложные действия, чем просто поменять параметры GET-запроса в ссылке.
Итак, атаку на POST запрос будем уже проводить в два этапа. И первым этапом будет подготовка. Для того, чтобы реализовать атаку, нам нужно, на подконтрольном нам сайте, создать страницу-ловушку, которая будет содержать форму-дубликат с уязвимого сайта, которая уже и будет отправляться обратно на уязвимый сайт.
Для начала просто откроем код страницы на уязвимом сайте и посмотрим как выглядит форма, которую мы будем атаковать:
Первое что стоит сделать – просто скопировать код формы и вставить его себе на “страницу-ловушку”:
Стили у нас, разумеется, не прогружаются, но это и не нужно, так как пользователь эту форму видеть не должен вообще. Потому что, во первых пользователь не должен контролировать ее содержимое, а во вторых – это было бы глупо, да и обьяснить зачем пользователю вводить какие-то определенные данные было бы сложно. Поэтому мы пойдем следующим путем – полностью скроем форму от глаз пользователя. Для начала добавим ко всем полям атрибуты “type=’hidden’ ” Это сделает поля на странице невидмыми. Так же, добавим полям атрибуты “value=’<password>’ ”, где <password> - это пароль, который мы хотим отправить. Таким образом мы изначально установим нужный нам пароль. Ну и последнее – в тег формы добавим атрибут “action=<link>”, где <link> - это ссылка на страницу смены пароля на уязвимом сайте. В моем случае это “http://10.0.2.7/xvwa/vulnerabilities/csrf/index.php”. Таким образом мы заполним форму данными и полностью скроем ее от пользователя
Но остается еще одна проблема – кнопка. Она видна на странице и для отправки формы нужно чтобы пользователь ее нажал. Можно конечно стилизовать ее так, чтобы на нее очень уж хотелось нажать, но это всетаки ненадежно. Вдруг пользователь по какой-то причине не захочет на нее жать ? Поэтому, пойдем еще дальше – сделаем автоматическую отправку формы. Реализовать это можно буквально при помощи одной строчки JS. Для начала добавим еще один атрибут к тегу form – атрибут “name=’myform’ ” для того, чтобы JS понимал с каким элементом ему работать. А затем просто добавляем тег <script> на страницу, со следующим содержимым:
Теперь, при открытии страницы, уже заполненая нами форма будет отправлятся на уязвимое веб-приложение. Как итог, при открытии нашей html-страницы, пользователя сразу же перекинет на страницу смены пароля, где будет сообщаться что пароль успешно изменен:
Далее остается только второй этап – заманить пользователя на эту, созданую нами страницу, после чего его пароль будет заменен на нужный нам.
Про кроссдоменную эксплуатацию через JS-запросы
Как известно, на ошибках мы учимся, но лучше учится на чужих, чтобы самому потом не набивать шишек. Поэтому, в этом разделе статьи я затрону такую тему как кросдоменная подделка запроса при помощи JS. Метод весьма спорный с точки зрения соотношения силовых затрат и результата, далее вы в этом убедитесь. Но упомянуть его стоит, хотябы потому, чтобы сэкономить время новичкам, которые решат пойти дальше и улучшить способ эксплуатации, описаный выше в статье.
Почему вообще можно захотеть что-то улучшить ? Вроде все неплохо – нам удалось заманить пользователя на страницу с формой, которая автоматически отправилась и действия выполнились. Но есть одна небольшая проблема и то, что может смутить – после отправки формы пользователя сразу же перекидывает на страницу, на которую форма отправлялась, то есть обратно на уязвимый сайт, прямо на страницу смены пароля, где крупными буквами написано что пароль успешно изменен. Тут конечно все ситуативно, но конкретно в данном случае все выглядит далеко не идеально – пользователь видит, что его почему-то перебросило на другой сайт, где он до этого был авторизован, да еще и на этом сайте написано, что пароль был изменен. Даже у самого последнего чайника возникнут подозрения, что что-то здесь не так.
Поэтому, следующий вопрос, который возникает – можно ли каким-то образом отправить запрос, но так, чтобы пользователь об этом не узнал и его не перекинуло на уязвимую страницу. И да, такой способ существует. Но, сразу оговорюсь – такой способ будет работать далеко не всегда, а затраты сил на его реализацю будут не маленькими. Поэтому, сами решайте, нужен ли вам такой апгрейд. Но обо всем по порядку:
Начнем с того, как вообще можно реализовать незаметную для пользователя отправку запроса. И первая очевидная мысль – JavaScript. Всем известно (а кому не известно, теперь знают), что при помощи Javascript’а и его библиотеки XMLHttpRequest (в простонародье - XHR) можно сформировать http-запросы. То есть, в теории, мы может просто написать скрипт на нашем же сайте-ловушке, но уже не с автоматической отправкой html-формы, а с автоматической отправкой всех данных через JS. Для этого напишем небольшой скрипт, который впоследствии добавим на нашу страницу-ловушку:
Быстренько пробежимся по коду:
Сначала мы создаем экземпляр класса XHR и называем его request. Он и будет ключевой фигурой в отправке запросов.
Далее создаем переменную data, в которую записываем строкой POST-параметры (passwd и confirm) и их значения для нашего запроса.
После этого открываем соединение методом open, в который передаем тип запроса – POST, url, куда собственно отправлять запрос.
Далее важный момент – поле withCredentials переводим в значение true. Это завставит бразуер прикрепить к запросу пользовательские cookies
Далее при помощи метода setRequestHeader устанавливаем заголовок Content-Type в значение “aplication/x-www-form-urlencoded”, так мы избежим отправки предварительного CORS запроса.
Ну и отправляем запрос методом send, передав в метод данные POST-запроса в переменной data.
Дальше просто добавляем этот скрипт на страницу. Но, если мы попробуем его запустить методом перехода на страницу – увидим вот такой лог в консоли:
Видим что запрос был заблокирован в связи с отсутствием заголовка Access-Control-Allow-Orign в ответе веб-сервера.
Что это такое и почему так произошло ? Давайте разбираться:
И вот тут мы переходим к такой штуке как CORS – Cross Origin Resource Sharing. Сильно углубляться не будем, но если вкратце - это такая технология, которая по сути является механизмом безопасности, обеспечивающим доступ к ресурсам одного домена с другого. По умолчанию такой доступ запрещен – то есть мы не можем получить доступ с одного ресурса на другой, именно поэтому наш JS-запрос и не работает.
Но разработчик может разрешить такой доступ при помощи настройки веб-сервера, которая будет добавлять в ответ сервера заголовок Access-Control-Allow-Orign, на отсутствие которого и ругалась консоль. Эту технологию поддерживают все современые бразуеры, технология достаточно широко используется на разных сайтах. И вот здесь и кроется важный момент – разработчик может забить болт на политики безопасности и установить заголовок следующим образом:
Access-Control-Allow-Orign: *
Эта звездочка как раз означает, что доступ к ресурсам домена могут получить все. То есть, отправка таких межсайтовых запросов будет доступна с любого другого домена, в том числе и с нашего атакующего.
И многие, дойдя до этого этапа и увидев что в ответе сервера заголовок отмечен звездочкой, могут подумать, что теперь выполнение нашего скрипта возможно и мы можем абсолютно незаметно для пользователя отправить запрос – но, это к сожалению не так. Если мы попробуем выполнить скрипт – увидим вот это:
Если мы прочтем документацию, то поймем в чем проблема – оказывается, как раз из соображений безопасности, CORS запрещает доступ к ресурсам домена, если параметр Access-Control-Allow-Orign установлен в значение “*” и в запросе присутствуют пользовательские данные (в нашем случае cookies – помните поле withCredentials в скрипте), т.е. такой запрос все равно будет блокироваться, не смотря на то, что доступ открыт всем. Всем, но только без передачи пользовательских данных в запросе. Но если убрать cookies из запроса, то отправка такого запроса в нашем случае теряет всякий смысл.
И всетаки, данную атаку можно реализовать. Но для этого придется нехило так заморочится. Потому что отправка такого запроса будет возможна только с доверенного домена.
То есть, если параметр настроен следующим образом:
Access-Control-Allow-Orign: “white-example.com” ,
что по сути является правильным, то отправить такой запрос вместе с пользовательскими данными можно будет только с сайта white-example.com. А для этого у вас должны быть возможность разместить на этом домене свой скрипт, а значит - довереный сайт придется взломать.
Провернуть это можно, если вы, например нашли на довереном сайте XSS – тогда вы просто сможете внедрить скрипт на страницу, а потом заманить на эту страницу пользователя. Таким образом получится целая цепочка – XSS на одном домене поможет незаметно проэксплутировать CSRF на другом.
Как я уже сказал – способ апгрейда весьма спорный. Да, в случае успеха пользователь вообще ничего не заметит, но для этого придется скомпроментировать аж два домена. В любом случае, я считаю что про него стоит знать, а пытаться его применить или нет – решать только вам.
(Про этот способ я написал, так как в начале своего пути сам крутил подобный кейс и здорово подзавис на этом моменте. Описывал все исходя из своего опыта, но не исключаю что я мог отстать от жизни или просто не до конца разобраться в теме. Поэтому, если я вдруг ошибся, а вы вдруг знаете как обойти механизмы защиты CORS в данном случае и напишете как это сделать – буду очень благодарен=) )
Разумеется CSRF может работать не только с важными данными. Например, CSRF хорошо работает в симбиозе с XSS и в обратную сторону. Представьте ситуацию: есть уязвимая к отраженному XSS страница, уязвимый параметр на которой передается в POST запросе. Как в таком случае реализовывать атаку, если простой передачи ссылки недостаточно – параметр-то в POST, а не в GET ? В этом случае и может прийти на помощь CSRF. Если подлиность POST запроса не проверяется (А она может и не проверяться, если передаваемые данные не являются чувствительными), то можно вполне, так же как и в примере, создать форму, которая будет отправляться на уязвимый сайт. А так как после отправки формы жертву выкинет обратно на уязвимый сайт, он получит уже ответ на POST запрос, где и будет внедренный JS-код.
Надеюсь, ход мыслей понятен. Так что, CSRF может быть полезна не только для изменений важных данных без ведома пользователя.
Защита:
Для защиты от CSRF применяются разные способы, начиная от очень просто реализованых, но при этом не слишком удобных для самих пользователей, заканчивая сложными способами, которые для рядового пользователя почти не заметны.
1. Двойная авторизация.
Самый простой способ защититься от CSRF – запрашивать пароль каждый раз, когда пользователь пытается совершить какие-либо действия с чувствительными данными. Наглядным примером является как раз таки запрос старого пароля при смене его на новый – это делается не только для дополнительной безопасности при случайном получении доступа к аккаунту третьих лиц, но и для защиты от CSRF, так как не зная текущий пароль, злоумышленик не сможет сформировать запрос для смены его на новый. Способ простой и надежный, обойти его можно только в случае, если атакующий узнает пароль/секретную фразу. Так же, метод достаточно прост в реализации, но далеко не очень удобный для пользователей. Представьте что будет, если работа с подобными данными производится часто. Пользователю постоянно придется вводить пароль при малейшей попытке обратиться или изменить важные данные, что не очень удобно. Поэтому, этот способ защиты реализуется как правило для наиболее “опасных”, с точки зрения подвержености уязвимости, функций – например, смена пароля, как я упомянул выше. В других же случаях используются другие методы, о которых я рассскажу далее.
2.Использование токенов
Наверное самый популярный способ защиты от CSRF в наше время. Есть несколько вариантов реализаций такой защиты, но самый классический способ – синхронизирующийся токен. Его суть заключается в том, что для каждой операции с чувствительными данными генерируется токен – уникальный отпечаток, который служит для идентификации пользователя и проверки подлинности запроса.
Звучит не совсем понятно, поэтому поясню на примере:
Пользователь авторизуется на сайте. Сайт тут же генерирует специальный отпечаток, который и сохраняет себе в хранилище сессии, и передает пользователю. И теперь, при каждом действии, пользователь будет прикреплять к запросу этот отпечаток (токен). В случае если токена не будет – запрос не будет работать. Токены хранятся на компьютере пользователя и отправляются только на домен, с которого были установлены. Живут такие токены только на время сесии и для каждой новой сессии генерируется новый токен. Так как атакующий не может знать токен, а автоматически такие заголовки к форме не крепятся, в отличие от cookies, классическая атака CSRF становится невозможна.
Это не единственный способ реализации защиты через токены, есть так же подходы по типу double cookie, при которых уникальный токен генерируется не на время сессии, а для каждого отдельного запроса, и передается клиенту дважды, для дальнейшего получения обоих токенов и их сверки, или еще возможен подход через шифрование токена, когда внутрь токена шифруются данные, а затем проверяются при помощи закрытого ключа на сервере.
Для обхода таких методов защиты с токенами используются разные подходы. По хорошему, токен дожен иметь ограниченое время жизни и при этом не должно быть возможности его перехватить атакующему. Но для перехвата может быть использована, например, XSS, так как JS имеет доступ к данным пользователя. Или для способа с double-cookie токенами могут быть использованы особенности работы http с поддоменами.
Так же, последнее время набирает обороты технология same site. Этот атрибут для cookies позволяет указать, что принимать запрос с cookies следует только от того сайта, на котором cookies были установлены. Это позволяет реализовать защиту от csrf без каких-либо дополнительных реализаций в коде.
Заключение:
CSRF уязвимости медленно умирают, так как защита от них эволюционирует, да и сейчас почти каждый джун-вебщик знает что это за уязвимость и как от нее защищаться. Если лет 7 назад эта уязвимость была практически в каждом первом сайте, то сейчас количество сайтов, подверженных этой уязвимости очень сильно сократилось.
Но, не смотря на это, CSRF-уязвимости никак не вымрут до конца. Они до сих пор встречаются даже в очень крупных проектах (отчеты с bug bounty программ тому подтверждение), а про более мелкие проекты и говорить нечего. Да, количество сайтов, подверженных уязвимости медленно снижается, но происходит это гораздо медленнее, чем должно бы. Поэтому – умение эксплуатировать эту уязвимость достаточно важный навык в арсенале атакующего в наши дни.
Ну а на этом я с темой CSRF заканчиваю, всем добра !
©Urob0ros, специально для форума xss.pro.
Всех приветствую. В прошлой статье я рассказывал о Server Side Request Forgery (SSRF) – подделке запроса на серверной стороне. Эта уязвимость помогала разведать внутрений периметр компаний. Там же я упомянул еще две уязвимости – XXE, и что у SSRF есть родственник на клиентской стороне. Ну, о XML-иньекциях в следующей статье, а сегодня же поговорим о “клиентской” стороне подделок запроса – Client Side Request Forgery, то есть, уже подделке на стороне клиента. В отличии от старшего брата это уязвимость попроще, да и в целом, если с серверной подделкой очень много ситуативных моментов, а профит носит исключительной информативный характер, то здесь все куда более осязаемо. Клиенсткая подделка запроса используется для атак уже на пользователей приложения и выполнения каких-либо действий в приложении от их имени. Причем, атакованый пользователь даже не будет об этом подозревать. Как именно все это работает, как использовать, искать, и какие методы защиты используются – все это постараюсь разобрать и показать. Статья в основном для новичков, так как рассказывать буду в основном про базовые моменты, но затрону и более сложные ситуации по ходу дела. Так как уязвимость даже сейчас достаточно популярна, знать о ней, особенно если ваша деятельность связана с веббезом просто необходимо. Поехали!
Теория:
Итак, снова теоретическое обьяснение. Выше я упомянул, что уязвимость позволяет выполнять какие-либо действия от имени ничего не подозревающего пользователя. Давайте сразу на примере и рассмотрим как это происходит. У нас есть:
1 – Пользователь. Ничего не подозревающая жертва.
2 – Хакер. Непосредственно взломщик, который хочет получить доступ к аккаунту пользователя.
3 – Уязвимый к CSRF сайт. Приложение, в котором пользователь авторизирован и которым пользуется.
4 – Сайт хакера, который тот использует для своих “хакерских” делишек =)
Схема классической CSRF атаки
Итак, у нас есть пользователь, который авторизирован на уязвимом сайте – скажем социальной сети, блоге, магазине или чем-то похожем. И на этом сайте есть форма смены пароля, которая POST-запросом принимает на ввод два параметра – новый пароль и его подтверждение. Причем запрос старого пароля не производится. Задача хакера в данной ситуации – изменить пароль на свой.
Каким образом происходит атака ? Сначала хакер каким-либо образом заманивает пользователя на подконтрольный себе сайт. Пользователь ничего не подозревая переходит на какую-либо страницу подконтрольного хакеру сайта, а сайт тем временем, по переходу пользователя отправляет форму с двумя параметрами, которые контролирует уже хакер. Форма эта отправляется на уязвимый сайт, а так как с точки зрения протокола HTTP пользователь сам отправил эту форму ( и не важно что с другого сайта), cookies пользователя автоматически прикрепляются к запросу. Как итог – пароль успешно изменяется. Уязвимость сайта засключается в том, что отсутсвует проверка подлиности запроса, то есть нет проверки на то, что пользователь хотел отправить этот запрос на смену пароля. А так как для реализации атаки достаточно чтобы пользователь просто перешел по ссылке, причем на сторонний сайт, шанс обмануть пользователя очень высок. Ведь мало кто из простых пользователей может подумать, что просто от перехода на сайт А, тебя могут взломать на сайте B.
Практика:
От теории переходим к практике. Для наглядного показа я буду использовать то же уязвимое приложение, что и в прошлой статье – XVWA.
Если кто пропустил
Исходники уязвимой машины – тут:
https://github.com/s4n7h0/xvwa
Живой образ iso – тут
https://www.vulnhub.com/entry/xtreme-vulnerable-web-application-xvwa-1,209/
Сразу ищем раздел CSRF. При открытии страницы видим, что для реализации атаки необходима авторизация:
Авторизируемся через кнопку login, при помощи дефолтных данных admin/admin:
И теперь внимательнее смотрим на саму страницу. Мы видим форму смены пароля – как я и говорил в примере. Запрашивается новый пароль и его подтверждение. Старый пароль от нас не требуют. Чтож, попробуем поменять пароль администратора на “1234”. После отправки формы видим что пароль был успешно изменен:
А теперь обратите внимение на URL – оказывается наш новый пароль передается прямо в GET запросе, абсолютно никак при этом не защищенный. То есть, здесь все еще хуже чем в примере, который я приводил в теории. Для реализации атаки на эту страницу будет достаточно сформировать ссылку, поменять значения параметров “passwd” и “confirm” на нужные нам и отправить ссылку жертве. Просто при переходе по ней пароль будет изменен. А чтобы жертва ничего не заподозрила, передаваемые параметры можно полностью закодировать в URL, превратив таким образом ссылку:
http://10.0.2.7/xvwa/vulnerabilities/csrf?passwd=1234&confirm=1234&submit=submit
в
http://10.0.2.7/xvwa/vulnerabilities/csrf?%70%61%73%73%77%64%3d%31%32%33%34%26%63%6f%6e%66%69%72%6d%3d%31%32%33%34%26%73%75%62%6d%69%74%3d%73%75%62%6d%69%74
Теперь ссылка выглядит более безопасно, благодаря тому что в ней не светятся имена параметров типа “passwd” и “confirm”. Для простого пользователя этого вполне хватит.
Но, встретить такой пример, в котором все чувствительные данные передаются GET-запросом будет большой редкостью, так как согласно стандартам, методы GET, OPTIONS, TRACE и HEAD не должны использоваться для передачи информации, которая может что-либо изменить. Такая информация должна передаваться в других методах – POST, PATH, DELETE, PUT. Такой кейс с GET-запросом можно найти разве что на каких-то старых сайтах, которые делал индуский студент на коленке, тренировки ради. А вот в случае с POST запросом такие кейсы имеют место быть. Как правило, они, конечно, более сложные, но суть примерно одна. Поэтому, я слегка переписал код, чтобы данные принимались POST-запросом, и предлагаю разобрать уже такой кейс. Здесь уже одним переходом по ссылке дело не обойдется. Точнее, для жертвы все так и будет выглядеть – просто переход на сайт, но уже сторонний. А вот нам придется провернуть более сложные действия, чем просто поменять параметры GET-запроса в ссылке.
Итак, атаку на POST запрос будем уже проводить в два этапа. И первым этапом будет подготовка. Для того, чтобы реализовать атаку, нам нужно, на подконтрольном нам сайте, создать страницу-ловушку, которая будет содержать форму-дубликат с уязвимого сайта, которая уже и будет отправляться обратно на уязвимый сайт.
Для начала просто откроем код страницы на уязвимом сайте и посмотрим как выглядит форма, которую мы будем атаковать:
Первое что стоит сделать – просто скопировать код формы и вставить его себе на “страницу-ловушку”:
Стили у нас, разумеется, не прогружаются, но это и не нужно, так как пользователь эту форму видеть не должен вообще. Потому что, во первых пользователь не должен контролировать ее содержимое, а во вторых – это было бы глупо, да и обьяснить зачем пользователю вводить какие-то определенные данные было бы сложно. Поэтому мы пойдем следующим путем – полностью скроем форму от глаз пользователя. Для начала добавим ко всем полям атрибуты “type=’hidden’ ” Это сделает поля на странице невидмыми. Так же, добавим полям атрибуты “value=’<password>’ ”, где <password> - это пароль, который мы хотим отправить. Таким образом мы изначально установим нужный нам пароль. Ну и последнее – в тег формы добавим атрибут “action=<link>”, где <link> - это ссылка на страницу смены пароля на уязвимом сайте. В моем случае это “http://10.0.2.7/xvwa/vulnerabilities/csrf/index.php”. Таким образом мы заполним форму данными и полностью скроем ее от пользователя
Но остается еще одна проблема – кнопка. Она видна на странице и для отправки формы нужно чтобы пользователь ее нажал. Можно конечно стилизовать ее так, чтобы на нее очень уж хотелось нажать, но это всетаки ненадежно. Вдруг пользователь по какой-то причине не захочет на нее жать ? Поэтому, пойдем еще дальше – сделаем автоматическую отправку формы. Реализовать это можно буквально при помощи одной строчки JS. Для начала добавим еще один атрибут к тегу form – атрибут “name=’myform’ ” для того, чтобы JS понимал с каким элементом ему работать. А затем просто добавляем тег <script> на страницу, со следующим содержимым:
JavaScript:
document.myform.submit();
Далее остается только второй этап – заманить пользователя на эту, созданую нами страницу, после чего его пароль будет заменен на нужный нам.
Про кроссдоменную эксплуатацию через JS-запросы
Как известно, на ошибках мы учимся, но лучше учится на чужих, чтобы самому потом не набивать шишек. Поэтому, в этом разделе статьи я затрону такую тему как кросдоменная подделка запроса при помощи JS. Метод весьма спорный с точки зрения соотношения силовых затрат и результата, далее вы в этом убедитесь. Но упомянуть его стоит, хотябы потому, чтобы сэкономить время новичкам, которые решат пойти дальше и улучшить способ эксплуатации, описаный выше в статье.
Почему вообще можно захотеть что-то улучшить ? Вроде все неплохо – нам удалось заманить пользователя на страницу с формой, которая автоматически отправилась и действия выполнились. Но есть одна небольшая проблема и то, что может смутить – после отправки формы пользователя сразу же перекидывает на страницу, на которую форма отправлялась, то есть обратно на уязвимый сайт, прямо на страницу смены пароля, где крупными буквами написано что пароль успешно изменен. Тут конечно все ситуативно, но конкретно в данном случае все выглядит далеко не идеально – пользователь видит, что его почему-то перебросило на другой сайт, где он до этого был авторизован, да еще и на этом сайте написано, что пароль был изменен. Даже у самого последнего чайника возникнут подозрения, что что-то здесь не так.
Поэтому, следующий вопрос, который возникает – можно ли каким-то образом отправить запрос, но так, чтобы пользователь об этом не узнал и его не перекинуло на уязвимую страницу. И да, такой способ существует. Но, сразу оговорюсь – такой способ будет работать далеко не всегда, а затраты сил на его реализацю будут не маленькими. Поэтому, сами решайте, нужен ли вам такой апгрейд. Но обо всем по порядку:
Начнем с того, как вообще можно реализовать незаметную для пользователя отправку запроса. И первая очевидная мысль – JavaScript. Всем известно (а кому не известно, теперь знают), что при помощи Javascript’а и его библиотеки XMLHttpRequest (в простонародье - XHR) можно сформировать http-запросы. То есть, в теории, мы может просто написать скрипт на нашем же сайте-ловушке, но уже не с автоматической отправкой html-формы, а с автоматической отправкой всех данных через JS. Для этого напишем небольшой скрипт, который впоследствии добавим на нашу страницу-ловушку:
JavaScript:
var request = new XMLHttpRequest();
var data = "passwd=1234&conirm=1234";
request.open('POST','http://10.0.2.7/xvwa/vulnerabilities/csrf/index.php',true);
request.withCredentials = true;
request.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
request.send(data);
Сначала мы создаем экземпляр класса XHR и называем его request. Он и будет ключевой фигурой в отправке запросов.
Далее создаем переменную data, в которую записываем строкой POST-параметры (passwd и confirm) и их значения для нашего запроса.
После этого открываем соединение методом open, в который передаем тип запроса – POST, url, куда собственно отправлять запрос.
Далее важный момент – поле withCredentials переводим в значение true. Это завставит бразуер прикрепить к запросу пользовательские cookies
Далее при помощи метода setRequestHeader устанавливаем заголовок Content-Type в значение “aplication/x-www-form-urlencoded”, так мы избежим отправки предварительного CORS запроса.
Ну и отправляем запрос методом send, передав в метод данные POST-запроса в переменной data.
Дальше просто добавляем этот скрипт на страницу. Но, если мы попробуем его запустить методом перехода на страницу – увидим вот такой лог в консоли:
Видим что запрос был заблокирован в связи с отсутствием заголовка Access-Control-Allow-Orign в ответе веб-сервера.
Что это такое и почему так произошло ? Давайте разбираться:
И вот тут мы переходим к такой штуке как CORS – Cross Origin Resource Sharing. Сильно углубляться не будем, но если вкратце - это такая технология, которая по сути является механизмом безопасности, обеспечивающим доступ к ресурсам одного домена с другого. По умолчанию такой доступ запрещен – то есть мы не можем получить доступ с одного ресурса на другой, именно поэтому наш JS-запрос и не работает.
Но разработчик может разрешить такой доступ при помощи настройки веб-сервера, которая будет добавлять в ответ сервера заголовок Access-Control-Allow-Orign, на отсутствие которого и ругалась консоль. Эту технологию поддерживают все современые бразуеры, технология достаточно широко используется на разных сайтах. И вот здесь и кроется важный момент – разработчик может забить болт на политики безопасности и установить заголовок следующим образом:
Access-Control-Allow-Orign: *
Эта звездочка как раз означает, что доступ к ресурсам домена могут получить все. То есть, отправка таких межсайтовых запросов будет доступна с любого другого домена, в том числе и с нашего атакующего.
И многие, дойдя до этого этапа и увидев что в ответе сервера заголовок отмечен звездочкой, могут подумать, что теперь выполнение нашего скрипта возможно и мы можем абсолютно незаметно для пользователя отправить запрос – но, это к сожалению не так. Если мы попробуем выполнить скрипт – увидим вот это:
Если мы прочтем документацию, то поймем в чем проблема – оказывается, как раз из соображений безопасности, CORS запрещает доступ к ресурсам домена, если параметр Access-Control-Allow-Orign установлен в значение “*” и в запросе присутствуют пользовательские данные (в нашем случае cookies – помните поле withCredentials в скрипте), т.е. такой запрос все равно будет блокироваться, не смотря на то, что доступ открыт всем. Всем, но только без передачи пользовательских данных в запросе. Но если убрать cookies из запроса, то отправка такого запроса в нашем случае теряет всякий смысл.
И всетаки, данную атаку можно реализовать. Но для этого придется нехило так заморочится. Потому что отправка такого запроса будет возможна только с доверенного домена.
То есть, если параметр настроен следующим образом:
Access-Control-Allow-Orign: “white-example.com” ,
что по сути является правильным, то отправить такой запрос вместе с пользовательскими данными можно будет только с сайта white-example.com. А для этого у вас должны быть возможность разместить на этом домене свой скрипт, а значит - довереный сайт придется взломать.
Провернуть это можно, если вы, например нашли на довереном сайте XSS – тогда вы просто сможете внедрить скрипт на страницу, а потом заманить на эту страницу пользователя. Таким образом получится целая цепочка – XSS на одном домене поможет незаметно проэксплутировать CSRF на другом.
Как я уже сказал – способ апгрейда весьма спорный. Да, в случае успеха пользователь вообще ничего не заметит, но для этого придется скомпроментировать аж два домена. В любом случае, я считаю что про него стоит знать, а пытаться его применить или нет – решать только вам.
(Про этот способ я написал, так как в начале своего пути сам крутил подобный кейс и здорово подзавис на этом моменте. Описывал все исходя из своего опыта, но не исключаю что я мог отстать от жизни или просто не до конца разобраться в теме. Поэтому, если я вдруг ошибся, а вы вдруг знаете как обойти механизмы защиты CORS в данном случае и напишете как это сделать – буду очень благодарен=) )
Разумеется CSRF может работать не только с важными данными. Например, CSRF хорошо работает в симбиозе с XSS и в обратную сторону. Представьте ситуацию: есть уязвимая к отраженному XSS страница, уязвимый параметр на которой передается в POST запросе. Как в таком случае реализовывать атаку, если простой передачи ссылки недостаточно – параметр-то в POST, а не в GET ? В этом случае и может прийти на помощь CSRF. Если подлиность POST запроса не проверяется (А она может и не проверяться, если передаваемые данные не являются чувствительными), то можно вполне, так же как и в примере, создать форму, которая будет отправляться на уязвимый сайт. А так как после отправки формы жертву выкинет обратно на уязвимый сайт, он получит уже ответ на POST запрос, где и будет внедренный JS-код.
Надеюсь, ход мыслей понятен. Так что, CSRF может быть полезна не только для изменений важных данных без ведома пользователя.
Защита:
Для защиты от CSRF применяются разные способы, начиная от очень просто реализованых, но при этом не слишком удобных для самих пользователей, заканчивая сложными способами, которые для рядового пользователя почти не заметны.
1. Двойная авторизация.
Самый простой способ защититься от CSRF – запрашивать пароль каждый раз, когда пользователь пытается совершить какие-либо действия с чувствительными данными. Наглядным примером является как раз таки запрос старого пароля при смене его на новый – это делается не только для дополнительной безопасности при случайном получении доступа к аккаунту третьих лиц, но и для защиты от CSRF, так как не зная текущий пароль, злоумышленик не сможет сформировать запрос для смены его на новый. Способ простой и надежный, обойти его можно только в случае, если атакующий узнает пароль/секретную фразу. Так же, метод достаточно прост в реализации, но далеко не очень удобный для пользователей. Представьте что будет, если работа с подобными данными производится часто. Пользователю постоянно придется вводить пароль при малейшей попытке обратиться или изменить важные данные, что не очень удобно. Поэтому, этот способ защиты реализуется как правило для наиболее “опасных”, с точки зрения подвержености уязвимости, функций – например, смена пароля, как я упомянул выше. В других же случаях используются другие методы, о которых я рассскажу далее.
2.Использование токенов
Наверное самый популярный способ защиты от CSRF в наше время. Есть несколько вариантов реализаций такой защиты, но самый классический способ – синхронизирующийся токен. Его суть заключается в том, что для каждой операции с чувствительными данными генерируется токен – уникальный отпечаток, который служит для идентификации пользователя и проверки подлинности запроса.
Звучит не совсем понятно, поэтому поясню на примере:
Пользователь авторизуется на сайте. Сайт тут же генерирует специальный отпечаток, который и сохраняет себе в хранилище сессии, и передает пользователю. И теперь, при каждом действии, пользователь будет прикреплять к запросу этот отпечаток (токен). В случае если токена не будет – запрос не будет работать. Токены хранятся на компьютере пользователя и отправляются только на домен, с которого были установлены. Живут такие токены только на время сесии и для каждой новой сессии генерируется новый токен. Так как атакующий не может знать токен, а автоматически такие заголовки к форме не крепятся, в отличие от cookies, классическая атака CSRF становится невозможна.
Это не единственный способ реализации защиты через токены, есть так же подходы по типу double cookie, при которых уникальный токен генерируется не на время сессии, а для каждого отдельного запроса, и передается клиенту дважды, для дальнейшего получения обоих токенов и их сверки, или еще возможен подход через шифрование токена, когда внутрь токена шифруются данные, а затем проверяются при помощи закрытого ключа на сервере.
Для обхода таких методов защиты с токенами используются разные подходы. По хорошему, токен дожен иметь ограниченое время жизни и при этом не должно быть возможности его перехватить атакующему. Но для перехвата может быть использована, например, XSS, так как JS имеет доступ к данным пользователя. Или для способа с double-cookie токенами могут быть использованы особенности работы http с поддоменами.
Так же, последнее время набирает обороты технология same site. Этот атрибут для cookies позволяет указать, что принимать запрос с cookies следует только от того сайта, на котором cookies были установлены. Это позволяет реализовать защиту от csrf без каких-либо дополнительных реализаций в коде.
Заключение:
CSRF уязвимости медленно умирают, так как защита от них эволюционирует, да и сейчас почти каждый джун-вебщик знает что это за уязвимость и как от нее защищаться. Если лет 7 назад эта уязвимость была практически в каждом первом сайте, то сейчас количество сайтов, подверженных этой уязвимости очень сильно сократилось.
Но, не смотря на это, CSRF-уязвимости никак не вымрут до конца. Они до сих пор встречаются даже в очень крупных проектах (отчеты с bug bounty программ тому подтверждение), а про более мелкие проекты и говорить нечего. Да, количество сайтов, подверженных уязвимости медленно снижается, но происходит это гораздо медленнее, чем должно бы. Поэтому – умение эксплуатировать эту уязвимость достаточно важный навык в арсенале атакующего в наши дни.
Ну а на этом я с темой CSRF заканчиваю, всем добра !
©Urob0ros, специально для форума xss.pro.