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

时间:2011-12-07 16:42:49

标签: php html input xss sanitization

在表单上接收用户输入时,我想检测“username”或“address”等字段是否包含在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 and CSFR attacks都需要<>围绕值来让浏览器执行代码(至少从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:工作代码

您可以下载the gist I created并将代码作为文本或HTML响应运行,以查看我在说什么。这个简单的检查通过了http://ha.ckers.org XSS Cheat Sheet,我找不到任何可以做到的东西。 (我忽略了Internet Explorer 6及以下版本)。

我开始了另一个赏金,奖励那些可能会出现这种方法问题的人或者实施方面的弱点。

更新4:询问DOM

这是我们想要保护的DOM - 所以为什么不问它? Timur's answer导致这一点:

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 个答案:

答案 0 :(得分: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";
}
?>

答案 1 :(得分:8)

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

  

只需停止浏览器将字符串视为标记。

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

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

考虑以下数据:

<strong>Test</strong>

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

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

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

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

答案 2 :(得分:6)

如果您只是“寻找print '<h3>' . $name . '</h3>'的保护”,那么是的,至少是 第二种方法是足够的,因为它检查值是否会被解释为标记,如果不是  逃过一劫。 (在这种情况下,$name出现的区域是元素内容,只有字符&<>在元素内容中出现时才有特殊含义。)(对于href和类似属性,可能需要检查“JavaScript:”,但正如您在评论中所述,这不是目标。)

官方消息来源,我可以参考XML specification

  • Content production in section 3.1:此处内容包含元素,CDATA部分,处理说明和评论(必须以<开头),参考文献(必须以&开头) )和字符数据(包含任何其他合法字符)。 (虽然前导>被视为元素内容中的字符数据,但很多人通常会将其与<一起使用,并且将其视为特殊处理比保证更安全。)

  • Attribute value production in section 2.3:有效的属性值包含引用(必须以&开头)或字符数据(包含任何其他合法字符,但不包含<或用于包装属性值的引号。如果您需要将附加中的字符串输入放置到元素内容中,则除了",{{{{}}之外,还需要检查字符'& 1}},可能<(和其他字符在XML中非法)。

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

HTML5(latest working draft,这是一项正在进行的工作,描述了一个非常精细的解析 HTML文档的算法:

  • 元素内容对应于解析算法中的"data state"。 此处,字符串输入不应包含空字符>(以新标记开头)或< (从字符引用开始)。
  • 属性值对应"before attribute value state" 在解析算法中。 为简单起见,我们假设属性值包含在双引号中。在这种情况下,解析器移动到 "attribute value (double-quoted) state"。 在这种情况下,字符串输入不应包含空字符&(结束属性值)或"(开始字符引用)。

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

  

用户代理应按如下方式解释属性值:

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

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

XML中还指定了属性值规范化 规范,但显然不在HTML5中。


EDIT(2019年4月25日):另外,要怀疑包含 -

的输入
  • 空代码点(因为它可能导致某些地方出现解析错误,如HTML5规范中所指定),或
  • 任何非法的代码点(因为它会在读取XML文档时导致解析错误),

...假设&已经没有逃脱这些代码点。

答案 3 :(得分:3)

我想你回答了自己的问题。函数htmlspecialchars()完全符合您的需要,但在将用户输入写入页面之前不应使用它。要将其存储在数据库中,还有其他功能,例如mysqli_real_escape_string()

根据经验,可以说对于给定的目标系统,您应该仅在需要时转义用户输入:

  1. 转义用户输入通常意味着丢失原始数据,并且不同的目标系统(HTML输出/ SQL /执行)需要不同的转义。它们甚至可以相互冲突。
  2. 无论如何,您必须为特定目的逃避数据,始终。您甚至不应该信任数据库中的条目。因此,从用户输入读取时逃避没有任何大的优势,但双重转义可能导致无效数据。
  3. 与转义相比,验证内容是一件好事。如果你期望一个整数,只接受整数,否则拒绝用户输入。

答案 4 :(得分:2)

HTML Purifier做得很好,很容易实现。您还可以使用Zend_Filter_StripTags等Zend Framework过滤器。

HTML Purifier不只是修复HTML

答案 5 :(得分:2)

我当然不是安全专家,而是从我收集的内容中提取的内容

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

应该可以防止你传染受污染的字符串,因为你的编码就在那里。

XSS攻击不需要'&lt;'或'&gt;'依赖于JavaScript块中正在处理的字符串然后,从我如何阅读你的问题,这不是你在这种情况下所关心的。

答案 6 :(得分:2)

我建议您查看CodeIgniter中的xss_clean函数。我知道你不想清理,消毒或过滤任何东西。你只想“发现不良行为”并拒绝它。这正是我建议你看看这个功能代码的原因。

国际海事组织,我们可以在那里找到深刻而强大的XSS漏洞知识,包括您想要和所需的所有知识。

然后,我对你的简短回答是:

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

现在,您不需要使用整个CodeIgniter框架,因为您当然需要这个单一功能。但是我相信你可能想要抓住整个CI_Security类(/system/core/Security.php)并做一些修改以消除其他依赖。

正如您将看到的,xss_clean代码非常复杂,因为XSS漏洞确实存在,而且我只是相信它并且不会尝试“重新发明这个轮子”......恕我直言,你无法得到仅通过检测十几个字符就可以消除XSS漏洞。

答案 7 :(得分:2)

检测字符串输入是否包含HTML标记的正确方法, 或者在显示时(除了作为实体)在XML或(X)HTML中具有特殊含义的任何其他标记只是

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

你是对的!所有XSS和CSFR攻击都需要&lt;或者&gt;围绕值来让浏览器执行代码(至少从IE6 +开始)。

考虑到给定的输出上下文,这足以安全地以HTML格式显示:

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

当然,如果输入中有任何实体,例如&aacute;,则浏览器不会将其输出为&aacute;,而是á,除非我们使用htmlspecialchars之类的函数{1}}在做输出时。在这种情况下,即使<>也是安全的。

在使用字符串输入作为属性值的情况下,安全性取决于属性。

如果属性是输入值,我们必须引用它并使用htmlspecialchars之类的函数,以便将相同的内容重新编辑。

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

同样,即使<>字符在这里也是安全的。

我们可以得出结论,我们不必对输入进行任何检测和拒绝,如果我们总是使用htmlspecialchars输出它,我们的上下文将始终适合上述情况(或同样安全的。)

[我们还有多种方法可以安全地将其存储在数据库中,从而防止SQL攻击。]

如果用户想要他的用户名&#34;该怎么办?是&amp; is not an &?它不包含<也不包含> ...我们会检测并拒绝它吗?我们会接受吗?我们将如何展示它? (这个输入在新的赏金中给出了有趣的结果!)

最后,如果我们的上下文扩展,并且我们将字符串输入用作 anchor href ,那么我们的整个方法会突然发生巨大变化。但问题中不包括这种情况。

(值得一提的是,即使使用htmlspecialchars,如果每一步的字符编码不同,字符串输入的输出也可能不同。)

答案 8 :(得分:1)

如果问题的原因是XSS预防,有几种方法可以爆炸XSS漏洞。关于此问题的一个很好的备忘单是XSS Cheatsheet at ha.ckers.org

,在这种情况下检测无用。您只需要预防,在将文本输入保存到数据库之前正确使用htmlspecialchars / htmlentities比检测错误输入更快更好。

答案 9 :(得分:1)

如果您知道允许的字符集,则可以使用正则表达式。如果某个字符位于不允许的用户名中,则会抛出错误:

[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);
?>

答案 10 :(得分:0)

filter_input + FILTER_SANITIZE_STRING(你可以选择很多旗帜)

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

答案 11 :(得分:0)

您可以使用strip_tags中的PHP功能。此函数将从给定数据中删除HTML和PHP标记。

例如, $ data 是保存您内容的变量,然后您可以像这样使用:

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

它会根据原始内容检查已剥离的内容。如果两者都相等,那么我们可以希望没有任何HTML标记,并返回 true 。否则,它会返回 false ,因为它找到了一些HTML标记。

答案 12 :(得分:0)

正则表达式仍然是解决问题的最有效方法。无论您计划使用哪种框架,或建议使用哪种框架,最有效的方法仍然是自定义正则表达式代码。您可以使用正则表达式测试字符串,并使用htmlcharacter函数删除(或转换)受影响的部分。
无需安装任何其他框架,或使用一些冗长的应用程序。