Как защититься от csrf атак. Что такое CSRF? Значение термина CSRF

Как защититься от csrf атак. Что такое CSRF? Значение термина CSRF

Статья эвакуирована с DrupalDance.com

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

Итак, подделка межсайтовых запросов (анг. Сross Site Request Forgery, или, сокращенно, CSRF): что это такое и с чем его едят.

CSRF - это вид атак на посетителей веб-сайтов, использующий недостатки протокола HTTP. Если жертва заходит на сайт, созданный злоумышленником, от её лица тайно отправляется запрос на другой сервер (например, на сервер платёжной системы), осуществляющий некую вредоносную операцию (например, перевод денег на счёт злоумышленника). Для осуществления данной атаки, жертва должна быть авторизована на том сервере, на который отправляется запрос, и этот запрос не должен требовать какого-либо подтверждения со стороны пользователя.

Данный тип атак, вопреки распространённому заблуждению, появился достаточно давно: первые теоретические рассуждения появились в 1988 году, а первые уязвимости были обнаружены в 2000 году.

Одно из применений СSRF - эксплуатация пассивных XSS, обнаруженных на другом сервере. Так же возможны отправка спама от лица жертвы и изменение каких-либо настроек учётных записей на других сайтах(например, секретного вопроса для восстановления пароля).

Живой пример

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

