昨夜翻到了曾经所写网站的【留言板】,思来想去觉得有必要优化一下,而首要问题就是所谓【XSS攻击】的防范。
听老师说,这非常重要,甚至可以轻而易举的获取此网站上的所有信息,和权限。今早试了一下,果然如此。笔者觉得有必要以此为例写篇文章来总结和警示一下…
XSS的“前世今生”
XSS原理: XSS攻击是Web攻击中最常见的攻击方法之一,它是通过对网页注入可执行代码且成功地被浏览器 执行,达到攻击的目的,形成了一次有效XSS攻击,一旦攻击成功,它可以获取用户的联系人列表,然后向联系人发送虚假诈骗信息,可以删除用户的日志等等,有时候还和其他攻击方式同时实 施比如SQL注入攻击服务器和数据库、Click劫持、相对链接劫持等实施钓鱼,它带来的危害是巨 大的,是web安全的头号大敌。
攻击条件:
- 需要向web页面注入恶意代码;
- 这些恶意代码能够被浏览器成功的执行
XSS攻击与防御手段
- 反射型
- 存储型
反射型XSS攻击: XSS代码在URL中随输入提交(请求)到服务器端,服务器端解析后响应。XSS代码随响应内容一起回到浏览器,被执行。
这是一个明文攻击,或者,常表现为“诱导型攻击”。
存储型XSS攻击: 他和反射型攻击唯一的区别在于代码存储地方。存储型XSS,其提交代码会被存储在服务端(数据库、内存。文件系统…)
# XSS防御 #
- 编码 ——对用户输入的数据进行HTML Entity编码:
'' - "、& - &、< - <、> - >、不断开空格 -
(前面的是HTML内容,后面是编码成什么样子) - 过滤 —— 1、移除用户上传的DOM属性,如:
onerror
; 2、移除用户上传的style节点、script
节点、Iframe
节点、frame
节点、link
节点… ——比如这样:if(tag==’ … ’ || …) return;
- 校正 —— 避免直接对HTML Entity编码,使用DOM Parse转换,校正不配对的DOM标签
图样
XSS实战
首先,创建一个目录,并进入、运行(node.js):
mkdir mxcyun
cd mxcyun/
npm install
cd ../
open mxcyun -a HBuilder #用HBuilder打开此目录(mxcyun)
启动服务命令:
cd mxcyun/
npm start
(启动服务后即可在相应网址查看效果!后面所用到的也是这个命令)
此次服务器端所用node.js ,中的express插件(模块)(主要是其中的Router中间件)!客户端所用为domParse.js插件。
npm install express -g
npm install domParse.js -g
//node.js代码
var express=require('express');
var router=express.Router();
router.get('/',function(req,res,next){
res.render('index',{title:'Express'});
});
module.exports=router;
(在其中)先构造两个接口 —— 接收输入和返回文字:
//node.js代码-接收接口部分
var comments={};
router.get('/comment',function(req,res,next){
comments.v=req.query.comment;
})
这个接口的作用即为【保存输入内容】,但是这就够了么?
我们前面才说过编码的问题:
//node.js代码-“编码”函数部分
function html_encode(str){
var s='';
if(s.length==0) return ""
s=str,replace(/&/g,">");
s=str,replace(/</g,"<");
s=str,replace(/>/g,">");
s=str,replace(/\s/g," ");
s=str,replace(/\'/g,"'");
s=str,replace(/\"/g,""");
s=str,replace(/\n/g,"<br>");
return s;
};
所以接收部分代码应改为:
//node.js代码-接收接口部分
var comments={};
router.get('/comment',function(req,res,next){
comments.v=html_encode(req.query.comment);
});
那么,
//node.js-用户拉取(获取)评论接口部分
router.get('/getComment',function(req,res,next){
res.json({
comment:comments.v
})
})
让我们把目光聚焦到前端部分:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="/style/style.css" />
<script src="/javascript/encode.js"></script><!-- 这是上面的服务端node.js文件名(引用) -->
<script src="/javascript/domParse.js"></script>
</head>
<body>
<textarea name="name" rows="8" cols="80" id="txt">
<p>sks <img src="null" onerror="alert(1)"></p>
</textarea>
<button type="button" name="button" id="btn">评论</button>
<button type="button" name="button" id="get">获取评论</button>
</body>
</html>
如上,前端部分所用为node.js中的ejs模板,如果是普通HTML文件,则需在服务器node文件中加入path模块定位前端资源:
var path=require('path');
var app=express();
app.use(express.static(path.join(__dirname+'/public')))
app.get('/',function(req,res){
res.sendFile(path.join(__dirname+'/public/index.html'));
})
或直接用:
router.get('/',function(req,res,next){
res.sendFile(path.join(__dirname+'/public/index.html'));
})
下面来写整个的交互部分:
<script>
btn.addEventListener('click',function(){
var xhr=new XMLHttpRequest();
var url='/comment?comment='+txt.value;
xhr.open('GET',url,true);
xhr.onreadystatechange=function(){
if(xhr.readyState==4 && xhr.status==200){
console.log(xhr);
}else{
console.log('error');
}
}
xhr.send();
});
get.addEventListener('click',function(){
var xhr=new XMLHttpRequest();
var url='/getComment';
xhr.open('GET',url,true);
xhr.onreadystatechange=function(){
if(xhr.readyState==4 && xhr.status==200){
//1
}else{
console.log('error');
}
}
xhr.send();
});
</script>
代码中【注释1】部分是从后端拿到数据的展示过程,但在此之前,有两个步骤:
- 解码
- 配对校验
笔者在ejs文件head
中又写了一个script标签 —— 其中放置的是解码和配对的函数:
<script>
var parse=function(str){
var results='';
//为防止错误,将过程放在try-catch中进行
try{
HTMLParse(he.unescape(str,{strict:true}),{
//HTMLParse提供了几个内置选项
//标签的开始部分(标签,属性,是不是单标签)
start:function(tag,attrs,unary){
//在start中过过滤掉不安全的标签元素
if(tag=='script' || tag=='img' || tag='style' || tag=='link' || tag=='iframe' || tag=='frame') return;
results+='<'+tag;
//for(var i in attrs){
// 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>
HTMLParse函数时domParese第三方插件的内置函数,就是为解决反转义问题,其中unescape的第一个参数就是文本/html片段,第二个参数是“使用严格模式”,而he是HTMLParse这个函数的一个(负责此块功能的)内置对象。
然后我们将回到上一个代码【注释1】部分:
var com=parse(JSON.parse(xhr.response).comment);
var txt=document.createElement('span');
txt.innerHTML=com; //这里为什么用HTML?因为com已经是转移之后的内容了
document.body.appendChild(txt);