Каков правильный способ определить, содержат ли входы строки HTML или нет?

При получении пользовательского ввода в формах я хочу определить, не содержат ли поля «имя пользователя» или «адрес» разметку, которая имеет особое значение в XML (RSS-каналах) или (X) HTML (при отображении).

Итак, какой из них является правильным способом определить, не введен ли введенный ввод каких-либо специальных символов в контексте HTML и XML?

if (mb_strpos($data, '<') === FALSE AND mb_strpos($data, '>') === FALSE) 

или

 if (htmlspecialchars($data, ENT_NOQUOTES, 'UTF-8') === $data) 

или

 if (preg_match("/[^\p{L}\-.']/u", $text)) // problem: also caches symbols 

Я пропустил что-нибудь еще, например, последовательности байтов или другие сложные способы получить метки разметки вокруг таких вещей, как «javascript:»? Насколько мне известно, все атаки XSS и CSFR требуют < или > вокруг значений, чтобы заставить браузер выполнять код (ну хотя бы из Internet Explorer 6 или новее) в любом случае – это правильно?

Я не ищу что-то для уменьшения или фильтрации ввода. Я просто хочу найти последовательности опасных символов, когда они используются в контексте XML или HTML. ( strip_tags() ужасно небезопасно. Как говорится в руководстве, он не проверяет неверный HTML.)

Обновить

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

Обновление 2: пример

  • Пользователь вводит ввод
  • if (mb_strpos($data, '<') === FALSE AND mb_strpos($data, '>') === FALSE)
  • Я его сохраняю

Теперь, когда данные находятся в моем приложении, я делаю с ним две вещи: 1) отображение в формате HTML – или 2) отображение внутри элемента формата для редактирования.

Первый из них безопасен в контексте XML и HTML

<h2><?php print $input; ?></h2>' <h2><?php print $input; ?></h2>' <xml><item><?php print $input; ?></item></xml> <xml><item><?php print $input; ?></item></xml>

Вторая форма более опасна, но она все равно должна быть безопасной:

<input value="<?php print htmlspecialchars($input, ENT_QUOTES, 'UTF-8');?>">

Обновление 3: Рабочий код

Вы можете загрузить созданный мной gist и запустить код в виде текстового или HTML-ответа, чтобы посмотреть, о чем я говорю. Эта простая проверка проходит http://ha.ckers.org XSS Cheat Sheet , и я не могу найти ничего, что делает это. (Я игнорирую Internet Explorer 6 и ниже).

Я начал еще одну награду, чтобы наградить кого-то, кто может показать проблему с этим подходом или слабость в ее реализации.

Обновление 4: запрос DOM

Это DOM, который мы хотим защитить – так почему бы просто не спросить об этом? Ответ Тимура приводит к следующему:

 function not_markup($string) { libxml_use_internal_errors(true); if ($xml = simplexml_load_string("<root>$string</root>")) { return $xml->children()->count() === 0; } } if (not_markup($_POST['title'])) ... 

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

 <?php $strings = array(); $strings[] = <<<EOD ';alert(String.fromCharCode(88,83,83))//\';alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//\";alert(String.fromCharCode(88,83,83))//--></SCRIPT>">'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT> EOD; $strings[] = <<<EOD '';!--"<XSS>=&{()} EOD; $strings[] = <<<EOD <SCRIPT SRC=http://ha.ckers.org/xss.js></SCRIPT> EOD; $strings[] = <<<EOD This is a safe text EOD; $strings[] = <<<EOD <IMG SRC="javascript:alert('XSS');"> EOD; $strings[] = <<<EOD <IMG SRC=javascript:alert('XSS')> EOD; $strings[] = <<<EOD <IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;> EOD; $strings[] = <<<EOD perl -e 'print "<IMG SRC=java\0script:alert(\"XSS\")>";' > out EOD; $strings[] = <<<EOD <SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT> EOD; $strings[] = <<<EOD </TITLE><SCRIPT>alert("XSS");</SCRIPT> EOD; libxml_use_internal_errors(true); $sourceXML = '<root><element>value</element></root>'; $sourceXMLDocument = simplexml_load_string($sourceXML); $sourceCount = $sourceXMLDocument->children()->count(); foreach( $strings as $string ){ $unsafe = false; $XML = '<root><element>'.$string.'</element></root>'; $XMLDocument = simplexml_load_string($XML); if( $XMLDocument===false ){ $unsafe = true; }else{ $count = $XMLDocument->children()->count(); if( $count!=$sourceCount ){ $unsafe = true; } } echo ($unsafe?'Unsafe':'Safe').': <pre>'.htmlspecialchars($string,ENT_QUOTES,'utf-8').'</pre><br />'."\n"; } ?> 

В комментарии выше, вы написали:

Просто запретите браузер обрабатывать строку как разметку.

Это совершенно другая проблема с тем, что в названии. Подход в названии обычно неправильный. Снятие тегов просто управляет входными данными и может привести к потере данных. Вы когда-нибудь пытались говорить о HTML в блоге, который разбивает теги? Разочарование.

Решение, которое обычно является правильным, заключается в том, чтобы делать так, как вы сказали в своем комментарии, – чтобы браузер не обрабатывал строку как разметку. Это – буквально взятое – невозможно. Вместо этого вы кодируете содержимое как HTML.

Рассмотрим следующие данные:

 <strong>Test</strong> 

Теперь вы можете посмотреть на это одним из двух способов. Вы можете посмотреть на него как на буквенные данные – последовательность символов. Вы можете посмотреть на него как на HTML-разметку, которая включает в себя сильное подчеркивание текста.

Если вы просто выгружаете это в HTML-документ, вы рассматриваете его как HTML. Вы не можете рассматривать это как литеральные данные в этом контексте. Вам нужен HTML, который будет выводить литералы. Вам нужно закодировать его как HTML.

Ваша проблема заключается не в том, что у вас слишком много HTML – это то, что у вас слишком мало. Когда вы выводите < , вы выводите необработанные данные в контексте HTML. Вам необходимо преобразовать его в &lt; , который является представлением HTML этих данных перед его выпуском.

PHP предлагает несколько различных вариантов для этого. Самым прямым является использование htmlspecialchars() для преобразования его в HTML, а затем nl2br() для преобразования разрывов строк в элементы.

Если вы просто «ищете защиту для print '<h3>' . $name . '</h3>' ", то да, по крайней мере, второй подход является адекватным, поскольку он проверяет, будет ли значение интерпретироваться как разметка если бы не бежать. (В этом случае область, в которой будет отображаться $name , представляет собой содержимое элемента, и только символы & , < и > имеют особое значение, когда они появляются в содержимом элемента.) (Для href и подобных атрибутов проверка на "javascript: «может быть необходимо, но, как вы сказали в комментарии, это не цель.)

