Каков правильный способ определить, содержат ли входы строки 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.)
Update
Я думаю, мне нужно уточнить, что многие люди принимают этот вопрос за вопрос об основной безопасности посредством "экранирования" или "фильтрации" опасных символов. Это не тот вопрос, и большинство простых ответов в любом случае не решит эту проблему.
Обновление 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: Рабочий код
Вы можете загрузить созданный мной 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'])) ...
Ответы
Ответ 1
Я не думаю, что вам нужно реализовать огромный алгоритм, чтобы проверить, не содержит ли строка небезопасных данных - фильтры и регулярные выражения выполняют эту работу. Но если вам нужна более сложная проверка, возможно, это будет соответствовать вашим потребностям:
<?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=javascript:alert('XSS')>
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";
}
?>
Ответ 2
В вышеприведенном комментарии вы писали:
Просто запретите браузер обрабатывать строку как разметку.
Это совершенно другая проблема с тем, что указано в названии. Подход в названии обычно неправильный. Снятие тегов просто управляет входными данными и может привести к потере данных. Вы когда-нибудь пытались говорить о HTML в блоге, который разбивает теги? Разочарование.
Решение, которое обычно является правильным, состоит в том, чтобы делать так, как вы сказали в своем комментарии, - чтобы браузер не обрабатывал строку как разметку. Это - буквально взятое - невозможно. Вместо этого вы кодируете содержимое как HTML.
Рассмотрим следующие данные:
<strong>Test</strong>
Теперь вы можете посмотреть на это одним из двух способов. Вы можете посмотреть на него как на буквенные данные - последовательность символов. Вы можете посмотреть на него как на HTML-разметку, которая включает в себя сильное подчеркивание текста.
Если вы просто выгружаете это в HTML-документ, вы рассматриваете его как HTML. Вы не можете рассматривать это как литеральные данные в этом контексте. Вам нужен HTML, который будет выводить литералы. Вам нужно закодировать его как HTML.
Ваша проблема заключается не в том, что у вас слишком много HTML - это у вас слишком мало. Когда вы выводите <
, вы выводите необработанные данные в контексте HTML. Вам нужно преобразовать его в <
, который является представлением HTML этих данных перед его выходом.
PHP предлагает несколько различных вариантов для этого. Самое непосредственное - использовать htmlspecialchars()
, чтобы преобразовать его в HTML, а затем nl2br()
для преобразования линия разбивается на элементы <br>
.
Ответ 3
Если вы просто "ищете защиту для 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.
Ответ 4
HTML очиститель делает хорошую работу и очень легко реализовать. Вы также можете использовать фильтр Zend Framework, например Zend_Filter_StripTags.
HTML Очиститель не просто исправляет HTML.
Ответ 5
Думаю, ты ответил на свой вопрос. Функция htmlspecialchars()
делает именно то, что вам нужно, но вы не должны использовать ее, пока не напишите ввод пользователя на страницу. Чтобы сохранить его в базе данных, существуют другие функции, такие как mysqli_real_escape_string()
.
Как правило, можно сказать, что вы должны избегать ввода пользователя только тогда, когда это необходимо, для данной целевой системы:
- Выход из пользовательского ввода часто означает потерю исходных данных, а разные целевые системы (вывод HTML/SQL/выполнение) нуждаются в различном ускорении. Они могут даже конфликтовать друг с другом.
- В любом случае вам нужно избегать данных для данной цели, всегда. Вы не должны доверять даже записям из своей базы данных. Таким образом, экранирование при чтении с пользовательского ввода не имеет большого преимущества, но двойное экранирование может привести к недопустимым данным.
В отличие от экранирования, проверка содержимого - это хорошо, что нужно делать раньше. Если вы ожидаете целое число, принимайте только целые числа, иначе отказываетесь от ввода пользователя.
Ответ 6
Я, конечно, не эксперт по безопасности, но из того, что я собираю, что-то вроде предложенного вами
if (htmlspecialchars($data, ENT_NOQUOTES, 'UTF-8') === $data)
должен работать, чтобы вы не проходили зараженные строки, поскольку вы получили свое кодирование прямо там.
атаки XSS, которые не требуют '<' или ' > ' полагаются на строку, обрабатываемую в блоке JavaScript прямо там и потом, что, как я прочитал ваш вопрос, не то, что вас беспокоит в этой ситуации.
Ответ 7
Я предлагаю вам взглянуть на функцию xss_clean
из CodeIgniter. Я знаю, что вы не хотите чистить, дезинфицировать или фильтровать что угодно. Вы просто хотите "обнаружить плохое поведение" и отвергнуть его. Именно поэтому я рекомендую вам посмотреть на этот код функции.
IMO, мы можем найти глубокую и сильную информацию об уязвимости XSS, включая все необходимые знания и потребности с вашим вопросом.
Тогда мой короткий/прямой ответ вам будет следующим:
if (xss_clean($data) === $data)
Теперь вам не нужно использовать всю инфраструктуру CodeIgniter только потому, что вам нужна эта единственная функция, конечно. Но я считаю, что вы можете захватить весь класс CI_Security
(at /system/core/Security.php
) и сделать несколько изменений, чтобы устранить другие зависимости.
Как вы увидите, код xss_clean
довольно сложный, так как XSS-уязвимости действительно есть, и я бы просто доверял ему и не пытаюсь "изобретать это колесо"... ИМХО, вы не можете избавиться от XSS, просто обнаружив дюжину символов.
Ответ 8
Правильный способ определить, содержат ли входы строки 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>
Конечно, если у нас есть какой-либо объект на входе, например á
, браузер не будет выводить его как á
, а как á
, если мы не используем функцию типа htmlspecialchars
при выполнении вывода, В этом случае даже <
и >
будут также безопасными.
В случае использования ввода строки в качестве значения атрибута безопасность зависит от атрибута.
Если атрибут является входным значением, мы должны его процитировать и использовать функцию типа htmlspecialchars
, чтобы иметь тот же контент для редактирования.
<input value="<?php print htmlspecialchars($input, ENT_QUOTES, 'UTF-8');?>">
Опять же, даже символы <
и >
будут здесь безопасными.
Мы можем заключить, что нам не нужно делать какие-либо обнаружения и отклонения ввода, если мы всегда будем использовать htmlspecialchars
для вывода его, и наш контекст будет всегда соответствовать вышеуказанным случаям (или в равной степени безопасные).
[И у нас также есть несколько способов безопасно хранить его в базе данных, предотвращая эксплойты SQL.]
Что делать, если пользователь хочет, чтобы его "имя пользователя" было & is not an &
? Он не содержит <
и >
... мы его обнаружим и отклоним? Признаем ли мы это? Как мы его покажем? (Этот ввод дает интересные результаты в новой награде!)
Наконец, если наш контекст расширяется, и мы будем использовать ввод строки как anchor href, тогда весь наш подход внезапно резко изменится. Но этот сценарий не включен в вопрос.
(Стоит упомянуть, что даже при использовании htmlspecialchars
вывод ввода строки может отличаться, если кодировки символов различаются на каждом шаге.)
Ответ 9
filter_input + FILTER_SANITIZE_STRING (есть много флагов, которые вы можете выбрать)
: - http://www.php.net/manual/en/filter.filters.sanitize.php
Ответ 10
Если причина вопроса заключается в предотвращении XSS, есть несколько способов взлома уязвимости XSS. Отличная статья об этом - XSS Cheatsheet на ha.ckers.org.
Но, обнаружение в этом случае бесполезно. Вам нужна только профилактика, и правильное использование htmlspecialchars/htmlentities на ваших текстовых вводах перед сохранением их в вашей базе данных происходит быстрее и лучше, чем обнаружение плохого ввода.
Ответ 11
Вы можете использовать регулярное выражение, если знаете набор символов, которые разрешены. ЕСЛИ символ находится в имени пользователя, которое не разрешено, а затем выдает ошибку:
[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);
?>
Ответ 12
Вы можете использовать функцию strip_tags в PHP. Эта функция будет удалять теги HTML и PHP из данных.
Например, $datastrong > - это переменная, которая содержит ваш контент, и вы можете использовать это следующим образом:
if (strlen($data) != strlen(strip_tags($data))){
return false;
}
else{
return true;
}
Он проверит разделенный контент на исходный контент. Если оба они равны, то мы можем надеяться, что нет никаких тэгов HTML, и он возвращает true. В противном случае он возвращает false, поскольку обнаружил некоторые HTML-теги.
Ответ 13
Regex по-прежнему является наиболее эффективным способом решения вашей проблемы. Неважно, какие рамки вы планируете использовать или рекомендуется использовать, наиболее эффективным способом может быть пользовательский код регулярного выражения. Вы можете проверить строку с помощью регулярного выражения и удалить (или преобразовать) затронутый раздел с помощью функции htmlcharacter.
Нет необходимости устанавливать какую-либо другую инфраструктуру или использовать какое-то долговременное приложение.