Безопасный код: Подделка межсайтовых запросов

Прислано: neochief

пн, 16/02/2009 - 21:59

Другие статьи по теме:


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

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

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

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

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

Живой пример

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

node_destroy.module

<?php
/**
 * Реализация 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,
  );
}

/**
 * Реализация коллбека.
 */
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, но получает от ворот поворот, так как уже не имеет прав на удаление материалов.

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

<img src="http://site.ru/node/123/destroy" />

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

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

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

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

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

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

<?php
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 стандартным способом.

<?php
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();
}
?>

via DrupalDance

Поддержите на хабре, пожалуйста

Комментарии


Настройки просмотра комментариев

Выберите нужный метод показа комментариев и нажмите "Применить"
Опубликовано Ветер в пн, 16/02/2009 - 22:33.

Этот дырявый модуль в ядре или сторонний?


Опубликовано neochief в пн, 16/02/2009 - 23:04.

Сторонний.


Опубликовано Stan.Ezersky в вт, 17/02/2009 - 00:27.

Поддержал на Хабре. Спасибо за разбор полётов, вовремя. Я недавно с друпалом и такие вещи в изучении незаменимая вещь. Она помогает избежать дальнейших ошибок. Еще раз спасибо, neochief!


Опубликовано jeehadina в вт, 17/02/2009 - 03:37.

зачет!
Спасибо, что делишься своим добытым опытом.
Еще раз спасибо!


Опубликовано Химический Али в вт, 17/02/2009 - 07:10.

Спасибо, принял к сведению.


Опубликовано VladSavitsky в вт, 17/02/2009 - 07:54.

Основательная работа. Спасибо. Подшил в раздел "Безопасность".

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


Опубликовано sadmin в вт, 17/02/2009 - 08:31.

Автору большое спасибо та тему, заметка очень актуальна.


Опубликовано astal в вт, 17/02/2009 - 11:12.

старые грабли на новый лад.
раньше этим на drupal.ru и на хабре поднимали пузомерки на темы и пользователей, теперь нужно придумывать специальные модули для показа этих проблем?

за вариант обхода данной уязвимости - спасибо, но как заметили эта ошибка у стороннего модуля(пускай возможно и популярного), а на сторонних модулях найти можно как SQL-инъекции и не менее популярные XSS

они были и к сожалению будут появляться новые...


Опубликовано gorr в вт, 17/02/2009 - 11:41.

Спасибо, Александр, будем внимательнее.


Опубликовано Valeratal в вт, 17/02/2009 - 12:20.

спасибо, пример очень понравился :)
поддержал на хабре

а что если, вставить ссылку вида node/номер/delete

сработает?


Опубликовано neochief в вт, 17/02/2009 - 12:29.

"astal" написал(а):

они были и к сожалению будут появляться новые

Главное, чтобы оставались люди, готовые улучшать положение вещей. Причитания точно не помогут, а накрутку можно было пофиксить еще 2 года назад. Чего ж никто не пофиксил?


Опубликовано Ветер в вт, 17/02/2009 - 13:11.

А не проще сделать обработку тега IMG.
Если адрес картинки не оканчивается разрешенным расширением, то запретить запись ноды.


Опубликовано igor701 в вт, 17/02/2009 - 14:10.

Хороший пример!

Думаю, выход - передавать в запросе зашифрованное число-сессию вида f(секретный код, IP пользователя, время). Заодно по истечении времени - уничтожать.

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


Опубликовано neochief в вт, 17/02/2009 - 14:37.

"igor701" написал(а):

Думаю, выход - передавать в запросе зашифрованное число-сессию

Почитайте статью, и не выдумывайте велосипед.


Опубликовано Kremenetskiy в вт, 17/02/2009 - 17:32.

Спасибо за информацию!


Опубликовано seaji в вт, 17/02/2009 - 18:17.

"Valeratal" написал(а):

а что если, вставить ссылку вида node/номер/delete

Не получится, там нужно подтверждение через нажатие кнопки.

Еще вопрос. А время жизни токена какое?


Опубликовано seaji в вт, 17/02/2009 - 18:19.

Кстати, по моему Вконтакте так спам рассылают, или нет?


Опубликовано neochief в вт, 17/02/2009 - 21:21.

Время жизни токена = время жизни сессии.


Опубликовано Demimurych в ср, 18/02/2009 - 17:08.

Valeratal написал(а):

спасибо, пример очень понравился :)
поддержал на хабре

а что если, вставить ссылку вида node/номер/delete

сработает?

конечно. Только нода не удалится. Потому как по этому пути всегда (если руками не патчить) возвращается форма - да нет


Опубликовано inc в чт, 19/02/2009 - 22:20.

хорошо если внесут в ядро друпала защиту от этого, а то ведь

<img src="/logout" />

работает


Опубликовано seaji в чт, 19/02/2009 - 22:50.

Картинки это зло :)


Опубликовано Ветер в чт, 19/02/2009 - 23:07.