Для официальных источников я могу обратиться к спецификации XML :

  • Производство контента в разделе 3.1 : здесь контент состоит из элементов, разделов CDATA, инструкций по обработке и комментариев (которые должны начинаться с < ), ссылок (которые должны начинаться с & ) и символьных данных (которые содержат любой другой юридический символ). (Хотя ведущий > рассматривается как символьные данные в содержимом элемента, многие люди обычно избегают его вместе с < , и лучше безопаснее, чем жаль, чтобы рассматривать его как особый.)

  • Создание значения атрибута в разделе 2.3 : Действительное значение атрибута состоит из ссылок (которые должны начинаться с & ) или символьных данных (которые содержат любой другой юридический символ, но не < или символ кавычки, используемый для обертывания значения атрибута). Если вам нужно помещать строковые входы в атрибуты в дополнение к содержимому элемента, символы " и ' должны быть проверены в дополнение к & , < и, возможно, > (и другим символам, незаконным в XML).

  • Раздел 2.2 . Определяет, какие кодовые точки Юникода являются законными в XML. В частности, null является незаконным в документе XML и может отображаться неправильно в HTML.

HTML5 ( последний рабочий проект , который находится в процессе разработки, описывает очень продуманный алгоритм анализа HTML-документов:

  • Содержимое элемента соответствует «состоянию данных» в алгоритме синтаксического анализа. Здесь ввод строки не должен содержать нулевой символ, < (который начинает новый тег), или & (который начинает ссылку на символ).
  • Значения атрибутов соответствуют «до состояния значения атрибута» в алгоритме синтаксического анализа. Для простоты предположим, что значение атрибута обернуто в двойные кавычки. В этом случае синтаксический анализатор переходит в состояние «значение атрибута (двойное кавычки)» . В этом случае строковый ввод не должен содержать нулевой символ, " (который заканчивает значение атрибута), или« (который начинает ссылку на символ).

Если строковые входы должны быть помещены в значения атрибутов (если их размещение не предназначено исключительно для показа), есть дополнительные соображения, которые следует учитывать. Например, HTML 4 указывает :

Пользовательские агенты должны интерпретировать значения атрибутов следующим образом:

  • Замените объекты символов символами,
  • Игнорировать строки,
  • Замените каждый возврат каретки или вкладку на одно место.

Пользовательские агенты могут игнорировать начальное и конечное пробелы в значениях атрибута CDATA [.]

Нормализация значения атрибута также указана в спецификации XML , но, по-видимому, не в HTML5.

HTML очиститель делает хорошую работу и очень легко реализовать. Вы также можете использовать фильтр Zend Framework, такой как Zend_Filter_StripTags.

HTML-очиститель не просто исправляет HTML .

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

Как правило, можно сказать, что вы должны избегать ввода пользователем только тогда, когда это необходимо, для данной целевой системы:

  1. Избегание пользовательского ввода часто означает потерю исходных данных, а разные целевые системы (вывод HTML / SQL / выполнение) нуждаются в различном ускорении. Они могут даже конфликтовать друг с другом.
  2. Вы всегда должны избегать данных для данной цели. Вы не должны доверять даже записям из своей базы данных. Таким образом, экранирование при чтении с пользовательского ввода не имеет большого преимущества, но двойное экранирование может привести к неверным данным.

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

Правильный способ определения того, содержат ли строковые входы теги HTML или любую другую разметку, которая имеет особое значение в XML или (X) HTML при отображении (кроме существа), просто

if (mb_strpos($data, '<') === FALSE AND mb_strpos($data, '>') === FALSE)

Ты прав! Для всех атак XSS и CSFR требуется <или> вокруг значений, чтобы заставить браузер выполнять код (по крайней мере, от IE6 +).

Учитывая заданный выходной контекст, этого достаточно, чтобы безопасно отображать в формате HTML:

<h2><?php print $input; ?></h2> <xml><item><?php print $input; ?></item></xml>

Конечно, если у нас есть какой-либо объект на входе, например &aacute; , браузер не будет выводить его как &aacute; , но как á , если мы не используем функцию, подобную htmlspecialchars когда делаем вывод. В этом случае даже < и > будут также безопасными.

В случае использования ввода строки в качестве значения атрибута безопасность зависит от атрибута.

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

<input value="<?php print htmlspecialchars($input, ENT_QUOTES, 'UTF-8');?>">

Опять же, даже символы < и > будут здесь безопасными.

Мы можем заключить, что нам не нужно делать какие-либо обнаружения и отклонения ввода, если мы всегда будем использовать htmlspecialchars для его вывода, и наш контекст будет соответствовать всегда вышеуказанным случаям (или одинаково безопасным).

[И у нас также есть несколько способов безопасно хранить его в базе данных, предотвращая эксплойты SQL.]

Что делать, если пользователь хочет, чтобы его «имя пользователя» было &amp; is not an & &amp; is not an & ? Он не содержит < nor > … мы его обнаружим и отклоним? Признаем ли мы это? Как мы его покажем? (Этот ввод дает интересные результаты в новой награде!)

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

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

Я предлагаю вам взглянуть на функцию xss_clean от CodeIgniter . Я знаю, что вы не хотите чистить, дезинфицировать или фильтровать что угодно. Вы просто хотите «обнаружить плохое поведение» и отвергнуть его. Именно поэтому я рекомендую вам посмотреть на этот код функции.

ИМО, мы можем найти глубокие и сильные знания об уязвимости XSS там, включая все знания, которые вы хотите и которые вам нужны, с вашим вопросом.

Тогда мой короткий / прямой ответ вам будет следующим:

 if (xss_clean($data) === $data) 

Теперь вам не нужно использовать всю инфраструктуру CodeIgniter только потому, что вам нужна эта единственная функция, конечно. Но я считаю, что вам может понадобиться захватить весь класс CI_Security/system/core/Security.php ) и сделать несколько изменений для устранения других зависимостей.

