写在前面

之前翻译了OWASP的XSS过滤绕过速查表,这篇也算是个后续。文中的翻译尽可能保持原文格式,但一些地方为了通顺和易于理解也做了一定改动,如有翻译问题,还请各位大牛指正。本文翻译时版本是20171014,后续如果有大更新的话也会跟进更新。

一、介绍

本文提供了一种通过使用输出转义/编码来防止XSS攻击的简单有效模型。尽管有着庞大数量的XSS攻击向量,依照下面这些简单的规则可以完全防止这种攻击。这篇文章不会去研究XSS技术及业务上的影响。简而言之,受害者能在其浏览器上做的任何事情攻击者都可以通过XSS实现。

t01a0a1fc107c2fdf85.jpg

反射型和存储型XSS都可以在服务器端进行适当的验证和转义。基于DOM的XSS可以通过基于DOM的XSS防御指南中的一系列子规则进行防御。如果想查找XSS相关攻击向量,可以参考XSS过滤绕过速查表。更多的浏览器安全背景知识以及各种浏览器知识可以在浏览器安全指南中找到。

阅读本文之前,有注入原理的基础知识是很重要的。

1.1.  一个有效的XSS防御模型

这篇文章将一个HTML页面视为一个模板,其向开发者提供可以向其中放置不可信数据的位置。这些位置覆盖了开发人员可能希望放置不可信数据的大多数地方。在向其它HTML位置放置不可信数据是不被允许的。这是一个“白名单”模型,其会拒绝任何没有特殊允许的内容。

timg_看图王.jpg

根据浏览器解析HTML的不同,不同位置的安全规则也会有所不同。当你将不可信数据放在这些位置时,你需要采取一定步骤来确保数据不会从该位置逃逸到其他内容中导致代码执行。在某种程度上,这种方法将HTML文档视为参数化的数据库查询-数据在特定的位置并且进行转义以便与代码隔离。

本文列举了大多数常见位置类型以及将不可信数据安全的放在其中的规则。基于多种已知类型的XSS攻击向量和大量在流行浏览器上的实际测试,我们在这里提供的安全规则都是可靠的。

我们定义了这些位置以及每个位置都提供了样例。开发人员不应该在没有经过仔细分析确保他们所做的事情是安全的前就将数据放在其他位置。浏览器如何进行解析是十分令人头疼的事情,很多看上去无害的字符在不同上下文中也必须格外注意。

1.2.  为什么不能仅对不可信数据进行HTML实体编码?

对于放在HTML文档body中的不可信数据进行HTML实体编码是没有问题的,比如在<div>标签中。编码后甚至可以在属性中引用不可信数据,特别是使用引号将属性包含的时候。但是HTML实体编码在当你将不可信数据放到任何地方的<script>标签里时是不起作用的,同样在例如onmouseover的事件属性或CSS、URL中也是无效的。所以即使你在所有地方都使用了HTML实体编码,也很有可能受到XSS的影响。你必须对放置不可信数据的HTML文档部分使用转义语法。这是所有规则的基础。

1.3.  你需要一个安全编码库

编写编码器并不是十分困难,但是这会有一些隐患。例如,你可能会想在JavaScript中使用一些转义字符类似“\”。但是,这些值很容易被浏览器错误解析,十分危险。你也可能忘记转义转义符,这使得攻击者可以绕过你的防御方案。OWASP推荐使用一个注重安全的编码库来保证这些规则可以正确实现。

微软提供了一个编码库叫做Microsoft Anti-Cross SiteScripting Library(译者注:.NET4.0以后已经包含在Framework内),主要面向.NET平台,并且ASP.NET Framework通过内建的ValidateRequest功能实现了有限的过滤。OWASP Java EncoderProject则为Java提供了一个高性能的编码库。

二、XSS防御规则

下面的规则是为了防止任何形式的XSS在你的应用里出现。这些规则不允许在放置不可信数据到HTML文档中时有绝对自由,它们应该涵盖了大多数常见用例。你不需要在你的组织内应用所有规则。大多数组织发现只要应用1号规则和2号规则就可以满足它们的需求。

请不要仅仅转义下面各个规则中提供的示例字符。仅转义清单中的字符是不够的。黑名单的做法极容易失败。下面提供的白名单规则已经经过仔细设计以便保护未来由于浏览器发展可能带来的漏洞。

2.1.  规则#0-不要将不可信数据插入指定位置外