Нда, весело админу будет. Причем удалить ноду можно будет только из admin/content/node
Вариант
- включить тригерры и проверять наличие таких подлостей.

Что там у нас еще без подтверждения работает?:)


Опубликовано Ветер в чт, 19/02/2009 - 23:05.

Даешь цифро-буквенные картинки ! :)


Опубликовано fasdalf@fasdalf.ru в пт, 20/02/2009 - 10:03.

img == XSS

Поэтому и рулят  image  imce и  imagefield


Опубликовано ingumsky@drupal.org в чт, 26/02/2009 - 16:47.

Спасибо! Очень интересный материал.


Опубликовано Demimurych в чт, 19/03/2009 - 09:08.

хм.

Если следовать неписанному стандарту, то гет запросом можно ТОЛЬКО ПОЛУЧАТЬ информацию, но не модифицировать.

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


Опубликовано fasdalf@fasdalf.ru в чт, 19/03/2009 - 15:33.

jquery и post делать умеет.


Опубликовано Demimurych в чт, 19/03/2009 - 19:53.

"fasdalf@fasdalf.ru" написал(а):

jquery и post делать умеет.

вы это к чему?

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


Опубликовано fasdalf@fasdalf.ru в пт, 20/03/2009 - 13:45.

нет большой разницы между POST и GET. Post запрос не на много сложнее подделать.
Другое дело, что написанное здесь можно и нужно применять и для обработки POST-форм.


Опубликовано Demimurych в сб, 21/03/2009 - 11:06.

"fasdalf@fasdalf.ru" написал(а):

нет большой разницы между POST и GET. Post запрос не на много сложнее подделать.
Другое дело, что написанное здесь можно и нужно применять и для обработки POST-форм.

Давайте говорить предметно.
Не на много сложнее говорите?
Живой пример в студию.

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

И так, по клику на кнопке я посылаю ПОСТ запрос который обрабатывает моя функция и удаляет ноду. Я посылаю параметров просто nid ноды. Никаких токенов не использую.

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

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


Опубликовано neochief в сб, 21/03/2009 - 14:02.

Да ну что вы прицепились к человеку со своим постом. Вам описали решение проблемы в случаях, когда POST использовать не представляется возможным. Джейквери, джейквери.. а если джаваскрипт вырублен и нужно сделать действие БЕЗ формы? Что тогда делать? Везде пихать кнопки и подтверждения? Каким тогда будет интерфейс?

И почему, раз пост такой непробиваемый по-вашему, токены автоматом вставляеются во все друпаловские формы? А я вам отвечу — потому, что это надежнее простого поста.


Опубликовано Demimurych в сб, 21/03/2009 - 15:35.

"neochief" написал(а):

И почему, раз пост такой непробиваемый по-вашему, токены автоматом вставляеются во все друпаловские формы? А я вам отвечу — потому, что это надежнее простого поста.

Вы простите ушли от темы разговора.

Безопасный код: Подделка межсайтовых запросов

тут пост практически не пробиваем. Будете спорить? Примеры в студию.

Покажите мне реальную ситуацию когда пост использовать нельзя это раз
Расскажите мне почему может быть невозможно использовать форму - это два

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

Не уходите от темы. Мы обсуждаем именно подделку межсайтовых запросов, а не пост или гет в целом.

Зачем городить огород с токенами, если все это решается банальным постом?

http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html

9.1.1 Safe Methods

Implementors should be aware that the software represents the user in their interactions over the Internet, and should be careful to allow the user to be aware of any actions they might take which may have an unexpected significance to themselves or others.

In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe". This allows user agents to represent other methods, such as POST, PUT and DELETE, in a special way, so that the user is made aware of the fact that a possibly unsafe action is being requested.

Naturally, it is not possible to ensure that the server does not generate side-effects as a result of performing a GET request; in fact, some dynamic resources consider that a feature. The important distinction here is that the user did not request the side-effects, so therefore cannot be held accountable for them.


Опубликовано neochief в сб, 21/03/2009 - 21:35.

Я не могу представить вам кода, которым можно взломать грамотно написанную POST форму. Вы это хотели услышать?

В то же время, я могу представить несколько вариантов, где форму использовать неудобно. Главный — если вы хотите упростить некие действия, не добавляя лишних подтверждений. Да, и спасибо вам за ссылку на w3, я уже там был. Это те чуваки, кторых я очень уважаю, но которые же ввели неудобную бокс-модель в xhtml.


Опубликовано inc в сб, 21/03/2009 - 23:07.

Demimurych, отличная идея.

Если взять, к примеру, действие logout, то достаточно ссылку заменить на кнопку формы, отсылающую post-запрос с параметром(input type=hidden).
А кнопку можно стилизовать как угодно, пользователь и не догадается, что это кнопка.
И никаких подтверждений!


Опубликовано Demimurych в сб, 21/03/2009 - 23:11.

"neochief" написал(а):

Я не могу представить вам кода, которым можно взломать грамотно написанную POST форму. Вы это хотели услышать?

Нет

"neochief" написал(а):

В то же время, я могу представить несколько вариантов, где форму использовать неудобно.