Как вы увидите, xss_clean код довольно сложный, так как XSS-уязвимости действительно есть, и я бы просто доверял ему и не пытаюсь «изобретать это колесо» … IMHO, вы не можете избавиться от уязвимостей XSS, просто обнаружив десяток персонажей.

filter_input + FILTER_SANITIZE_STRING (есть много флагов, которые вы можете выбрать)

: – http://www.php.net/manual/en/filter.filters.sanitize.php

Вы можете использовать регулярное выражение, если знаете набор символов, которые разрешены. ЕСЛИ символ находится в имени пользователя, которое не разрешено, а затем выдает ошибку:

 [a-zA-Z0-9_.-] 

Проверьте свои регулярные выражения здесь: http://www.perlfect.com/articles/regextutor.shtml

 <?php $username = "abcdef"; $pattern = '/[a-zA-Z0-9_.-]/'; preg_match($pattern, $username, $matches); print_r($matches); ?> 

Если причина вопроса заключается в предотвращении XSS , существует несколько способов взлома уязвимости XSS. Отличная чит-карта об этом – это Cheatsheet XSS на ha.ckers.org .

Но в этом случае обнаружение бесполезно. Вам нужна только профилактика, и правильное использование htmlspecialchars / htmlentities на ваших текстовых вводах перед сохранением их в вашей базе данных происходит быстрее и лучше, чем обнаружение плохого ввода.

Я, конечно, не эксперт по безопасности, но из того, что я собираю,

 if (htmlspecialchars($data, ENT_NOQUOTES, 'UTF-8') === $data) 

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

Атаки XSS, которые не требуют, чтобы «<» или «>» полагались на строку, обрабатываемую в блоке JavaScript прямо там и тогда, что, как я прочитал ваш вопрос, не в том, что вас беспокоит в этой ситуации.

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

Вы можете использовать функцию strip_tags в PHP . Эта функция будет удалять теги HTML и PHP из данных.

Например, $ data – это переменная, которая содержит ваш контент, и вы можете использовать это следующим образом:

 if (strlen($data) != strlen(strip_tags($data))){ return false; } else{ return true; } 

Он проверит разделенный контент на исходный контент. Если оба равны, то мы можем надеяться, что нет никаких тэгов HTML, и он возвращает true . В противном случае он возвращает false, поскольку обнаружил некоторые HTML-теги.