第一条规则是要拒绝所有-不要将不可信数据放在你的HTML文档中,除非这个位置符合规则#1到规则#5。设立规则#0的原因是HTML中可能包含很多奇怪的内容,这会让转义规则十分复杂。我们想不出有什么好的理由让不可信数据放在这些内容里。这包括了“混合内容”例如JavaScript中的URL——这些地方的编码规则是十分复杂并且危险的。如果你坚持想要将不可信数据放在混合内容里,请做大量的跨浏览器测试并和我们分享你发现了什么。

<script>...绝对不要将不可信数据放在这...</script>   直接放置在script内 <!--...绝对不要将不可信数据放在这....-->        放在HTML注释内 <div ...绝对不要将不可信数据放在这...=test />    放在属性名 <绝对不要将不可信数据放在这... href="/test" />    放在标签名内 <style>...绝对不要将不可信数据放在这...</style>    直接放在CSS中 

最重要一点,绝对不要接受并执行不可信数据中的JavaScript代码。例如,一个叫“callback”的参数包含了JavaScript代码片段。再多的转义也不能解决这个问题。

2.2.  规则#1-将不可信数据插入HTML元素内容前进行HTML转义

规则#1是为了当你想将不可信数据直接放到HTML body里时设立的。这其中包括普通标签例如div, p, b, td等等。大多数Web框架都会有一个HTML转义方法来转义下面列出的字符。

<body>...将不可信数据放在这前进行转义...</body> <div>...将不可信数据放在这前进行转义...</div> 

 其它普通HTML元素 

使用HTML实体编码转义下列字符,以避免代码变成可执行内容,例如script,style或eventhandlers。推荐使用十六进制转义。除了XML中显著的5个字符外(&, <, >, “, ‘),前斜杠也应该包含在内,因为它有助于结束HTML实体。

& --> &amp;

< --> &lt;

> --> &gt;

" --> &quot;

' --> &#x27;     &apos;不推荐使用因为其不在HTML语法中。&apos;存在于XML和XHTML语法. / --> &#x2F;     包含前斜杠是因为它有助于结束HTML实体 

2.3.  规则#2-将不可信数据插入HTML常规属性前将属性进行转义

规则#2是为了将不可信数据放到典型属性里例如width, name, value等而设立的。这些不应该用在复杂属性例如href,src,style或任何event handler例如onmouseover。尤其重要的是event handler属性应该遵循为JavaScript数据值准备的规则#3。

<div attr=...将不可信数据放在这前进行转义...>content</div>     在无引号属性间 <div attr='... 将不可信数据放在这前进行转义...'>content</div>   在单引号属性间 <div attr="...将不可信数据放在这前进行转义...">content</div>   在双引号属性间 

除了字母以外,转义所有ASCII值小于256的字符为&#xHH; 形式(或者命名实体形式)来防止值逃逸出属性。这么做的原因是开发者经常将属性设为无引号的。正确使用引号包含的属性只能被未转义的引号破坏。无引号包含的属性则可以由很多字符打断,包括[空格] % * + , – / ; < = > ^ 和|。

2.4.  规则#3-将不可信数据插入JavaScript数据值时对JavaScript转义

规则#3关注动态生成的JavaScript代码-包括script块和event-handler属性。唯一安全的位置放置不可信数据是被引号包含的“数据值”。在任何其他的JavaScript内容中包含不可信数据都是十分危险的,因为遇到包括(但不限于)分号、等号、空格、加号和其他字符时很容易变成可执行内容,所以请谨慎使用。

<script>alert('... 将不可信数据放在这前进行转义...')</script>    在被引号包含的字符串 <script>x='... 将不可信数据放在这前进行转义...'</script>         被引号包含的表达式 <div onmouseover="x='... 将不可信数据放在这前进行转义...'"</div> 被引号包含的event handler 

请注意有一些JavaScript函数永远不可能安全的使用不可信数据作为输入-即使JavaScript已经转义!

示例:

<script>  window.setInterval('...即使转义也会发生XSS...'); </script> 

除了字母以外,转义所有ASCII值小于256的字符为\xHH的形式来防止数据值进入脚本内容或者其他属性。不要使用任何转义方法如\”因为引号可能被HTML属性解析时优先配对。这种转义方法容易受到“转义逃脱”攻击,攻击者可以发送\”然后存在漏洞的代码就会将其转换为\\”,这样引号就正常解析了。

如果一个event handler被引号正确包含了,打破包含就需要未被转义的引号。然而,我们有意让这条规则更加广泛,因为event handler属性经常是无引号包含的。无引号包含的属性则可以由很多字符打断,包括[空格] % * + , – / ; < = > ^ 和|。同样一个</script>闭合标签会结束脚本块即使它是被引号包含的字符,因为HTML解析器在JavaScript解析器前运行。

