2013-04-20 87 views
2

以下内容是否足以阻止HTML元素内部的XSS?从HTML元素中防止XSS

function XSS_encode_html ($str) 
{ 
    $str = str_replace ('&', "&", $str); 
    $str = str_replace ('<', "&lt;", $str); 
    $str = str_replace ('>', "&gt;", $str); 
    $str = str_replace ('"', " &quot;", $str); 
    $str = str_replace ('\'', " &#x27;", $str); 
    $str = str_replace ('/', "&#x2F;", $str); 

    return $str; 
} 

如这里提到的: -
https://www.owasp.org/index.php/Abridged_XSS_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content


编辑我没有使用用htmlspecialchars(),因为

: -

  1. 它不会改变/到&#x2F;
  2. '(单引号)当设置ENT_QUOTES时变为'&#039;'(或&apos;)。

根据OWASP,'(单引号)应该成为&#x27;叫我迂腐),并且不建议
&apos;,因为它不是在HTML规范


+2

号它不是。为什么当你可以'htmlspecialchars()'的时候重新发明轮子? – 2013-04-20 21:17:47

+0

@MarcB:没有,用htmlspecialchars()不遵循OWASP建议,PHP的一个子集, – 2013-04-20 21:29:59

+0

在将使用什么样的背景下,这样转义值? – Gumbo 2013-04-20 21:46:03

回答

2

元素的内容内,因为它可能表示某个标记声明的开始,无论是开始标记,结束标记还是注释。所以那个字符应该总是被转义。

其他字符不一定需要在元素的内容中转义。

引号只需要在标签内部转义,尤其是用于包装在同一引号内或根本不引用的属性值时。同样,标记声明关闭分隔符>只需要在标记内部转义,此处仅在使用未加引号的属性值时才需要转义。但是,escaping plain ampersands as well is recommended to avoid them being interpreted as start of a character reference by mistake

现在作为取代/以及原因,这可以是由于SGML的特征,标记语言HTML是从适合,这使得所谓null end-tag

怎么看空结束标签在实际工作中考虑其结合元件结合使用,其可以被定义为:

<!ELEMENT ISBN - - CDATA --ISBN number-- > 

,而不用输入ISBN号码作为

我们可以使用空结束标签选项,在缩短的形式进入元素:

<ISBN/0 201 17535 5/ 

但是,我从来没有见过这个功能曾经被任何浏览器来实现。 HTML的语法规则一直比SGML语法规则更严格。

另一个更可能的原因是,这样的内容模型称为raw text elements (script and style),这是纯文本有以下restriction

在原始文本和RCDATA元素的文本不能包含字符串“任何出现(U + 003C LESS-THAN SIGN,U + 002F SOLIDUS)后跟字符,不区分大小写匹配元素的标签名称后跟一个“制表符”(U + 0009),“LF”(U + 000A) ),“FF”(U + 000C),“CR”(U + 000D),U + 0020 SPACE,“>”(U + 003E)或“/”(U + 002F)。

这说,原始的文本元素,如script内部的</script/发生将表示结束标记:

<script> 
alert(0</script/.exec("script").index) 
</script> 

虽然完全有效的JavaScript代码,结束标签将由</script/表示。但除此之外,/不会有任何伤害。如果您只允许在转义HTML的情况下在JavaScript上下文中使用任意输入,那么您已经注定了。

顺便说一下,这些字符是以什么样的character reference转义的,无论是命名字符引用(即实体引用)还是数字字符引用(十进制或十六进制表示法)都没有关系。他们都引用相同的字符。

2

您应该使用htmlspecialchars

$str = htmlspecialchars($str, ENT_QUOTES, 'UTF-8'); 

这是the documentation,b asically做什么在你的功能,但它已经实施,它更干净。但是,它不转换斜杠和反斜杠。

如果你想每一个字符转换有一个名为HTML实体,你可以使用htmlentities

$str = htmlentities($str, ENT_QUOTES, 'UTF-8'); 

在这里是documented。如果你想要做的就是防止XSS攻击和JS注入,我会推荐前者,因为它的开销要低得多。

+1

即使是大致如此,如果它已经存在,你应该利用现有的功能,它是PHP的*用htmlspecialchars()*只做什么OWASP表明这里的一个子集,它不把'/'这样的例子。 – 2013-04-20 21:31:23

