不可忽视的前端安全问题——XSS攻击

5,101 阅读9分钟

XSS是什么

XSS是跨站脚本攻击(Cross-Site Scripting)的简称。

XSS是一种注入脚本式攻击,攻击者利用如提交表单、发布评论等方式将事先准备好的恶意脚本注入到那些良性可信的网站中,当其他用户进入该网站后,脚本就在用户不知情的情况下偷偷地执行了,这样的脚本可能会窃取用户的信息、修改页面内容、或者伪造用户执行其他操作等等,后果不可估量。

XSS攻击通常会发生在以下情况下:

  • 向一个web app中输入含有一些不良代码的内容,通常是一段含有http请求的代码
  • 这些内容包含在发给其他用户的动态内容之中,而且这些内容还没有经过校验

发送到Web浏览器的恶意内容通常采用JavaScript代码片段的形式,但也可能包括HTML,Flash或浏览器可能执行的任何其他类型的代码。 基于XSS的攻击方式几乎是无限的。但是它们通常的方式是:

  • 向攻击者发送包括诸如cookie或其他会话信息的私有数据
  • 攻击者篡改页面的内容
  • 在用户的机器上,以含有漏洞的网站为幌子,执行其他恶意操作

OWASP(Open Web Application Security Project)最新公布的2017 10项最严重的 Web 应用程序安全风险中,XSS榜上有名。


而事实上,XSS在每次的TOP10评比中都会出现……


XSS实例

想知道XSS是如何造成攻击的?可以看这个下面的文章,它介绍了一个XSS漏洞——XSS跨站脚本攻击过程最简单演示 - CSDN博客 案例中攻击者利用XSS可以获取用户的隐私信息。

XSS攻击的分类

一般会把XSS分为两类——存储型XSS(Stored XSS)和反射型XSS(Reflected XSS)

不过还有一种不为人知的类型——DOM-based XSS。


存储型XSS是指那些将恶意脚本永久的保存在目标服务器上的攻击方式,如存储在数据库、消息论坛、访问日志、评论内容扥等。

反射型XSS是当用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。Web服务器将注入脚本,比如一个错误信息,搜索结果等 返回到用户的浏览器上。浏览器会执行这段脚本,因为,它认为这个响应来自可信任的服务器。一个危险的XSS案例--轻松拿到登录用户的cookie - CSDN博客,这个例子中,如果有人诱导你点击了上面文章中写到的链接,那么你在站酷网站中的隐私信息就发送到了其他服务器中了。


DOM-Based型XSS是指攻击者利用原生JavaScript代码篡改客户端的DOM结构,导致用户操作执行了“意外”的动作。

关于DOM-Based型XSS可以看下面的例子

下面是一段网站的代码,它提供一个下拉框让你来选择语言,而且还根据你URL上的default参数来进行默认语言的推荐。

Select your language:

<select><script>

document.write("<OPTION value=1>"+document.location.href.substring(document.location.href.indexOf("default=")+8)+"</OPTION>");

document.write("<OPTION value=2>English</OPTION>");

</script></select>
…

通常情况下,这个页面的地址会是下面的样子:

http://www.some.site/page.html?default=French

而DOM-Based型XSS会利用这个页面DOM结构的漏洞,向受害者发送下面的链接

http://www.some.site/page.html?default=<script>alert(document.cookie)</script>

当受害者点开这个链接时,就会将用户的cookie全部alert出来了。


XSS的防范原则

关于XSS攻击的防范,我在OWASP上给出防范方法进行了精简,如果你有兴趣的话,可以去看详细内容

原则0——永远不要把不受信任的数据插入到原本允许JavaScript可以放置的地方

就像下面的代码中所示的那样:

<script>...永远不要把不受信任的数据放在这...</script>   直接放在script标签内
 
 <!--...永远不要把不受信任的数据放在这...-->             放在HTML注释内
 
 <div ...永远不要把不受信任的数据放在这...=test />       做为一个属性名
 
 <永远不要把不受信任的数据放在这... href="/test" />   做为一个标签名
 
 <style>...永远不要把不受信任的数据放在这...</style>   直接放在style标签内

原则1——在向元素中插入不受信任的HTML代码之前一定要进行转义

就像下面的代码中所示的那样:

<body>...将不受信任的数据转义后再放在这...</body>
 
 <div>...将不受信任的数据转义后再放在这...</div>
 
 any other normal HTML elements