node_destroy.module /** * Реализация hook_menu(). Регистрирует наш коллбек в системе меню. */ function node_destroy_menu() { $menu["node/%node/destroy"] = array("page_callback" => "node_destroy", "page_arguments" => array(1), "access_arguments" => array("administer nodes"), "type" => MENU_CALLBACK,); return $menu; } /** * Реализация коллбека. */ function node_destroy($node) { if ($node->nid) { node_delete($node->nid); print("SUCCESS"); } // в коллбеках для аякса почти всегда надо принудительно завершать скрипт, // чтобы не выводить оформление сайта вместе с вашими данными exit(); } /** * Реализация hook_link(). Добавляем свою ссылку в служебные ссылки ноды. */ function node_destroy_link($type, $node = NULL, $teaser = FALSE) { switch ($type) { case "node": // если эта функция вызывается, значит мы выводим ссылки ноды, // а это значит, что нам и скрипты нужны $path = drupal_get_path("module", "node_destroy"); drupal_add_js($path ."/node_destroy.js"); // собственно, добавление ссылки $links["node_destroy"] = array("title" => t("Destroy node"), "href" => "node/$node->nid/destroy", "attributes" => array("class" => "node_destroy_link"),); break; } return $links; } node_destroy.js // Таким нехитрым путем правильно инициализировать некие действия // вместо обычного $(document).ready(function() { ... }) Drupal.behaviors.node_destroy = function(context) { // Мы перебираем все наши ссылочки и навешиваем на них аякс запросы. // Заметьте необычный селектор. Он предотвратит двойное навешивание обработчиков. $(".node_destroy_link:not(.processed)", context).addClass("processed").click(function(){ href = $(this).attr("href"); $.ajax({ type: "GET", url: href, success: function(result){ // SUCCESS нам возвращает наш коллбек меню, если все замечательно if (result != "SUCCESS") { alert("Error"); } } }); }); }

И все бы хорошо, но в один солнечный день, на сайт приходит злой тролль... Или более жизненная ситуация - озлобленный бывший сотрудник приходит на сайт и пытается его поломать. Помня старый опыт, он пробует зайти по адресу http://site.ru/node/123/destroy , но получает от ворот поворот, так как уже не имеет прав на удаление материалов.

И тут, в порыве деструктивного креатива, он создает ноду с таким контетом:

Что происходит в этот момент? Никакая картинка, естественно, не подгрузится, но браузер тролля выполнит запрос на этот путь с прежним результатом.

Смирившись с неудачей, тролль уходит с сайта. Через день, администратор сайта замечает эту мусорную ноду, заходит в нее и удаляет. А вернувшись в список материалов, не находит в нем ноды с айдишником 123. Атака удалась. Занавес.

Для тех, кто не понял, когда администратор зашел в ноду, его браузер тоже ломанулся по ссылке картинки. Но здесь уже прав доступа хватило, и нода была успешно удалена, а админ даже ничего не заметил.

Как избежать CSRF уязвимостей?

Ответ - использовать уникальные ссылки для действий по изменению данных. Как это возможно? В друпале используется метод токенизации ссылок. Это означает, что к ссылке активного действия, прибавляется уникальный параметр, который проверяется при осуществлении самого действия. В друпале сгенерировать такой параметр можно функцией drupal_get_token() . Проверить -drupal_valid_token() . Токен генерируется на основе подаваемого значения, сессии пользователя, а также приватного ключа сайта, что практически сводит на ноль вероятность генерации вредителем правильного токена.

Внесем изменения в наш модуль. Начнем с выставления правильной ссылки:

Function node_destroy_link($type, $node = NULL, $teaser = FALSE) { switch ($type) { case "node": $path = drupal_get_path("module", "node_destroy"); drupal_add_js($path ."/node_destroy.js"); $links["node_destroy"] = array("title" => t("Destroy node"), "href" => "node/$node->nid/destroy", "attributes" => array("class" => "node_destroy_link"), // query - это все GET параметры, т.е. все что в ссылке находится после знака вопроса // мы добавляем параметр token "query" => "token=". drupal_get_token("node_destroy_". $node->nid)); break; } return $links; }

Как вы помните, мы шлем аякс запрос по адресу, который зашит в ссылке, поэтому в коллбеке нам остается только проверить $_GET стандартным способом.

Function node_destroy($node) { if ($node->nid && isset($_GET["token"]) && drupal_valid_token($_GET["token"], "node_destroy_". $node->nid)) { node_delete($node->nid); print("SUCCESS"); } exit(); }

Cross Site Request Forgery, или CSRF, является атакой, которая осуществляется в тот момент, когда вредоносный сайт, письмо, сообщение, приложение или что-либо иное заставляет браузер пользователя выполнить некоторые действия на другом сайте, где этот пользователь уже аутентифицирован. Зачастую это происходит без ведома пользователя.

Вред от CSRF-атаки зависит от сайта, принимающего действие. Вот пример:

  • Бобвходитвсвойличныйкабинетвбанковскомонлайнклиенте, выполняет какие-то операции, но не разлогинивается.
  • Бобпроверяетсвоюпочтуикликаетнассылку,ведущую на незнакомый сайт.
  • Незнакомыйсайтделаетзапросконлайн-клиентубанка Боба на перевод денег, передавая информацию в cookie Боба, сохранившуюся с предыдущей его сессии.
  • СайтбанкаБобапринимаетзапросотнезнакомого(вредоносного) сайта без использования CSRF-токена и выполняет перевод.
  • Еще более интересна ситуация, когда ссылка на вредоносный
    сайт может содержаться в валидном HTML, благодаря чему Бо-
    бу даже не придется нажимать на ссылку: . Если устройство Боба (например, браузер) отрису-
    ет это изображение, оно сделает запрос к malicious_site.com и потенциально совершит CSRF-атаку.

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

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

    Теперь, когда мы знаем о CSRF и CSRF-токенах, Cross Origin Resource Sharing (CORS) становится более понятным, хотя возможно, я просто заметил это по мере исследования новых целей. В общем, CORS ограничивает список ресурсов, которые могут получать доступ к данным. Другими словами, когда CORS используется для защиты сайта, вы можете написать Javascript для вызова целевого приложения, прочитать ответ и сделать другой вызов, если целевой сайт вам это позволяет.

    Если это кажется непонятным, то попробуйте с помощью Javascript сделать вызов к HackerOne.com/activity.json, прочитать ответ и сделать второй вызов. Вы также увидите важность этого и потенциальные способы обхода в примере #3 ниже.

    Наконец, важно заметить (спасибо Джоберту Абме за указание на этот момент), что не каждый запрос без CSRF-токена является невалидным. Некоторые сайты могут выполнять дополнительные проверки, такие, как сравнение заголовка исходящей стороны (хотя это не является гарантией безопасности и есть известные случаи обхода). Это поле, которое идентифицирует адрес страницы, которая ссылается на запрашиваемый ресурс. Другими словами, если исходящая сторона (реферер)

    выполняет POST-вызов не с того же сайта, который получил HTTP-запрос, сайт может не позволить вызову достигнуть того же эффекта, что достигается с использованием CSRFтокена. Кроме того, не каждый сайт использует терми csrf при создании или определении токена. Например, на Badoo это параметр rt, как описано ниже.

    Прочтите руководство по тестированию на OWASP Testing for CSRF

    Примеры 1. Экспорт установленных пользователей Shopify

    Сложность: Низкая
    Url: https://app.shopify.com/services/partners/api_clients/XXXX/export_installed_users


    Описание:

    API Shopify предоставляет эндпоинт для экспорта списка установленных пользователей через URL, показанный выше. Уязвимость заключалась в том, что сайт мог сделать запрос к этому эндпоинту и получить в ответ информацию, поскольку

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

    1 2 csrf 3 4 7 8 9

    В этом примере при простом посещении страницы Javascript отправляет форму, которая включает GET-запрос к API Shopify, используя cookie Shopify, установленные в браузере жертвы.

    Выводы

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

    2. Отключение Shopify от Twitter

    Сложность: Низкая
    Url: https://twitter-commerce.shopifyapps.com/auth/twitter/disconnect

    Shopify предоставляет интеграцию с Twitter, что позволяет владельцам магазинов постить твиты о своих продуктах. Подобным образом сервис позволяет и отключить аккаунт Twitter от связанного магазина.

    URL для отключения аккаунта Twitter от магазина указан выше. При совершении запроса Shopify не валидировал CSRFтокен, что позволило злоумышленнику создать фальшивую ссылку, клик на которую приведет ничего не подозревающего владельца магазина к отключению его магазина от Twitter.

    Описывая уязвимость, WeSecureApp предоставил следующий пример уязвимого запроса обратите внимание, что использование тега img делает вызов к уязвимому URL:

    1 GET /auth/twitter/disconnect HTTP/1.1 2 Host: twitter-commerce.shopifyapps.com 3 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.1 4 1; rv:43.0) Gecko/20100101 Firefox/43.0 5 Accept: text/html, application/xhtml+xml, application/x 6 ml 7 Accept-Language: en-US,en;q=0.5 8 Accept-Encoding: gzip, deflate 9 Referer: https://twitter-commerce.shopifyapps.com/accou 10 nt 11 Cookie: _twitter-commerce_session=REDACTED 12 >Connection: keep-alive

    Поскольку браузер выполняет GET-запрос для получения изображения по переданному URL и CSRF-токен не валидируется, пользовательский магазин теперь отключен:

    1 2 3 5 6

    Выводы

    В этой ситуации, описанная уязвимость могла быть найдена при использовании проксисервера, такого, как Burp или Firefox Tamper Data, достаточно было взглянуть на запрос, отправляемый к Shopify и увидеть, что этот запрос был осуществлен с помощью GET-запроса. Поскольку это было разрушительное действие и GETзапросы не должны изменять данные на сервер, это стоит исследовать.

    3. Полный захват аккаунта на Badoo

    Сложность: Средняя
    Url: https://badoo.com
    Ссылка на отчет: https://hackerone.com/reports/12770312
    Дата отчета: 1 апреля 2016
    Выплаченное вознаграждение: $852

    Описание:

    Если вы взглянете на Badoo, выпоймете, что они защищаются от CSRF включением в URL параметра rt, который имеет длину всего в 5 символов (по крайней мере, на момент написания). Хотя я заметил это, когда Badoo запустил кампанию на HackerOne, я не нашел способа это использовать. Однако, Махмуд Джамал (zombiehelp54) нашел.

    Поняв значение параметра rt, он также заметил, что параметр возвращался в почти всех json-ответах. К сожалению, это не принесло пользы, поскольку CORS защищает Badoo от атак, читая эти ответы. Махмуд продолжил искать.

    Оказалось, что файл https://eu1.badoo.com/worker-scope/chrome-ser содержит значение rt. Еще лучше было то, что этот файл
    мог быть произвольно прочитан атакующим и не требовал от
    жертвы никаких действий кроме посещения вредоносной веб-страницы. Вот код, который он предоставил:

    1 2 3 Badoo account take over 4 6 7 8 9 function getCSRFcode(str) { 10 return str.split(’=’); 11 } 12 window.onload = function(){ 13 var csrf_code = getCSRFcode(url_stats); 14 csrf_url = ’https://eu1.badoo.com/google/verify.phtml?c 15 ode=4/nprfspM3yfn2SFUBear08KQaXo609JkArgoju1gZ6Pc&authu 16 ser=3&session_state=7cb17 4b560..a810&prompt=none&rt=’+ csrf_code; 18 window.location = csrf_url; 19 }; 20

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

    Выводы

    Где дым, там и огонь, Здесь Махмуд заметил, что параметр rt возвращался в разных местах, в конкретных json-ответах. Поэтому он правильно предположил, что он может быть показан где-то, где его можно будет использовать в этом случае в js файле.

    Итоги

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

    Как правило, формы стандартно защищены фреймворками вроде Rails если сайт выполняет POST-запрос, но API могут

    быть отдельной историей. Например, Shopify написан в основном на основе фреймворка Ruby on Rails, который предоставляет защиту от CSRF-атак для всех форм по умолчанию (хотя её и можно отключить). Однако, очевидно, что это не обязательно так для API, созданных с помощью этого фреймворка. Наконец, обращайте внимание на вызовы, которые изменяют данные на сервере (такие, как действие удаления) и выполняются с помощью GET-запроса.

    От автора: поводом к записи данного урока послужил вопрос на нашем форуме, который звучал следующим образом — как защитить сайт от CSRF -атак? Конечно мы сразу же ответили по данной теме и привели небольшой алгоритм по реализации механизма защиты. Но так как, скорее всего форум читают, далеко не все наши читатели я решил записать отдельный урок по вышеуказанному вопросу.

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

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

    CSRF – это аббревиатура образованная английскими словами Cross-Site Request Forgery, что означает межсайтовая подделка запросов. Данный термин введен уже достаточно давно еще 2001 году Питером Воткинсом, но говорить о возможных атаках подобного рода начали еще в далеком 1988 году. При этом заметьте, прошло уже достаточное количество времени, но все же данной атаке подвергается большая часть веб-сайтов интернета. Сразу же возникает вопрос – почему так? И ответ довольно прост и заключается в том, что уязвимость к атакам CSRF — это не ошибка кода приложения, а следствие вполне обычной работы браузера и веб-сервера.

    Суть атаки заключается в том, что злоумышленник может выполнить на незащищенном сайте различные действия от имени другого зарегистрированного (авторизированного) пользователя. Другими словами данный тип атаки предусматривает посещение пользователем сайта злоумышленника, что в свою очередь приводит к тому, что незаметно для него выполняются некоторые заранее прописанные действия на другом сайте или сервисе, на котором этот пользователь в данный момент авторизован.

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

    Теперь давайте рассмотрим действие указанной атаки на примере тестового сайта.

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

    Данная страница вполне обычная, но в глаза бросается чек-бокс “Member”, который используется для сохранения данный авторизации в куках браузера. Собственно данный механизм очень удобен пользователям, так как упрощает повторный доступ к странице, но крайне не желателен с точки зрения безопасности. Но все же ради посетителей часто приходится идти на определенные уступки.

    Код отправки сообщений двумя методами (GET и POST) выглядит примерно следующим образом:

    //Отправка сообщения if($this->isGet() && !empty($_GET["email"])) { $body = "Hello this is message form - ".$this->user["name"]; $body .= " Content - from GET - ".$_GET["content"]."From - ".$_GET["email"]; mail("mail@yandex.ru","New message",$body); } if($this->isPost()) { $body = "Hello this is message form - ".$this->user["name"]; $body .= " Content - FROM POST - ".$_POST["content"]."From - ".$_POST["email"]; mail("mail@yandex.ru","New message",$body); }

    //Отправка сообщения

    if ($ this -> isGet () && ! empty ($ _GET [ "email" ] ) ) {

    $ body = . $ this -> user [ "name" ] ;

    $ body . = " Content - from GET - " . $ _GET [ "content" ] . "From - " . $ _GET [ "email" ] ;

    if ($ this -> isPost () ) {

    $ body = "Hello this is message form - " . $ this -> user [ "name" ] ;

    $ body . = " Content - FROM POST - " . $ _POST [ "content" ] . "From - " . $ _POST [ "email" ] ;

    mail ("mail@yandex.ru" , "New message" , $ body ) ;

    Теперь, рассмотрим еще один сайт – сайт хакера.

    На котором он может разместить, довольно простой, но очень эффективный код,если необходимо произвести атаку по запросу типа GET:

    < img src = "http://localhost/csrf/index.php?option=admin&email=haker@mail.ru&content=Hello world" >

    То есть, по сути, мы видим тег img, в атрибуте src которого располагается путь к сайту, предполагаемого для атаки, с набором необходимых параметров, для выполнения конкретного действия. В нашем случае это отправка сообщения, а значит, теперь злоумышленнику достаточно заманить пользователя на текущий сайт и незаметно от него, будет осуществлен запрос к интересующему сайту, так как браузер будет пытаться загрузить изображение, путь к которому указан в атрибуте src. При этом мы помним, что в куках браузера хранятся данные для авторизации и, конечно же, приложение сразу же их использует и соответственно вышеуказанный запрос успешно будет обработан сервером. А это означает, что будет отправлено сообщение от имени пользователя.

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

    Точно такая же ситуация обстоит и с отправкой запроса методом POST, единственно в этом случае злоумышленник создаст на своем сайте форму, которая будет автоматически отправляться при помощи JavaScript ,как только посетитель зайдет на данный сайт.

    Таким образом защищаться от атак подобного рода нужно обязательно и единственный наиболее эффективный способ защиты – это использование специальных токенов.

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

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

    Собственно на данном этапе текстовая часть урока завершена и продолжим говорить поданной теме мы уже в видео версии. При этом мы рассмотрим способы генерации токенов и практически реализуем алгоритм работы защиты, который описан выше. А сейчас давайте прощаться. Удачного кодирования!!!

    CSRF (Сross Site Request Forgery - подделка межсайтовых запросов) Так же известен как XSRF. Данный вид атак на посетителей интернет-сайтов использует недостатки HTTP протокола. Если жертва зайдет на сайт, который специально был создан злоумышленником, то от ее лица тайно отправится запрос на сторонний сервер (например сервер электронных платежей), осуществляющий некую вредоносную операцию (перевод денег на счет взломщика). Для успеха данной атаки, жертва должна быть авторизована на сервере, на который отправится запрос. Сам запрос не должен требовать подтверждения со стороны пользователя.

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

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

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

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


    просмотров