《白帽子讲web安全》之 XSS 防御 + 本章(跨站脚本攻击 XSS)小结

流行的浏览器都内置了一些对抗XSS的措施,比如Firefox的CSP、Noscript扩展,IE 8内置的XSS Filter等。而对于网站来说,也应该寻找优秀的解决方案,保护用户不被XSS攻击。

一、四两拨千斤:HttpOnly

HttpOnly最早由微软提出,并在IE 6实现,至今已经逐渐成为一个标准。浏览器将禁止页面的JavaScript访问带有HttpOnly属性的Cookie。

严格来说,HttpOnly并非为了对抗XSS——HttpOnly解决的是XSS之后的Cookie劫持攻击。之前XSS攻击进阶(1)中初探XSS时提到,如果Cookie设置了HttpOnly,则这种窃取Cookie的攻击将会失败,因为JavaScript读取不到Cookie值。

一个Cookie的使用过程如下:

Step1:浏览器向服务器发起请求,这时没有Cookie;
Step2:服务器返回时发送Set-Cookie头,向客户端浏览器写入Cookie;
Step3:在该Cookie到期前,浏览器访问该域下的所有页面,都将发送该Cookie。

HttpOnly是在Set-Cookie时标记的:

Set-Cookie:<name>=<value>[; <Max-Age>=<age>]
[; expires=<date>][; domain=<domain_name>]
[; path=<some_path>][; secure][; HttpOnly]

需要注意,服务可能会设置多个Cookie(多个key-value对),而HttpOnly可以有选择性地加在任何一个Cookie值上。

在某些时候,应用可能会需要JavaScript访问某几项Cookie,这种Cookie可以不设置HttpOnly标记;而仅把HttpOnly标记给用于认证的关键Cookie。

添加HttpOnly的过程很简单,且效果明显。但在部署时需要注意,如果业务非常复杂,则需要在每一个Set-Cookie的地方,给关键的Cookie都加上HttpOnly。漏掉一个,都可能使整个方案失效。

使用HttpOnly有助于缓解XSS攻击,但是HttpOnly不是万能的,添加了HttpOnly不等于解决了XSS问题。

XSS攻击带来的不光是Cookie劫持问题,还有窃取用户信息、模拟用户身份执行操作等诸多严重的后果。如前文所述,攻击者利用AJAX构造HTTP请求,以用户身份完成的操作,就是在不知道Cookie的情况下进行的。

 

二、输入检查

常见的web漏洞,如XSS、SQL Injection等,都要求攻击者构造一些特殊字符,这些特护字符可能是正常用户不会用到的,所以输入检查就有存在的必要了。

输入检查,在很多时候也被用于格式检查(比如:用户名只允许使用字母和数字的组合,注册的电话、手机号、邮件等)。这些格式检查,类似于一种“白名单”,也可以让一些基于特殊字符的攻击失效。

输入检查的逻辑,必须放在服务器端代码中实现。如果只是在客户端使用JavaScript进行输入检查,很容易被攻击者绕过。目前web开发的普遍做法,是同时在客户端和服务器端代码中实现相同的输入检测。客户端JavaScript的输入检测,可以阻挡大部分误操作的正常用户,从而介于了服务器资源。

在XSS的防御上,输入检查一般是检查用户输入的数据中是否包括一些特殊的字符,如:<、>、'、" 等。如果发现这些字符,则将这些字符过滤或者编码。比较智能的的“输入检查”,可能还会匹配XSS的特征。比如查找用户数据中是否包含了“<script>”、“javascript”等敏感字符。这种输入检查的方式,可以称为“XSS Filter”。

XSS Filter在用户提交数据时获取变量,并进行XSS检查;但此时用户数据并没有结合渲染页面的HTML代码,因此XSS Filter对语境的理解并不完整。

比如下面的这个XSS漏洞:

<script src="$var" ></script>

其中“$var”使用户可以控制的变量。用户只需要提交一个恶意脚本所在的URL地址,既可以实施XSS攻击。

如果只一个全局性的XSS Filter,则无法看到用户数据的输出语境,而只能看到用户提交了一个URL,就用可能会漏报。因为在绝大多数情况下,URL是一种合法的用户数据。

XSS Filter还有一个问题——其对“<”、“>”等字符的处理,可能会改变用户数据的语义,比如,用户输入:1+1<2(发现了敏感字符,但过于粗暴地过滤或者替换了“<”)。

输入数据,可能会被展示在不同的地方,每个地方的语境可能互不相同,如果使用单一的替换操作,则可能会出问题。

 

三、输出检查

既然“输入检查”存在这么多问题,那么“输出检查”也会存在问题。

一般来说,除了富文本的输出外,在变量输出到HTML时,可以使用编码或者转义的方式来防御XSS攻击。

  1. 安全的编码函数

    编码有很多种,针对于HTML代码的编码方式是HtmlEncode.。

    HtmlEncode作为一种函数实现,它的作用是将字符转换为HTML Entities,对应的标准是ISO-8859-1。

    为了对抗XSS,在HtmlEncode中要求至少转换以下字符:

    & --> &amp
    < --> &lt
    > --> &gt
    " --> quot
    ' --> &#27;  &apos不推荐
    / --> &#2F; 包含反斜线是因为它可能会闭合一些HTML entity

        在PHP中,有htmlentities()和htmlspecialchars();相应地,JavaScript可以使用JavascriptEncode。

        需要注意,编码后的数据长度可能会发生改变,从而影响某些功能,编写代码时应注意。

    2.不止需要一种编码