+1

这是真的,但没有''“<>'我不认为'/'是危险的,如果你想彻底的话'htmlentities()'比任何一个都好。 – 2013-04-20 21:33:04

0

您可以使用stripslashes()函数。

$str = stripslashes($str); 
0

这是一个很长的,但我觉得如果我不分享它,我会做一些伤害。所有的代码都是直接从最新的Drupal稳定版的源代码的各个部分中获取的,并被编译到一个区域中(如下所示)。非常有效的防止XSS攻击的方法。

实例:

$html = file_get_contents('http://example.com'); 
$output = filter_xss($html); 
print $output; 

或者:

$html = file_get_contents('http://example.com'); 
// Allow only <ul></ul>, <li></li>, and <p></p> tags. 
$allowed_tags = array('ul', 'li', 'p'); 
$output = filter_xss($html, $allowed_tags); 
print $output; 

这里的运行上述实施例所需要的代码:the only character that can be harmful is the start-tag delimiter <

/** 
* Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities. 
* 
* Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses. 
* For examples of various XSS attacks, see: http://ha.ckers.org/xss.html. 
* 
* This code does four things: 
* - Removes characters and constructs that can trick browsers. 
* - Makes sure all HTML entities are well-formed. 
* - Makes sure all HTML tags and attributes are well-formed. 
* - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g. 
* javascript:). 
* 
* @param $string 
* The string with raw HTML in it. It will be stripped of everything that can 
* cause an XSS attack. 
* @param $allowed_tags 
* An array of allowed tags. 
* 
* @return 
* An XSS safe version of $string, or an empty string if $string is not 
* valid UTF-8. 
* 
* @see validate_utf8() 
* @ingroup sanitization 
*/ 
function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) { 
    // Only operate on valid UTF-8 strings. This is necessary to prevent cross 
    // site scripting issues on Internet Explorer 6. 
    if (!validate_utf8($string)) { 
    return ''; 
    } 
    // Store the text format. 
    _filter_xss_split($allowed_tags, TRUE); 
    // Remove NULL characters (ignored by some browsers). 
    $string = str_replace(chr(0), '', $string); 
    // Remove Netscape 4 JS entities. 
    $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string); 

    // Defuse all HTML entities. 
    $string = str_replace('&', '&amp;', $string); 
    // Change back only well-formed entities in our whitelist: 
    // Decimal numeric entities. 
    $string = preg_replace('/&amp;#([0-9]+;)/', '&#\1', $string); 
    // Hexadecimal numeric entities. 
    $string = preg_replace('/&amp;#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string); 
    // Named entities. 
    $string = preg_replace('/&amp;([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string); 

    return preg_replace_callback('% 
    (
    <(?=[^a-zA-Z!/]) # a lone < 
    |     # or 
    <!--.*?-->  # a comment 
    |     # or 
    <[^>]*(>|$)  # a string that starts with a <, up until the > or the end of the string 
    |     # or 
    >     # just a > 
    )%x', '_filter_xss_split', $string); 
} 

/** 
* Processes an HTML tag. 
* 
* @param $m 
* An array with various meaning depending on the value of $store. 
* If $store is TRUE then the array contains the allowed tags. 
* If $store is FALSE then the array has one element, the HTML tag to process. 
* @param $store 
* Whether to store $m. 
* 
* @return 
* If the element isn't allowed, an empty string. Otherwise, the cleaned up 
* version of the HTML element. 
*/ 
function _filter_xss_split($m, $store = FALSE) { 
    static $allowed_html; 

    if ($store) { 
    $allowed_html = array_flip($m); 
    return; 
    } 

    $string = $m[1]; 

    if (substr($string, 0, 1) != '<') { 
    // We matched a lone ">" character. 
    return '&gt;'; 
    } 
    elseif (strlen($string) == 1) { 
    // We matched a lone "<" character. 
    return '&lt;'; 
    } 

    if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) { 
    // Seriously malformed. 
    return ''; 
    } 

    $slash = trim($matches[1]); 
    $elem = &$matches[2]; 
    $attrlist = &$matches[3]; 
    $comment = &$matches[4]; 

    if ($comment) { 
    $elem = '!--'; 
    } 

    if (!isset($allowed_html[strtolower($elem)])) { 
    // Disallowed HTML element. 
    return ''; 
    } 

    if ($comment) { 
    return $comment; 
    } 

    if ($slash != '') { 
    return "</$elem>"; 
    } 

    // Is there a closing XHTML slash at the end of the attributes? 
    $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist, -1, $count); 
    $xhtml_slash = $count ? ' /' : ''; 

    // Clean up attributes. 
    $attr2 = implode(' ', _filter_xss_attributes($attrlist)); 
    $attr2 = preg_replace('/[<>]/', '', $attr2); 
    $attr2 = strlen($attr2) ? ' ' . $attr2 : ''; 

    return "<$elem$attr2$xhtml_slash>"; 
} 

