xss攻防浅谈

目录
  1. 导读
  2. xss攻击方式
  3. 防止xss攻击
    1. 转义
    2. 使用浏览器自带的xss-filter
    3. Content Security Policy
    4. X-Frame-Options
    5. Http-Only
    6. iframe 沙箱环境
    7. 其他安全相关的HTTP头
      1. X-Content-Type-Options
      2. HPKP(Public Key Pinning)
      3. HSTS (HTTP Strict-Transport-Security)
  4. 前端xss过滤

导读

XSS (Cross-Site Script) 攻击又叫跨站脚本攻击, 本质是一种注入攻击. 其原理, 简单的说就是利用各种手段把恶意代码添加到网页中, 并让受害者执行这段脚本. XSS能做用户使用浏览器能做的一切事情. 伟大的同源策略也无法保证不受XSS攻击,因为此时攻击者就在同源之内.

xss攻击方式

从攻击的方式可以分为

  • 反射型
  • 存储型
  • 文档型

这种分类方式有些过时, 长久以来, 人们认为XSS分类有以上三种, 但实际情况中经常无法区分, 所以更明确的分类方式可以分为以下两类:

  • client(客户端型)
  • server(服务端型)

当一端xss代码是在服务端被插入的, 那么这就是服务端型xss, 同理, 如果代码在客户端插入, 就是客户端型xss.

防止xss攻击

转义

无论是服务端型还是客户端型xss,攻击达成都需要两个条件

  • 代码被注入
  • 代码被执行

其实只要做好无论任何情况下保证代码不被执行就能完全杜绝xss攻击.

总之, 任何时候都不要把不受信任的数据直接插入到dom中的任何位置, 一定要做转义。

对于某些位置,不受信任的数据做转义就可以保证安全

  • 一般的标签属性值
  • div body 的内部html

对于某些位置,即使做了转义依然不安全

  • script标签中
  • 注释中
  • 表签的属性名名
  • 标签名
  • css标签中

使用JSON.parse 而不是eval, request 的content-type要指定是Content-Type: application/json;

如果链接的URL中部分是动态生成的, 一定要做转义.

使用浏览器自带的xss-filter

xss-filter

可以通过http头控制是否打开 xss-filter, 默认为开启.

通常情况下, 在http header中加入以下字段表示启用xss-filter.

