检测字符串输入是否包含HTML的正确方法是什么?

Xeo*_*oss 23 html php xss sanitization input

在表单上接收用户输入时,我想检测"用户名"或"地址"等字段是否包含在XML(RSS提要)或(X)HTML(显示时)中具有特殊含义的标记.

那么这些是检测输入的输入是否在HTML和XML上下文中不包含任何特殊字符的正确方法中的哪一个?

if (mb_strpos($data, '<') === FALSE AND mb_strpos($data, '>') === FALSE)
Run Code Online (Sandbox Code Playgroud)

要么

if (htmlspecialchars($data, ENT_NOQUOTES, 'UTF-8') === $data)
Run Code Online (Sandbox Code Playgroud)

要么

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

我是否遗漏了其他任何内容,比如字节序列或其他棘手的方法来获取"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备忘单,但我发现它无法找到任何东西.(我忽略了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'])) ...
Run Code Online (Sandbox Code Playgroud)

Tim*_*mur 12

我认为你不需要实现一个巨大的算法来检查字符串是否有不安全的数据 - 过滤器和正则表达式可以完成工作.但是,如果您需要更复杂的检查,也许这将满足您的需求:

<?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";
}
?>
Run Code Online (Sandbox Code Playgroud)


Jim*_*Jim 8

在上面的评论中,你写道:

只需停止浏览器将字符串视为标记即可.

这与标题中的问题完全不同.标题中的方法通常是错误的.剥离标签只会破坏输入并导致数据丢失.有没有试过在剥离标签的博客上谈论HTML?令人沮丧.

通常是正确的解决方案是按照您在评论中所说的那样做 - 停止浏览器将字符串视为标记.这 - 字面意思 - 是不可能的.你做的是将内容编码 HTML.

请考虑以下数据:

<strong>Test</strong>
Run Code Online (Sandbox Code Playgroud)

现在,您可以看看这两种方式中的一种.您可以将其视为文字数据 - 一系列字符.您可以将其视为HTML - 包含强烈强调文本的标记.

如果您只是将其转储到HTML文档中,那么您将其视为HTML.您不能将其视为该上下文中的文字数据.你需要的是输出文字数据的HTML.您需要将其编码为HTML.

你的问题并不是你有太多的HTML - 而是你的太少了.输出时<,您将在HTML上下文中输出原始数据.您需要将其转换为&lt;,在输出数据之前是该数据的HTML表示形式.

PHP为此提供了一些不同的选项.最直接的方法是使用htmlspecialchars()将其转换为HTML,然后nl2br()将换行符转换为<br>元素.


Pet*_* O. 6

如果你只是"寻找保护print '<h3>' . $name . '</h3>'",那么是的,至少第二种方法是足够的,因为它检查如果没有转义,该值是否会被解释为标记.(在这种情况下,$name出现的区域是元素内容,只有字符&,<并且>当它们出现在元素内容中时具有特殊含义.)(对于href和类似的属性,可能需要检查"javascript:",但是正如你在评论中所说,这不是目标.)

对于官方消息来源,我可以参考XML规范:

  • 3.1节中的内容制作:这里,内容包括元素,CDATA部分,处理指令和注释(必须以<)开头,引用(必须以&)开头,以及字符数据(包含任何其他合法字符).(虽然领导>被视为元素内容中的角色数据,但很多人通常会将其与...一起逃脱<,并且将其视为特殊处理比保证更安全.)

  • 2.3节中的属性值生成:有效的属性值由引用(必须以之开头&)或字符数据(包含任何其他合法字符,但不包含<或用于包装属性值的引号)组成.如果您需要将字符串输入在属性中,除了对元素内容,人物"'需要除了被检查&,<以及可能>(和XML其他字符非法的).

  • 第2.2节:定义哪些Unicode代码点在XML中是合法的.特别是,null在XML文档中是非法的,并且可能无法在HTML中正确显示.

HTML5(最新的工作草案,正在进行中,描述了一个非常精细的HTML文档解析算法:

  • 元素内容对应于解析算法中的"数据状态".这里,字符串输入不应包含空字符<(开始新标记)或& (开始字符引用).
  • 属性值对应 于解析算法中的"前属性值状态".为简单起见,我们假设属性值包含在双引号中.在这种情况下,解析器移动到 "属性值(双引号)状态".在这种情况下,字符串输入不应包含空字符"(结束属性值)或&(开始字符引用).

如果要将字符串输入放在属性值中(除非将它们放在那里仅用于显示目的),还需要记住其他注意事项.例如,HTML 4 指定:

用户代理应解释属性值,如下所示:

  • 用字符替换字符实体,
  • 忽略换行,
  • 用一个空格替换每个回车或标签.

用户代理可能会忽略CDATA属性值中的前导和尾随空格[.]

属性值规范化也在XML 规范中指定,但显然不在HTML5中.