XSS的定义
攻击者利用网站漏洞把恶意的脚本代码(通常包括HTML代码和JavaScript脚本)注入到网页中,当其他用户浏览这些网页时,就会执行其中的恶意代码,对受害用户可能采取cookie窃取、会话劫持、钓鱼欺骗。
XSS的攻击方式
反射型
发出请求时,XSS代码出现在URL中,作为输入提交到服务器端,服务器解析后响应,XSS代码随响应内容一起传回浏览器,最后浏览器解析执行XSS代码。这个过程像一次反射,故叫反射XSS。
举例
本例使用nodejs构建项目,演示反射型XSS攻击。
创建目录
首先,我们先创建一个目录xssfilter
并且进入该目录
$ mkdir xssfilter
$ cd xssfilter
在该目录继续创建一个目录xss
,用于创建我们整个的XSS攻击的模拟服务。
$ mkdir xss
$ cd xss
搭建web项目
使用express框架快速搭建整个的应用服务
$ express -e ./
依赖
安装一下所有的依赖
$ npm install
目录结构
看一下目录结构
启动服务
$ npm start
页面
访问localhost:3000
,看是否展示成功
演示
接下来我们就要开始反射型XSS的演示了
js
xss:req.query.xss
我们在此设置一个查询的字段来获取用户在URL中写的search内容
视图层
<div class="">
<%- xss%>
</div>
我们在视图层展示这个内容
接下来重启一下,查看一下带有值后展示的是啥样的
由此,我们发现,值已经展示。
接下来,我们开始一些有攻击性的脚本
xss=<img src="null" onerror="alert(1);"/>
打开页面发现
这是因为Chrome会自动拦截XSS攻击
我们设置下不让浏览器对XSS拦截
res.set('X-XSS-Protection',0);
重启服务,打开浏览我们会看到XSS攻击成功
黑客会将一些攻击脚本在网页上通过引诱式点击植入广告、页面篡改等,典型的比如>
存储型
存储型XSS与反射型XSS的差别仅在于,提交的代码会存储到服务器端(内存,数据库,文件系统等),下次调用的时候就不需要再提交XSS代码。
存储型XSS只能读取缓存或者数据库了
XSS的防范措施
编码
对用户输入的数据进行HTML Entity编码
过滤
将用户输入的不安全的内容给过滤掉
比如:
移除用户上传的DOM属性,如onerror等
移除用户上传的Style节点、Script节点、Iframe节点等
校正
避免直接对HTML Entity解码
使用DOM Parse转换,校正不配对的DOM标签
实战
构造接口
通过构建Node服务和建立一个评论功能,实例演示XSS的攻击与预防。
如何构建Node服务我就不重复赘述了,在之前介绍反射型XSS中已经讲过,这边我依然使用这个服务来实战。
设定个缓存
var comments = {};
编写一个具有编码功能的函数
function html_encode(str){
var result = "";
if(str.length==0){
return "";
}
result = str.replace(/&/g,">");
result = result.replace(/</g,"<");
result = result.replace(/>/g,">");
result = result.replace(/\s/g," ");
result = result.replace(/\'/g,"'");
result = result.replace(/\"/g,""");
result = result.replace(/\n/g,"</br>");
return result;
};
定义一个评论的接口
router.get('/comment', function(req, res, next) {
comments.v = html_encode(req.query.comment);
})
定义一个用户拉取评论的接口
router.get('/getComment', function(req, res, next) {
res.json({
comment:comments.v
})
})
防范措施
ejs
<textarea name="name" rows="8" cols="80" id="text">
<p>sks<img src="null" alt="" onerror="alert(1)" /></p>
</textarea>
<button type="button" name="button" id="btn">评论</button>
<button type="button" name="button" id="get">获取评论</button>
<textarea>
用于用户输入区域
<button>
用于提交评论和拉取评论信息
通过这样,我们就实现了一个模拟的XSS攻击
js
获取对象
var btn = document.getElementById("btn");
var get = document.getElementById("get");
var txt = document.getElementById("text");
添加评论点击事件
btn.addEventListener("click",function(){
......
}
ajax请求
var xhr = new XMLHttpRequest();
url
var url = '/comment?comment='+txt.value;
因为是get请求
打开对象
在客户端向服务端发送之前,首先打开对象,告诉对象是以GET
方式打开
xhr.open('GET',url,true);
定义对象在客户端响应的方式
xhr.onreadystatechange = function(){
if(xhr.readyState==4){
if(xhr.status==200){
console.log(xhr);
}else{
console.log("error");
}
}
}
发送
xhr.send();
添加请求评论点击事件
get.addEventListener("click",function(){
......
}
ajax请求
var xhr = new XMLHttpRequest();
url
var url = '/getComment';
因为是get请求
打开对象
在客户端向服务端发送之前,首先打开对象,告诉对象是以GET
方式打开
xhr.open('GET',url,true);
定义对象在客户端响应的方式
-
导入js
<script src='/public/javascripts/encode.js'></script> <script src='/public/javascripts/domParse.js'></script>
自行去第三方库下载
-
定义一个函数
var prase = function(str){ var results = ''; try{ }catch(e){ //TODO handle the exception }finally{ } }
-
解码
HTMLParse(he.unescape(str,{strict:true}),{});
he
是encode.js
提供的unescape()
对输入一种反转义的过程HTMLParse()
在反转义的基础上进行domParse,获得我们能正常使用的结果 -
配对校验
start:function(tag,attrs,unary){//tag:标签;attrs:将属性组成数组;unary:是否是单标签 results += '<'+tag; for(int i=0,len=attrs.length;i<len;i++){ results += " "+attrs[i].name+'="'+attrs[i].escaped+'"'; } results += (unary?"/";"")+">"; }, end:function(tag){ results += "</"+tag+">"; }, chars:function(text){ results += text; }, comment:function(text){//注释 results += "<!--"+text+"-->" }
查看一下完整代码
<script type="text/javascript"> var prase = function(str){ var results = ''; try{ HTMLParse(he.unescape(str,{strict:true}),{ start:function(tag,attrs,unary){//tag:标签;attrs:将属性组成数组;unary:是否是单标签 results += '<'+tag; for(int i=0,len=attrs.length;i<len;i++){ results += " "+attrs[i].name+'="'+attrs[i].escaped+'"'; } results += (unary?"/";"")+">"; }, end:function(tag){ results += "</"+tag+">"; }, chars:function(text){ results += text; }, comment:function(text){//注释 results += "<!--"+text+"-->" } }); return results; }catch(e){ console.log(e); }finally{ } } </script>
-
定义对象客户端响应方式
xhr.onreadystatechange = function(){ if(xhr.readyState==4){ if(xhr.status==200){ var com = prase(JSON.parse(xhr.response).comment); }else{ console.log("error"); } } }
发送
xhr.send();
过滤、校正
把获取的com转换成DOM节点
var info = document.createElement('span');
info.innerHTML(com);
document.body.appendChild(info);
重启一下,打开浏览器
点击评论,然后再点击获取评论,来模拟浏览器加载服务端评论内容的行为
看下怎么执行的
这边有p
标签、sks
文本、img
标签,在img
标签里,有个src
属性,为null,看下控制台,报错:Failed to load resource
,因此触发了onerror
属性。
引诱式攻击
点击评论,再点击获取评论
点击攻击我
这就是引诱式攻击
到此,我们发现,并没有屏蔽掉XSS攻击,那是因为我们并没有进行过滤
过滤
if(tag=='script'||tag=='style'||tag=='link'||tag=='iframe'||tag=='frame'){
return;
}
这个就是去过滤这些标签
把之前的代码删掉,因为这段代码就包含了那些含有XSS攻击的脚本,从而保证我们获取信息的安全性,避免XSS脚本执行的空间。
for(int i=0,len=attrs.length;i<len;i++){
results += " "+attrs[i].name+'="'+attrs[i].escaped+'"';
}
打开浏览器,重新操作,发现已经成功拦截了XSS攻击,看下控制台,我们发现img
标签下的属性被自动过滤掉了。