/** 
* Processes a string of HTML attributes. 
* 
* @return 
* Cleaned up version of the HTML attributes. 
*/ 
function _filter_xss_attributes($attr) { 
    $attrarr = array(); 
    $mode = 0; 
    $attrname = ''; 

    while (strlen($attr) != 0) { 
    // Was the last operation successful? 
    $working = 0; 

    switch ($mode) { 
     case 0: 
     // Attribute name, href for instance. 
     if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) { 
      $attrname = strtolower($match[1]); 
      $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on'); 
      $working = $mode = 1; 
      $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr); 
     } 
     break; 

     case 1: 
     // Equals sign or valueless ("selected"). 
     if (preg_match('/^\s*=\s*/', $attr)) { 
      $working = 1; $mode = 2; 
      $attr = preg_replace('/^\s*=\s*/', '', $attr); 
      break; 
     } 

     if (preg_match('/^\s+/', $attr)) { 
      $working = 1; $mode = 0; 
      if (!$skip) { 
      $attrarr[] = $attrname; 
      } 
      $attr = preg_replace('/^\s+/', '', $attr); 
     } 
     break; 

     case 2: 
     // Attribute value, a URL after href= for instance. 
     if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) { 
      $thisval = filter_xss_bad_protocol($match[1]); 

      if (!$skip) { 
      $attrarr[] = "$attrname=\"$thisval\""; 
      } 
      $working = 1; 
      $mode = 0; 
      $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr); 
      break; 
     } 

     if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) { 
      $thisval = filter_xss_bad_protocol($match[1]); 

      if (!$skip) { 
      $attrarr[] = "$attrname='$thisval'"; 
      } 
      $working = 1; $mode = 0; 
      $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr); 
      break; 
     } 

     if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) { 
      $thisval = filter_xss_bad_protocol($match[1]); 

      if (!$skip) { 
      $attrarr[] = "$attrname=\"$thisval\""; 
      } 
      $working = 1; $mode = 0; 
      $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr); 
     } 
     break; 
    } 

    if ($working == 0) { 
     // Not well formed; remove and try again. 
     $attr = preg_replace('/ 
     ^
     (
     "[^"]*("|$)  # - a string that starts with a double quote, up until the next double quote or the end of the string 
     |    # or 
     \'[^\']*(\'|$)| # - a string that starts with a quote, up until the next quote or the end of the string 
     |    # or 
     \S    # - a non-whitespace character 
     )*    # any number of the above three 
     \s*    # any number of whitespaces 
     /x', '', $attr); 
     $mode = 0; 
    } 
    } 

    // The attribute list ends with a valueless attribute like "selected". 
    if ($mode == 1 && !$skip) { 
    $attrarr[] = $attrname; 
    } 
    return $attrarr; 
} 

/** 
* Processes an HTML attribute value and strips dangerous protocols from URLs. 
* 
* @param $string 
* The string with the attribute value. 
* @param $decode 
* (deprecated) Whether to decode entities in the $string. Set to FALSE if the 
* $string is in plain text, TRUE otherwise. Defaults to TRUE. 
* 
* @return 
* Cleaned up and HTML-escaped version of $string. 
*/ 
function filter_xss_bad_protocol($string, $decode = TRUE) { 
    // Get the plain text representation of the attribute value (i.e. its meaning). 
    if ($decode) { 

    $string = decode_entities($string); 
    } 
    return check_plain(strip_dangerous_protocols($string)); 
} 