X-XSS-Protection:1 (默认)
X-XSS-Protection:1;mode=block (强制不渲染, chrome跳空白页,IE展示一个#号)

如需禁用xss-filter, 将 X-XSS-Protection 设置为0即可.

如上, 现代浏览器都对反射型xss有一定的防御力, 其原理是检查url和dom中元素的相关性. 但这并不能完全防止反射型xss. 这里有个可供测试的链接, 传送门: XSS Test Page.

另外, 浏览器对于存储型xss并没有抵抗力, 原因很简单, 用户的需求是多种多样的. 所以, 抵御xss这件事情不能指望浏览器.

Content Security Policy

Content Security Policy, 即内容安全策略, 简称csp.

为了缓解很大一部分潜在的跨站脚本问题, 浏览器的扩展程序系统引入了CSP. CSP 管理网站允许加载的内容, 并且使用白名单的机制对网站加载或执行的资源起作用. 在网页中, 这样的策略通过 HTTP 头信息或者 meta 元素定义.

CSP 并不是用来防止 xss 攻击的, 而是最小化 xss 发生后所造成的伤害. 实际上, 除了开发者自己做好 xss 转义, 并没有别的方法可以防止 xss 的发生. CSP 可以说是HTML5给web安全带来的最实惠的东西. 那么如何引入 CSP 呢?

  1. 通过response头

只允许脚本从本源加载Content-Security-Policy: script-src ‘self’

  1. 通过HTML的META标签

作用同上<meta http-equiv=”Content-Security-Policy” content=”script-src ‘self’”>

那么CSP 除了限制script-src 之外还能限制什么呢?

base-uri : 限制这篇文档的uri

child-src :限制子窗口的源(iframe,弹窗等),取代frame-src

connect-src :限制脚本可以访问的源

font-src : 限制字体的源

form-action : 限制表单能够提交到的源

frame-ancestors : 限制了当前页面可以被哪些页面以iframe,frame,object等方式加载

frame-src :deprecated with child-src,限制了当前页面可以加载哪些源,与frame-ancestors对应

img-src : 限制图片可以从哪些源加载

media-src : 限制video, audio, source, track 能够从哪些源加载

object-src :限制插件可以从哪些源加载

sandbox :强制打开沙盒模式

可以看出, CSP是一个强大的策略, 几乎可以限制了所有能够用到的资源的来源. 使用好CSP可以很大成都降低XSS带来的风险.

另外, CSP还提供一个报告的头域 Content-Security-Policy-Report-Only, 使用这个头域, 浏览器会向服务器报告csp状态.

Content-Security-Policy-Report-Only: script-src 'self'; report-uri http://cspReport/

使用了上面的设置, 若页面上存在内联的js, 它依然会执行, 不过浏览器会向发送一个post请求, 包含如下信息.

{ 
  "csp-report":
      { 
      "document-uri": "http://cspReport/test.php",
      "referrer": "",
      "violated-directive": "script-src 'self'",
      "original-policy": "script-src 'self'; report-uri http://cspReport/",
      "blocked-uri": ""
    }
}

CSP 目前有两版, [CSP1][https://www.w3.org/TR/2012/CR-CSP-20121115/] 和 [CSP2][https://www.w3.org/TR/CSP2/]. 两版的支持状态可以在 http://caniuse.com/#search=csp 中查到. 如下:

CSP1支持性

CSP1

CSP2支持性

CSP2

CSP虽然提供了强大的安全保护, 但是他也造成了如下问题: Eval及相关函数被禁用、内嵌的JavaScript代码将不会执行、只能通过白名单来加载远程脚本.

X-Frame-Options

X-Frame-Options 响应头是用来给浏览器指示允许一个页面可否在 frame, iframe 或者 object 等标签中展现的标记. 网站可以使用此功能, 来确保自己网站的内容没有被嵌到别人的网站中去, 也从而避免了点击劫持 (clickjacking) 的攻击. 但以后可能被CSP的 frame-ancestors取代。目前支持的状态比起 CSP frame-ancestors要好.

X-Frame-Options 共有三个值:

  • DENY 表示这个页面不允许被以frame的方式加载
  • SAMEORIGIN 表示这个页面只允许被同源页面加载
  • ALLOW-FROM uri 表示这个页面只能被特定的域加载

服务器配置

java代码:

response.addHeader("x-frame-options","SAMEORIGIN");

Nginx配置:

addheader X-Frame-Options SAMEORIGIN

Apache配置:

Header always append X-Frame-Options SAMEORIGIN

浏览器兼容性

特性 Chrome Firefox (Gecko) Internet Explorer Opera Safari
基础支持 4.1.249.1042 3.6.9 (1.9.2.9) 8.0 10.5 4.0
ALLOW-FROM 支持 Not supported 18.0 8.0? ? Not supported

Http-Only

使用 http-only后, 可禁止js读写cookie, 可以保证即使发生了xss, 用户的cookie也是安全的.

iframe 沙箱环境

HTML5为iframe提供了安全属性 sandbox, 进而限制iframe的能力. 如下:

<iframe src="untrusted.html" sandbox="allow-scripts allow-forms"></iframe>

其他安全相关的HTTP头

X-Content-Type-Options

X-Content-Type-Options 阻止浏览器进行content-type 嗅探, 能够防止类型嗅探攻击.

这个header主要用来防止在IE9、chrome和safari中的MIME类型混淆攻击. 通常浏览器可以通过嗅探内容本身的方法来决定它是什么类型, 而不是看响应中的content-type值. 通过设置 X-Content-Type-Options:如果content-type和期望的类型匹配,则不需要嗅探,只能从外部加载确定类型的资源. 举个例子, 如果加载了一个样式表, 那么资源的MIME类型只能是text/css, 对于IE中的脚本资源, 以下的内容类型是有效的:

application/ecmascript  
application/javascript  
application/x-javascript  
text/ecmascript  
text/javascript  
text/jscript  
text/x-javascript  
text/vbs  
text/vbscript

对于chrome, 则支持下面的MIME 类型:

text/javascript  
text/ecmascript  
application/javascript  
application/ecmascript  
application/x-javascript  
text/javascript1.1  
text/javascript1.2  
text/javascript1.3  
text/jscript  
text/live script

正确的设置

nosniff – 这个是唯一正确的设置.

通常不正确的设置

‘nosniff’ – 引号是不允许的
: nosniff – 冒号也是错误的

如何检测

在IE和chrome中打开开发者工具,在控制台中观察配置了nosniff和没有配置nosniff的输出有啥区别.

HPKP(Public Key Pinning)

HPKP 是一个response 头, 用来检测一个证书的公钥是否发生了改变, 防止中间人攻击.

我们知道, 受信任的 CA(证书颁发机构)有好几百个, 他们成为整个网站身份认证过程中一个较大的攻击面. 现有的证书信任链机制最大的问题是, 任何一家受信任的 CA 都可以签发任意网站的站点证书, 这些证书在浏览器看来, 都是合法的.

HPKP 技术给予我们主动选择信任 CA 的权利. 它的工作原理是通过响应头或者 标签告诉浏览器当前网站的证书指纹, 以及过期时间等其它信息. 未来一段时间内, 浏览器再次访问这个网站必须验证证书链中的证书指纹, 如果跟之前指定的值不匹配, 即便证书本身是合法的, 也必须断开连接.

HPKP 官方文档见 RFC7469 , 目前 Firefox 35+ 和 Chrome 38+ 已经支持. 它的基本格式如下:

Public-Key-Pins: pin-sha256="base64=="; max-age=expireTime [; includeSubdomains][; report-uri="reportURI"]
HSTS (HTTP Strict-Transport-Security)

HSTS 是国际互联网工程组织IETE正在推行一种新的Web安全协议, 可以用来抵御中间人攻击, 它强制浏览器使用TSL作为数据通道, 即强制使用HTTPS与服务器创建连接.

服务器开启HSTS的方法是, 当客户端通过HTTPS发出请求时, 在服务器返回的超文本传输协议响应头中包含Strict-Transport-Security字段. 非加密传输时设置的HSTS字段无效.

比如, https://xxx 的响应头含有Strict-Transport-Security: max-age=31536000; includeSubDomains. 这意味着两点:

在接下来的一年(即31536000秒)中, 浏览器只要向xxx或其子域名发送HTTP请求时, 必须采用HTTPS来发起连接. 比如, 用户点击超链接或在地址栏输入 http://xxx/ , 浏览器应当自动将 http 转写成 https, 然后直接向 https://xxx/ 发送请求.

在接下来的一年中, 如果 xxx 服务器发送的TLS证书无效, 用户不能忽略浏览器警告继续访问网站.

不足就是, 用户首次访问网址是不受HSTS保护的, 这是因为首次还未收到HSTS. 解决方案有两个, 一是浏览器预置 HSTS域名列表, Google Chrome、Firefox 和 Internet Explorer 实现了这一方案. 二是将HSTS信息加入到域名系统记录中.

前端xss过滤

最后提供一种前端xss过滤的方法

function xssCheck(str,reg){
  return str ? str.replace(reg || /[&<">'](?:(amp|lt|quot|gt|#39|nbsp|#\d+);)?/g, function (a, b) {
    if(b){
      return a;
    }else{
      return {
        '<':'&lt;',
        '&':'&amp;',
        '"':'&quot;',
        '>':'&gt;',
        "'":'&#39;',
      }[a]
    }
  }) : '';
}

本文就讨论这么多内容,大家有什么问题或好的想法欢迎在下方参与留言和评论.

本文作者: louis

本文链接: http://louiszhai.github.io/2016/03/05/xss/

参考文章

Fork me on GitHub