Как правильно определить, содержат ли строковые данные HTML или нет? - PullRequest
23 голосов
/ 07 декабря 2011

При получении пользовательского ввода в формах я хочу определить, не содержат ли поля, такие как «имя пользователя» или «адрес», разметку, которая имеет особое значение в 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>' <xml><item><?php print $input; ?></item></xml>

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

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

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

Вы можете загрузить созданную мной суть и запустить код в виде текстового или 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'])) ...

Ответы [ 13 ]

12 голосов
/ 22 декабря 2011

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

<code><?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').'
.» "\ П";}?>
8 голосов
/ 13 декабря 2011

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

Просто не позволяйте браузеру воспринимать строку как разметку.

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

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

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

<strong>Test</strong>

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

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

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

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

6 голосов
/ 16 декабря 2011

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

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

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

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

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

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

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

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

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

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

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

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


EDIT (25 апреля 2019 г.): также,быть подозрительным к входным данным, содержащим -

  • нулевую кодовую точку (поскольку это может вызвать ошибки синтаксического анализа в определенных местах, как указано в спецификации HTML5), или
  • любую кодовую точку, недопустимую вXML (так как это вызовет ошибки синтаксического анализа при чтении документа XML),

... при условии, что htmlspecialchars уже не экранирует эти кодовые точки.

3 голосов
/ 13 декабря 2011

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

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

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

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

2 голосов
/ 19 декабря 2011

Правильный способ определить, содержат ли строковые входные данные теги 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.]

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

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

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

2 голосов
/ 18 декабря 2011

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

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

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

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

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

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

2 голосов
/ 16 декабря 2011

Я, конечно, не эксперт по безопасности, но из того, что я понял, что-то похожее на ваше предложение

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

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

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

2 голосов
/ 12 декабря 2011

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

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

1 голос
/ 16 декабря 2011

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

[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);
?>
1 голос
/ 13 декабря 2011

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

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

...