/** 
* Strips dangerous protocols (e.g. 'javascript:') from a URI. 
* 
* @param $uri 
* A plain-text URI that might contain dangerous protocols. 
* 
* @return 
* A plain-text URI stripped of dangerous protocols. As with all plain-text 
* strings, this return value must not be output to an HTML page without 
* check_plain() being called on it. However, it can be passed to functions 
* expecting plain-text strings. 
* 
*/ 
function strip_dangerous_protocols($uri) { 
    static $allowed_protocols; 

    if (!isset($allowed_protocols)) { 
    $allowed_protocols = array_flip(array('ftp', 'http', 'https', 'irc', 'mailto', 'news', 'nntp', 'rtsp', 'sftp', 'ssh', 'tel', 'telnet', 'webcal')); 
    } 

    // Iteratively remove any invalid protocol found. 
    do { 
    $before = $uri; 
    $colonpos = strpos($uri, ':'); 
    if ($colonpos > 0) { 
     // We found a colon, possibly a protocol. Verify. 
     $protocol = substr($uri, 0, $colonpos); 
     // If a colon is preceded by a slash, question mark or hash, it cannot 
     // possibly be part of the URL scheme. This must be a relative URL, which 
     // inherits the (safe) protocol of the base document. 
     if (preg_match('![/?#]!', $protocol)) { 
     break; 
     } 
     // Check if this is a disallowed protocol. Per RFC2616, section 3.2.3 
     // (URI Comparison) scheme comparison must be case-insensitive. 
     if (!isset($allowed_protocols[strtolower($protocol)])) { 
     $uri = substr($uri, $colonpos + 1); 
     } 
    } 
    } while ($before != $uri); 

    return $uri; 
} 

/** 
* Encodes special characters in a plain-text string for display as HTML. 
* 
* Also validates strings as UTF-8 to prevent cross site scripting attacks on 
* Internet Explorer 6. 
* 
* @param $text 
* The text to be checked or processed. 
* 
* @return 
* An HTML safe version of $text, or an empty string if $text is not 
* valid UTF-8. 
* 
* @see validate_utf8() 
* @ingroup sanitization 
*/ 
function check_plain($text) { 
    return htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); 
} 

/** 
* Decodes all HTML entities (including numerical ones) to regular UTF-8 bytes. 
* 
* Double-escaped entities will only be decoded once ("&amp;lt;" becomes "&lt;" 
* , not "<"). Be careful when using this function, as decode_entities can 
* revert previous sanitization efforts (&lt;script&gt; will become <script>). 
* 
* @param $text 
* The text to decode entities in. 
* 
* @return 
* The input $text, with all HTML entities decoded once. 
*/ 
function decode_entities($text) { 
    return html_entity_decode($text, ENT_QUOTES, 'UTF-8'); 
} 

/** 
* Checks whether a string is valid UTF-8. 
* 
* All functions designed to filter input should use validate_utf8 
* to ensure they operate on valid UTF-8 strings to prevent bypass of the 
* filter. 
* 
* When text containing an invalid UTF-8 lead byte (0xC0 - 0xFF) is presented 
* as UTF-8 to Internet Explorer 6, the program may misinterpret subsequent 
* bytes. When these subsequent bytes are HTML control characters such as 
* quotes or angle brackets, parts of the text that were deemed safe by filters 
* end up in locations that are potentially unsafe; An onerror attribute that 
* is outside of a tag, and thus deemed safe by a filter, can be interpreted 
* by the browser as if it were inside the tag. 
* 
* The function does not return FALSE for strings containing character codes 
* above U+10FFFF, even though these are prohibited by RFC 3629. 
* 
* @param $text 
* The text to check. 
* 
* @return 
* TRUE if the text is valid UTF-8, FALSE if not. 
*/ 
function validate_utf8($text) { 
    if (strlen($text) == 0) { 
    return TRUE; 
    } 
    // With the PCRE_UTF8 modifier 'u', preg_match() fails silently on strings 
    // containing invalid UTF-8 byte sequences. It does not reject character 
    // codes above U+10FFFF (represented by 4 or more octets), though. 
    return (preg_match('/^./us', $text) == 1); 
} 
0

对于perl脚本或者CGI,你可以使用HTML::Entities

use HTML::Entities; 

$str = encode_entities($str, '<>&"');