前端安全之XSS攻防之道

1 什么是XSS

XSS全称Cross Site Script,意为跨站脚本攻击。本质上是一种“HTML注入”,由于历史原因,最初这种攻击在演示的时候是跨域攻击的,所以就叫跨域脚本攻击。但是目前,XSS指所有通过外部注入,导致浏览器执行了攻击者的脚本而产生的攻击行为,已经不特指跨站。

2 XSS的分类

XSS攻击根据攻击效果可以分为以下三类:

1.反射型XSS

按照字面意思理解,就是需要把黑客的代码反射给浏览器执行,这种攻击一般是用户点击了黑客构造的一个url链接,然后url链接中参数具有攻击性,页面脚本直接按照正常的逻辑去解析了这个参数,导致恶意脚本被执行。

2.存储型XSS

存储型XSS顾名思义,是将攻击代码通过某种方式存储到服务器端,等到某个特定环境的时候,再在前端展示解析并执行,达到攻击的目的。

比如一个某个博客网站,黑客写一篇带有可执行脚本的内容发表之后,内容存储到服务器端。后面,每个访问这篇文章的用户,页面都将执行这段脚本,并受到攻击。

3.DOM XSS

DOM XSS不是按照是否存储数据到服务器来划分,它特指通过修改DOM节点的内容,然后触发脚本执行的一种攻击方式。

所以当一个反射型XSS通过DOM节点输出脚本控制,它就属于DOM XSS,一个存储型XSS通过DOM节点输出控制,也属于DOM XSS。

比如一个网站的搜索引擎输入框,会将你输入的搜索关键词,显示在另一个DOM中,如果没做任何过滤和转义,那么输入的而已内容将被解析,从而导致XSS。

3 XSS的典型案例分析和防御

在分析案例之前,我们需要了解XSS的两个关键因素:输入源,输出点。

输入源是XSS攻击中,攻击代码的来源,可以是url,可以是表单内容,可以是事件消息数据等。

输出点是攻击代码被触发的最直接的方式,比如innerHTML执行,eval执行,javascript:方式触发。

3.1 url参数攻击

下面的示例给出了一个典型的反射性XSS的行为:

<!--  a.test.com/username.html -->
<html>
<head>
	<title>attack</title>
</head>
<body>
<div>
Hello,<span id="username"></span>
</div>
<script>
	var userName=decodeURIComponent(milo.request('username'));
	document.getElementById('username').innerHTML=userName;
</script>
</body>
</html>

上面是一个简单的取url上的username参数作为用户名,直接显示到页面的span元素中的逻辑。

假如该页面位于:a.test.com/username.html

那么攻击者可以让用户访问如下链接:

a.test.com/username.html?username=%3Cscript%3Ealert(document.cookie)%3C%2Fscript%3E

即可达到攻击目的,用户访问后,将会弹出用户端cookie。

1 输入源

这种攻击的输入源很明显,就是url上的参数。

2 输出点

这种攻击的输出点可以是多种,看具体的执行方式,本案例中的输出点是innerHTML执行导致了脚本节点被解析。

3 防御

针对本案例,我们可以用以下两种方式进行防御:

url参数过滤,对url上取到的username参数进行xss过滤,转义。使innerHTML的时候不能被解析成script节点。

使用innerText方式,或者jquery的$.text方法写入内容,就不会被解析。

3.2 表单用户输入攻击

表单输入一般会制造存储型XSS,输入的内容存到了后端,然后再在其它的页面显示出来,显示的时候执行了相关的脚本逻辑,导致被攻击。

例如有某博客网站:www.xxblog.com

写博客的页面在 www.xxblog.com/write.html ,

看文章的页面在 www.xxblog.com/blogs/xxx.html。

下面简要列一下两个页面的核心代码:

写博客的页面

  <html>
 	<head>
		<title>写博客</title>
	</head>
	<body>
		<textadrea id="blog_content" placeholder="请输入博客内容"></textadrea>
		<a class="btn">发布博客</a>
		<script type="text/javascript">
			var blogContent=$('#blog_content').text();
			$.ajax({
				url:'api/submitBlog.php',
				type:'POST',
				data:{
					blog:blogContent
				},
				success:function(data){
					if(0 == data.iRet){
						alert('发表成功');
					}
				}
			})
		</script>
	</body>
 </html>

看博客的页面:

  <html>
  	<head>
		<meta http-equiv="content-type" content="text/html;charset=utf-8" >
		<title>博客详情</title>
	</head>
	<body>
	<div id="details"></div>
	<script type="text/javascript">
		$.ajax({
			url:'api/getBlog.php',
			type:'GET',
			data:{
				blogId:'xx'
			},
			success:function(data){
				if(0 == data.iRet){
					//显示文章内容
					$('#details').html(data.blogContent);
				}
			}
		})
	</script>
	</body>
  </html>

当黑客写入博客的时候,插入一些可执行的脚本到比如script,onerror事件等地方。发布这些博客之后,带恶意代码的博客就被存储到了网站的数据库中,最后其它用户浏览这些博客的时候,可执行代码就会被解析执行,达到了攻击的目的。

1 输入源

本案例中的输入源可以说是网站数据库存储的数据,更源头的应该是存储的内容的来源:写博客的表单节点。

2 输出点

本案例中的输出点,同样是innerHTML直接输出,导致了html解析,然后可执行的js呗执行。

下图1是本案例中存储型XSS攻击的4个关键环节:

图1 存储型XSS攻击的4个环节

3 防御

推荐1 后端过滤博客内容。存储型xss要过滤的话,最好是在后端过滤,因为后端可以认为是恶意代码源最可靠的拦截阶段了。