XSS攻击主要发生在MVC框架的View层。大部分的XSS漏洞都可以在模板系统中解决。

 

四、正确地防御XSS

为了更好地设计XSS防御方案,需要认清XSS产生的本质原因。

XSS的本质还是一种“HTML注入”,用户的数据被成了HTML代码的一部分来执行,从而混淆了原来的语义,产生了新的语义。

如果网站使用了MVC框架,那么XSS就发生在View层——在应用拼接变量到HTML页面时产生。所以在用户提交数据处进行输入检查方案,其实并不是在真正发生攻击的地方做防御。

想要根治XSS,可以列出所有的XSS可能发生的场景,再逐一解决。

如果用户能够完全控制URL,则可以执行脚本的方式很多。如何解决这一情况?

一般来说,如果变量是整个URL,则应该先检查变量是否以“http”开头(如果不是则自动添加),以保证不会出现伪协议类的XSS攻击。

<a href="$var" >test</a>

在此之后,再对变量进行URLEncode,即可保证不会有此类的XSS发生了。

 

 

五、处理富文本

有些时候,网站需要允许用户提交一些自定义的HTML代码,称之为“富文本”。比如一个用户在论坛里的发帖,帖子的内容里要有图片、视频、表格等等,这些“富文本”的效果都需要通过HTML代码来实现。

那就需要区分安全的“富文本”和有攻击性的XSS。

在处理富文本时,还是要回到“输入检查”的思路上来。“输入检查”的主要问题是,在检查时还不知道变量的输出语境。但用户提交的“富文本”数据,其语义是完整的HTML代码,在输出是也不会拼凑到某个标签的属性中。因此可以特殊情况特殊处理。

在过滤富文本时,“事件”应该被严格禁止,因为“富文本”的展示需要里不应该包括“事件”这种动态效果。而一些危险的标签,比如:<iframe>、<script>、<base>、<form>等,也是应该严格禁止。

在标签的选择上,应该使用白名单,避免使用黑名单。比如:只允许使用<a>、<img>、<div>等比较“安全”的标签存在。

“白名单原则”不仅仅用于标签的选择,同样应该用于属性与事件的选择。

在富文本的过滤中,处理CSS也是一件麻烦的事情。如果允许用户自定义CSS、style,则也可能导致XSS攻击。因此尽可能地禁止用户自定义CSS和style。如果一定要允许用户自定义样式,则只能像过滤“富文本”一样过滤“CSS”。这需要一个CSS Parser对样式进行智能分析,检查其中是否包含危险代码。

 

六、防御 DOM Based XSS

DOM Based XSS是一种比较特别的XSS漏洞,前面提到的几种防御方式都不太适用,需要特别对待。

事实上,DOM Based XSS是从JavaScript中输入数据到HTML页面里。而之前提到的都是针对“从服务器应用直接输出到HTML页面”的XSS漏洞,因此并不适用于DOM Based XSS。

正确的防御方法是:从JavaScript输出到HTML页面,相当于一次XSS输出的过程,需要分语境使用不同的编码函数。

 

七、换个角度看 XSS 的风险

之前谈到的所有XSS攻击,都是从漏洞形成的原理上看的。如果从业务风险的角度来看,则会有不同的观点。

一般来说,存储型XSS的风险会高于反射型XSS。因为存储型XSS会保存在服务器上,有可能会跨页面存在。它不会改变页面URL的原有结构,因此有时候还会逃过一些IDS的检测。比如IE 8 的XSS Filter和Firefox的Noscript Extension,都会检查地址栏中的地址是否包含XSS脚本。而跨页面的存储型XSS可能会绕过这些检测工具。

从攻击过程来说,反射型XSS,一般要求攻击者诱使用户点击一个包含XSS代码的URL连接;而存储型XSS,则只需要让用户查看一个正常的URL连接。比如一个web邮箱的邮件正文页面存在一个存储型的XSS漏洞,当用户打开一封新邮件时,XSS Payload会被执行。这样的漏洞极其隐蔽,且埋伏在用户的正常业务中,风险颇高。

从风险的角度看,用户之间有互动的页面,是可能发起XSS Worm攻击的地方。而根据不同页面的PageView高低,也可以分析出哪些页面受XSS根据后的影响会更大。比如:在网站的首页发生的XSS攻击,肯定比网站合作伙伴页面的XSS攻击要严重得多。

在修补XSS漏洞时遇到的最大的挑战之一是:数量太多。因此开发者可能来不及,也不愿意修补这些漏洞。所以从业务风险的角度来重新定位每个XSS漏洞,就具有了重要的意义。

 

八、本章小结

本章讲述了XSS攻击的原理,并从开发者的角度阐述了如何防御XSS。

理论上,XSS漏洞虽然复杂,但却是可以彻底解决的。在设计XSS解决方案时,应该深入理解XSS攻击的原理,针对不同的场景使用不同的方法。同时很多开源的项目为我们提供了参考。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值