2.4.1  规则#3.1-转义HTML内容中JSON值并由JSON解析器读取数据

在Web2.0的世界里,需要由JavaScript内容动态的生成数据是很常见的。一种方式是通过AJAX方法来获取值,但这不总是高效的。通常,加载一个初始化的JSON块到页面中来存储一系列数据。在这数据中插入攻击代码是困难的,但不是不可能的。只要正确的转义就可以不破坏格式和值的内容。

确保系统返回的Content-Type头部是application/json而不是text/html。这保证了浏览器不会误解内容并执行注入代码。

错误的HTTP响应

HTTP/1.1 200 Date: Wed, 06 Feb 2013 10:28:54 GMT Server: Microsoft-IIS/7.5.... Content-Type: text/html; charset=utf-8 <-- 错误    ....

Content-Length: 373 Keep-Alive: timeout=5, max=100  Connection: Keep-Alive

{"Message":"No HTTP resource was found that matches the request URI 'dev.net.ie/api/pay/.html?HouseNumber=9&AddressLine=The+Gardens<script>alert(1)</script>&AddressLine2=foxlodge+woods&TownName=Meath'.","MessageDetail":"No type was found that matches the controller named 'pay'."}   <--脚本将会执行!! 

正确的HTTP响应

HTTP/1.1 200 Date: Wed, 06 Feb 2013 10:28:54 GMT Server: Microsoft-IIS/7.5.... Content-Type: application/json; charset=utf-8 <--正确 .....

..... 

一个常见的反模式是:

<script>      var initData = <%= data.to_json %>; // 不要在没有使用任何下面列出的技术对数据进行编码前这样做。 </script> 

2.4.1.1  JSON实体编码

JSON编码规则可以在下面的输出编码规则汇总查看。注意这将不允许你使用CSP1.0提供的XSS保护。

2.4.1.2  HTML实体编码

这种技术的优点是HTML实体编码是广泛支持的,并且其帮助从服务器端分离数据而不用跨越内容边界。考虑将JSON块作为页面中的一个元素然后解析innerHTML来获得内容。读取这部分的JavaScript可以放在一个外部文件,这样就让CSP更加容易执行。

<div id="init_data" style="display: none">    <%= html_escape(data.to_json) %> </div> 
// 外部js文件  var dataElement = document.getElementById('init_data');

 //解码并解析div的内容  var initData = JSON.parse(dataElement.textContent); 

另一种在Javascript中转义和解析JSON的方法是在发送到浏览器前由标准的JSON服务器端转换’<’为’\u003c’。

2.5.  规则#4-将不可信数据插入HTML样式属性前对CSS进行转义和严格验证

规则4是为了当你想将不可信数据放在一个样式表或style标签中准备的。CSS惊人的强大,可以用于许多攻击。因此,只在属性值中使用不可信数据并且不在其它地方的样式数据使用是非常重要的。你应该让不可信数据远离复杂的属性,例如url, behavior, 和custom (-moz-binding)。你也不应该将不可信数据放在IE的表达式类型属性值里,它是允许JavaScript的。

<style>selector { property : ... 将不可信数据放在这前进行转义...; } </style>    属性值 <style>selector { property : "...将不可信数据放在这前进行转义..."; } </style>   属性值 <span style="property : ... 将不可信数据放在这前进行转义...">text</span>        属性值 

注意一些CSS内容永远不可以安全的使用不可信数据作为输入-即使CSS进行了转义!你需要确保URL只能以”http”开头而不是javascript”,并且还需要确保属性值不可以以”expression”开头。

例如:

{ background-url : "javascript:alert(1)"; }  // 和其他所有URL { text-size: "expression(alert('XSS'))"; }   // 仅IE有效 

除了字母以外,转义所有ASCII值小于256的字符为\HH形式。不要使用类似\”形式的转义方法因为引号字符可能会被先执行的HTML属性解析器所错误配对。这种转义方法容易受到“转义逃脱”攻击,攻击者可以发送\”然后存在漏洞的代码就会将其转换为\\”,这样引号就正常解析了。

如果属性被引号包含,那么需要未被转义的引号才能打破。所有的属性都应该被引号包含但是你的编码应该足够强以便在不可信数据放在未被引号包含位置时防止XSS。未被引号包含的属性可以被许多字符打破,包括[空格] % * + , – / ; < = > ^ 和 |。同样</style>标签也可以闭合style块即使是在被引号包含的字符串内,因为HTML解析器在JavaScript解析器之前运行。请注意我们建议同时进行CSS编码和验证来防止在引号包含区域和未被引号包含区域出现XSS攻击。