图3显示了存储型XSS经历4个环节:

(1)前端表单制造内容------(2)提交内容------(3)存储内容-------(4)前端HTML显示存储的内容

其中第2步可以伪造,可以不通过前端页面发布内容。所以过滤逻辑必须在第3步和第4步执行。选择第3步不选择第4步的原因是恶意代码可能会在多个页面(例如图中的PageB,PageC,PageD等)被使用和解析,但是恶意代码值保存一份。所以在入库的时候在后端逻辑中过滤是最直接也是最省事和安全的。

.

不推荐2 博客显示的时候使用innerText或者$.text()方法,但是这种方法不推荐使用。因为博客属于富文本内容,单纯的显示文本不可取,博客内容的本身就是一段拥有图片,颜色,字体等各种控制元素的html内容。

3.3 PostMessage攻击

有一类XSS是通过前端跨域的机制,让网站执行了跨域的不安全脚本导致的。

我们知道postMessage可以跨域通信,假如现在有一个场景是跨域设置cookie,在活动页面a.test.com需要将一些cookie对象设置到game.test.com中,这里需要活动页面通过postMessage将cookie数据传给game.test.com的页面B。这时,假如有一个黑客网站hack.com的页面,也通过postMessage向页面B发送带有攻击的数据,然后B获取到带有攻击数据的cookie数据,解析后导致B页面受到攻击,窃取game.test.com的cookie或者test.com下的cookie。

下面是game.test.com的页面B接收message消息的代码:

//跨域设置cookie的message事件
function doSetCookie(e){
	var data=e.data;
	var oCookie='';
	eval('oCookie='+data);
	//拿oCookie去设置
	//...
}

if(window.addEventListener){
	window.addEventListener('message',doSetCookie,false)
}else{
	window.onmessage=doSetCookie;
}

正常情况下,a.test.com通过postMessage向B设置cookie,没有问题。下面将模拟黑客网站hack.com向B发送一个攻击信息:

 //hack.com的攻击信息
 var oIframe=document.getElementById('winB');
 oIframe.contentWindow.postMessage("(alert(document['cookie']))","*");

以上信息代码会 B页面的message事件,然后执行eval函数,导致cookie被窃取。

其实纵观整个流程我们发现了B页面很明显的几个漏洞:

1.接受message信息的时候,没有判断来源origin

2.取message数据data之后,使用了eval函数直接解析数据

我们来分析本案例中的XSS输入源,输出点。

1. 输入源

本案例中的输入源是postMessage通信信息。

2. 输出点

本案例中,通过eval函数解析message数据。输出点是eval函数。

3. 防御

针对本类postMessage通信引起的XSS漏洞,其本质上属于反射性XSS。postMessage通信可以跨域,但是我们可以控制来源域,一般我们业务逻辑接受message信息,只针对白名单的来源域即可,这样可以屏蔽白名单之外的黑客网站页面发送的有害信息。还有一种方案同样是作用在输出点上,就是避免message的data被直接解析,避免使用eval,innerHTML等危险的方式处理data。可以给数据制定一个规范,比如用竖线分割不同字段的值等等。

总结来说就是2点:

1 对来源域进行判断,最好加上白名单验证机制。如下:

function onMessage(event){
	var origin=event.origin || event.originalEvent.origin;
	if(isLegal(origin)){
		//do something
	}else{
		//origin不在白名单内,直接return
		return;
	}
}

2 对message中拿到的数据进行过滤,避免使用高危方法处理数据。比如针对本案例,可以这么做:

//拿到信息
var data=event.data;
//假如cookie是固定格式为:a=xx; b=xxx; c=xxx
var arrCookie=data.split(' ;');
//do something

3.4 第三方组件漏洞

这种类型的XSS危害性很大,第三方组件的漏洞导致的XSS不是特指某一种类型的XSS,而是指页面中引用第三方组件,调用第三方组件的方法时,由于第三方组件的漏洞导致的XSS安全问题。

曾经的jquery就曝出了XSS漏洞,在jquery的1.11版本之前,使用jquery的选择器方法,如果传入的选择器字符串是非法的,可能会导致选择器字符串被执行。

如下所示:

//假设有以下表单页面a.html供用户填写个人信息
var domId=location.hash;
var sTel=$(domId).val();//获取用户输入的电话号码

假如用户点击了非法的链接进入个人信息填写页面,可能会导致XSS漏洞:如下链接就直接会窃取用户的cookie

<a href="./a.html#userinfo<img src=1 onerror=alert(document.cookie)>">填写个人信息</a>

上面的链接跳转到信息填写页面,然后hash参数是个具有危害性的内容,在jquery1.11之前的版本中作为选择器传入,都会导致页面创建HTML,从而执行了onerror中的js代码,导致XSS攻击。

1 输入源

本案例中的输入源是url上的hash参数

2 输出点

本案例中的输出点是jquery组件的选择器方法$,导致了恶意参数传入后被解析成HTML执行。

3 预防

针对本案例,如何有效的避免这类XSS攻击呢。下面支招:

1 输入检查,从hash获取的元素id,需要进行过滤,因为id不可能带有括号,冒号,等于号等特殊字符,所以制定相关的策略进行过滤即可

.

2 多关注业界XSS相关的动态,禁止使用会触发XSS的第三方组件和版本。

4 XSS小结

XSS攻击的案例数不胜数,这里笔者只列举了几种常见的常见加以分析。其实,综合来看,XSS攻击的本质还是一种“HTML”和脚本注入。在实际工作中,面对XSS,不要惊慌,分析输入源和输出点综合进行防御即可。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券