Web安全:细说XSS攻击与防范

昨夜翻到了曾经所写网站的【留言板】,思来想去觉得有必要优化一下,而首要问题就是所谓【XSS攻击】的防范。

听老师说,这非常重要,甚至可以轻而易举的获取此网站上的所有信息,和权限。今早试了一下,果然如此。笔者觉得有必要以此为例写篇文章来总结和警示一下…


XSS的“前世今生”

XSS原理: XSS攻击是Web攻击中最常见的攻击方法之一,它是通过对网页注入可执行代码且成功地被浏览器 执行,达到攻击的目的,形成了一次有效XSS攻击,一旦攻击成功,它可以获取用户的联系人列表,然后向联系人发送虚假诈骗信息,可以删除用户的日志等等,有时候还和其他攻击方式同时实 施比如SQL注入攻击服务器和数据库、Click劫持、相对链接劫持等实施钓鱼,它带来的危害是巨 大的,是web安全的头号大敌。
攻击条件:

  1. 需要向web页面注入恶意代码;
  2. 这些恶意代码能够被浏览器成功的执行

XSS攻击与防御手段

  • 反射型
  • 存储型

反射型XSS攻击: XSS代码在URL中随输入提交(请求)到服务器端,服务器端解析后响应。XSS代码随响应内容一起回到浏览器,被执行。
这是一个明文攻击,或者,常表现为“诱导型攻击”。
存储型XSS攻击: 他和反射型攻击唯一的区别在于代码存储地方。存储型XSS,其提交代码会被存储在服务端(数据库、内存。文件系统…)

# XSS防御 #

  1. 编码 ——对用户输入的数据进行HTML Entity编码:'' - &quot;、& - &amp;、< - &lt;、> - &gt;、不断开空格 - &nbsp; (前面的是HTML内容,后面是编码成什么样子)
  2. 过滤 —— 1、移除用户上传的DOM属性,如:onerror; 2、移除用户上传的style节点、script节点、Iframe节点、frame节点、link节点… ——比如这样:if(tag==’ … ’ || …) return;
  3. 校正 —— 避免直接对HTML Entity编码,使用DOM Parse转换,校正不配对的DOM标签

图样

xss1

xss2


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,"&gt");
	s=str,replace(/</g,"&lt;");
	s=str,replace(/>/g,"&gt;");
	s=str,replace(/\s/g,"&nbsp;");
	s=str,replace(/\'/g,"&#39;");
	s=str,replace(/\"/g,"&quot;");
	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】部分是从后端拿到数据的展示过程,但在此之前,有两个步骤:

  1. 解码
  2. 配对校验

笔者在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);
发布了193 篇原创文章 · 获赞 464 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/qq_43624878/article/details/105021832