Код мне не обязателен.
Достаточно описания не надуманного (реального) примера, где использование форм имеет ряд существенных недостатков из которых следует что требуется использовать гет запрос.

На всякий случай еще раз повторю задачу.

Вводные, сайт работает не используя javascript ов (потому как если использует, то выбор в сторону пост очевиден не так ли?)

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


Опубликовано Demimurych в сб, 21/03/2009 - 23:13.

"inc" написал(а):

Demimurych, отличная идея.

Идея не моя.

Это стандарт де факто для организации подобного рода запросов. Которым пренебрегают разработчики и выдумывают велосипеды в виде использования токенов.


Опубликовано fasdalf@fasdalf.ru в чт, 26/03/2009 - 18:38.

Возможно, начитавшись этого топика, некто lut4rp накатил патч, который прислал Alexandr Shvets. Так они залатали знаменитую дыру в Vote Up/Down.


Опубликовано neochief в чт, 26/03/2009 - 15:11.

Обратите внимание на то, кто запостил репорт:
http://drupal.org/node/413938

Он же и патчи сделал, а lut4rp их всего лишь закомитил ;)


Опубликовано HIgor1968 в чт, 26/03/2009 - 18:08.

Сам себя не похвалишь:)......


Опубликовано fasdalf@fasdalf.ru в чт, 26/03/2009 - 18:38.

исправил.


Опубликовано Demimurych в чт, 26/03/2009 - 20:41.

"neochief" написал(а):

Обратите внимание на то, кто запостил репорт:
http://drupal.org/node/413938

Он же и патчи сделал, а lut4rp их всего лишь закомитил ;)

Где Ваши примеры того почему следует использовать ГЕТ? Вместо ПОСТА?


Опубликовано fasdalf@fasdalf.ru в пт, 27/03/2009 - 08:55.

Хочу узнать, как текст цитаты связан с выбором post/get

BTW вот вам и пример. Представьте себе галвную страницу с 10+ формами и у каждой 2 сабмита.


Опубликовано Demimurych в вс, 29/03/2009 - 11:47.

"fasdalf@fasdalf.ru" написал(а):

Хочу узнать, как текст цитаты связан с выбором post/get

BTW вот вам и пример. Представьте себе галвную страницу с 10+ формами и у каждой 2 сабмита.

Текст цитаты связан с человеком, который сказал что покажет и не показал.

"fasdalf@fasdalf.ru" написал(а):

BTW вот вам и пример. Представьте себе главную страницу с 10+ формами и у каждой 2 сабмита.

Да и что?
Попробуйте, вы увидите что страница от этого не страдает.

Или, если я вас не правильно понял и вас беспокоят именно серые кнопки?


Опубликовано neochief в вс, 29/03/2009 - 18:08.

Demimurych, вам что-то доказывать бесполезно.


Опубликовано seaji в вс, 29/03/2009 - 21:49.

Ага, кто то любит кулаками махать, а кто то просто делает полезное дело :)


Опубликовано fasdalf@fasdalf.ru в пн, 30/03/2009 - 12:37.

Таки беспокоят. А прямоугольные цветные бесят.
А про 300% прирост html кода на from destination=... я вообще молчу :)

Для меня тема GET vs POST закрыта.


Опубликовано Demimurych в вт, 31/03/2009 - 07:46.

"neochief" написал(а):

Demimurych, вам что-то доказывать бесполезно.

Дружище.
Вы тут доказать ничего не можете.

Я прошу вас примеры вы молчите.

Я дал Вам ссылки на авторитетные источники.

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

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


Опубликовано Demimurych в вт, 31/03/2009 - 08:14.

"seaji" написал(а):

Ага, кто то любит кулаками махать, а кто то просто делает полезное дело :)

Ну что решили хуями меряться?
Посмотрели бы секьюрити рассылки внимательнее что ли. Ну или бы ник поугуглили.
Какое это имеет отношение к делу?

Кто то делает дело и молчит.

А кто то размножает НЕПРАВИЛЬНЫЕ способы работы с подобного рода задачами.


Опубликовано Demimurych в вт, 31/03/2009 - 07:44.

"fasdalf@fasdalf.ru" написал(а):

Таки беспокоят. А прямоугольные цветные бесят.
А про 300% прирост html кода на from destination=... я вообще молчу :)

Для меня тема GET vs POST закрыта.

1. Вы что еще не используете gzip компрессию? Сравните сжатый код страницы в 100 килобайт и страницы в 50 килобайт.

2. Кнопки не обязательно должны выглядеть так как они выглядят по умолчанию. Они могут выглядеть так, что вы никогда не помете что это кнопка пока не посмотрите в код. htmlbook.ru в руки


Опубликовано fasdalf@fasdalf.ru в вт, 31/03/2009 - 10:45.

"типичный пользователь" может и не отличит. Он и jpg может за кнопку принять. Я - отличу. А htmlbook читать второй раз не собираюсь.
Засим прощайте.


Новое на сайте

Ссылки партнёров