2.6.  规则#5-将不可信数据插入HTML URL参数值前对URL进行转义

规则#5是为当你想要将不可信数据放在HTTPGET参数值时使用的。

<a href="http://www.somesite.com?test=...将不可信数据放在这前进行转义...">link</a> 

除了字母以外,转义所有ASCII值小于256的字符为%HH形式。包括的数据中的不可信数据:URL不应该被允许,因为通过转义也不能很好防止逃逸出URL进行攻击。所有的属性都应该被引号包含。未被引号包含的属性可以被许多字符打破,包括[空格] % * + , – / ; < = > ^ 和 |。注意实体编码在这里是无用的。

警告:不要用URL编码对完整或相对URL进行编码!如果不可信的数据是指被放置在href, src或其它基于URL的属性时,需要进行验证确保它不会被指向其它的协议,尤其是JavaScript链接。URL随后才可以根据上下文进行编码。例如,用户在HREF中输入的URL应该被编码。示例:

String userURL = request.getParameter( "userURL" )

boolean isValidURL = Validator.IsValidURL(userURL, 255); if (isValidURL) {

     <a href="<%=encoder.encodeForHTMLAttribute(userURL)%>">link</a>

} 

2.7.  规则#6-使用专门设计的库来过滤HTML Markup

如果你的应用支持Markup——不可信的输入可能包含在HTML中——这可能很难进行验证。编码同样也很困难,因为它会破坏输入中所有可能的标记。因此,您需要一个能够解析和清除HTML格式文本的库。OWASP就提供了一些可用的并且很容易使用:

HtmlSanitizer -https://github.com/mganss/HtmlSanitizer

一个开源的.Net库。它用白名单的方法清理HTML。可以设置允许的标签和属性。这个库通过了OWASP的XSS过滤绕过速查表验证。

var sanitizer = new HtmlSanitizer();

sanitizer.AllowedAttributes.Add("class"); var sanitized = sanitizer.Sanitize(html); 

OWASP Java HTML Sanitizer -https://www.owasp.org/index.php/OWASP_Java_HTML_Sanitizer_Project

import org.owasp.html.Sanitizers; import org.owasp.html.PolicyFactory; PolicyFactory sanitizer = Sanitizers.FORMATTING.and(Sanitizers.BLOCKS); String cleanResults = sanitizer.sanitize("<p>Hello, <b>World!</b>"); 

更多的OWASP Java HTML Sanitizer使用规则信息,请参考https://github.com/OWASP/java-html-sanitizer

Ruby on Rails SanitizeHelper -http://api.rubyonrails.org/classes/ActionView/Helpers/SanitizeHelper.html

SanitizeHelper模块提供了一系列处理HTML元素中非预期内容的方法。

<%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %> 

其它提供HTML清理功能的库包括:

PHP HTML Purifier – http://htmlpurifier.org/

JavaScript/Node.js Bleach – https://github.com/ecto/bleach

Python Bleach – https://pypi.python.org/pypi/bleach

2.8.  规则#7-防御基于DOM的XSS