常用的转义规则如下:

& --> &amp;
 < --> &lt;
 > --> &gt;
 " --> &quot;
 ' --> &#x27; 
 / --> &#x2F;

原则2——在向元素的属性插入不受信任的HTML代码之前一定要进行转义

看下面的代码:

<div attr=...将不受信任的数据转义后再放在这...>content</div>  
在没有加引号的属性值内
 
 <div attr='...将不受信任的数据转义后再放在这...'>content</div>
在加了单引号的属性值内

<div attr="...将不受信任的数据转义后再放在这...">content</div>
在加了双引号的属性值内

原则3——在用不受信任的数据向JavaScript代码赋值前,一定要进行转义

看下面的代码:

<script>alert('...将不受信任的数据转义后再放在这...')</script>     
在一个字符串之内
 
 <script>x='...将不受信任的数据转义后再放在这...'</script> 
在表达式的一侧
 
 <div onmouseover="x='...将不受信任的数据转义后再放在这...'"</div>  
在事件处理函数内

需要注意的是,有一些JavaScript函数永远无法安全的使用不受信任的数据作为输入,比如下面的代码:

<script>
 window.setInterval('即使你做了转义,但是仍然可能被XSS攻击');
 </script>


原则3.1——在HTML的上下文中对JSON值进行转义,并用JSON.parse()方法来读取值

一定要确保http response中的头部信息的content-type为application/json,而不是text/html,因为那样的话,很可能会被人利用进行XSS攻击。


一个坏的案例:

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 <-- bad
   ....
   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'."}   <-- 这里script标签有可能会被执行

一个好的案例:

 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 <--good
   .....
   .....


原则4——在将不受信任的数据作为CSS属性插入到文档之前一定要进行转义

看下面的代码

<style>selector { property : ...将不受信任的数据转义后再放在这...; } </style> 
属性值

 <style>selector { property : "...将不受信任的数据转义后再放在这..."; } </style>
属性值

 <span style="property : ...将不受信任的数据转义后再放在这...">text</span> 
属性值

需要注意的是,还是有一些CSS属性值对于“不受信任的”数据是无法确保万无一失的——即使做了转义,如下面的两个CSS属性:

{ background-url : "javascript:alert(1)"; }  
 { text-size: "expression(alert('XSS'))"; }   // only in IE

你应该确保所有CSS属性值引入的外部链接是由“http”开头的,而不是“javascript”开头的。


原则5——在向HTML的URL参数插入将不受信任的数据前,一定要将进行转义

看下面的代码

<a href="http://www.somesite.com?test=...将不受信任的数据转义后再放在这...">link</a >


加分原则1——对于cookie使用httpOnly标识

使用httpOnly标识后的cookie JavaScript是无法获取的,又由于cookie是基于同源原则,所以一定程度上会防范那些利用客户cookie的XSS攻击。


加分原则2——在http header中使用Content Security Policy

利用http header中的属性值Content-Security-Policy来防范XSS。HTTP 响应头 Content-Security-Policy 允许站点管理者在指定的页面控制用户代理的资源。除了少数例外,这条政策将极大地指定服务源 以及脚本端点。


加分原则3——使用自动转义模板系统

许多Web应用程序框架提供了自动的上下文转义功能,如AngularJS严格的上下文转义Go模板。 尽可能使用这些技术。


加分原则4——在http header中使用X-XXS-Protection

HTTP X-XSS-Protection 响应头是Internet Explorer,Chrome和Safari的一个功能,当检测到跨站脚本攻击 (XSS)时,浏览器将停止加载页面。虽然这些保护在现代浏览器中基本上是不必要的,当网站实施一个强大的Content-Security-Policy来禁用内联的JavaScript ('unsafe-inline')时, 他们仍然可以为尚不支持 CSP 的旧版浏览器的用户提供保护。



总结

XSS攻击的后果是不可估量的,而往往他又是容易被人忽视的。结合上面提到的几点,检查一下自己的Web App是否有上面的漏洞。

如果你坚持将全文都看完,你一定深爱着前端技术,那我觉得你有必要关注一下我的公众号——较真的前端,在那里会有更多的技术分享和前端干货等着你。


参考文章:
8大前端安全问题(上) - ThoughtWorks洞见 
Cross-site Scripting (XSS) 
XSS (Cross Site Scripting) Prevention Cheat Sheet