了解更多基于DOM的XSS,以及如何防御此类XSS,请查阅OWASP的基于DOM的XSS防御指南。(https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet

2.9.  附加规则#1:使用HTTPOnly cookie标志

正如你所见,在应用里防御所有的XSS是很困难的。为了帮助减轻XSS对网站的影响,OWASP推荐为会话cookie和任何自定义的cookie启用HTTPOnly标志,以防止它们被你所写的任何JavaScript访问。这种cookie标志在.NET应用中是默认启用的,但在其它语言中你必须手动设置。了解HTTPOnlycookie标志的更多细节,包括它是什么和它如何工作,请查阅OWASP关于HTTPOnly的相关文章。

2.10.  附加规则#2:使用内容安全策略

另一个减少XSS影响的复杂方案是使用内容安全策略。这是一个浏览器端的方案,它允许你为你的Web应用客户端资源创建一个白名单,限制范围包括JavaScript、CSS、图像等等。CSP(ContentSecurity Policy)通过特定的HTTP头部告诉浏览器只执行或呈现指定来源的内容。CSP的示例如下:

Content-Security-Policy: default-src: 'self'; script-src: 'self' static.domain.tld 

上面的示例会告诉浏览器只加载来源是当前域的资源,对于JavaScript文件则除当前域外,可以额外从static.domain.tld加载。了解更多内容安全策略的细节,包括如何工作和如何使用,请参考OWASP相关文档(https://www.owasp.org/index.php/Content_Security_Policy)

2.11.  附加规则#3:使用自动转义功能

很多Web框架都会提供自动转义功能,例如AngularJS就提供了类似功能。尽量在有条件的情况下使用这些功能。

2.12.  附加规则#4:使用X-XSS-Protection响应头

这个HTTP响应头部会启用在一些新版本浏览器中内置的跨站脚本过滤功能。这个头部通常是默认启用的,所以大多数时候添加头部的作用是为了当用户关闭浏览器过滤功能时,为特定站点重新启用过滤功能。

三、XSS防御规则汇总

下面几段HTML示例展示了如何在不同情况下安全的处理不可信数据。

数据类型 内容位置 代码样例 防御方法
字符串 HTML Body <span>不可信数据</span> HTML实体转义
字符串 安全的HTML属性 <input   type=”text” name=”fname” value=”不可信数据 “> l   严格HTML实体转义 l   只将不可信数据放在白名单(下面给出)中的安全属性中 l   严格检查不安全的属性,例如background, id及name
字符串 GET参数 <a   href=”/site/search?value=不可信数据 “>clickme</a> URL转义
字符串 SRC中的不可信URL或HREF属性 <a href=”不可信URL“>clickme</a> <iframe src=”不可信URL” /> l   规范化输入 l   URL 验证 l   安全URL认证 l   只允许白名单内http和https URL(注意防止JavaScript打开新窗口) l   属性编码器
字符串 CSS <div   style=”width: 不可信数据;”>Selection</div> l     严格对结构进行验证 l     使用Hex转义CSS l     正确设计CSS特性
字符串 JavaScript变量 <script>var   currentValue=’不可信数据 ‘;</script> <script>someFunction(‘不可信数据 ‘);</script> l   确保JavaScript变量被引号包含 l   使用Hex转义JavaScript l   使用Unicode转义JavaScript l   避免反斜杠转义(\” 或 \’ 或\\)
HTML HTML Body <div>不可信HTML</div> 规范化HTML (JSoup,   AntiSamy, HTML Sanitizer)
字符串 DOM XSS <script>document.write(“不可信输入: ” +   document.location.hash);<script/> 参考防御基于DOM的XSS

安全的HTML属性包括: align, alink, alt, bgcolor,border, cellpadding, cellspacing, class, color, cols, colspan, coords, dir,face, height, hspace, ismap, lang, marginheight, marginwidth, multiple, nohref,noresize, noshade, nowrap, ref, rel, rev, rows, rowspan, scrolling, shape,span, summary, tabindex, title, usemap, valign, value, vlink, vspace, width

四、输出编码规则汇总

输出编码(与跨站脚本有关)的目的是在用户输入作为数据显示的时候,转换不可信的输入为安全形式,以避免被浏览器当做代码执行。下面的表格列出了阻止跨站脚本的关键输出编码方法。

编码类型 编码规则
HTML实体转义 转换 & 为 &amp; 转换 < 为 &lt; 转换 > 为 &gt; 转换 ” 为 &quot; 转换 ‘ 为 &#x27; 转换 / 为 &#x2F;
HTML属性编码 除了字母,转换所有字符为&#xHH;形式,包括空格(HH = Hex数值)
URL编码 标准的编码请参考http://www.w3schools.com/tags/ref_urlencode.asp。URL编码应该只用于参数部分,而不是整个URL或URL路径部分。
JavaScript编码 除了字母,转换所有字符为\uXXXX的unicode转义形式(X=整数)
CSS Hex编码 CSS转义支持\XX和\XXXXXX的形式。如果下一个字符会继续转义序列,那使用两个字符的转义形式可能会出现问题。有两种解决办法(a)在CSS转义后添加一个空格(会被CSS解析器忽略)(b)使用0填充以实现完整的CSS转义格式。

五、作者和主要编辑者

Jeff Williams – jeff.williams[at]contrastsecurity.com

Jim Manico – jim[at]owasp.org

Neil Mattatall – neil[at]owasp.org

版权与许可

版权所有:OWASP基金会©

本文档基于 Creative Commons Attribution ShareAlike4.0license 发布。任何重用或发行,都必须向他人明确该文档的许可条款。 http://creativecommons.org/licenses/by-sa/4.0/

中文翻译:walletong@ansion

原文地址:https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Shee

*本文作者:Jeff Williams、Jim Manico、Neil Mattatall