什么是XSS(跨站脚本攻击)
XSS又叫CSS (Cross Site Script) ,跨站脚本攻击。它指的是恶意攻击者往Web页面里插入恶意html代码或者javascript代码,当用户浏览该页之时,嵌入其中Web里面的html代码或者javascript代码会被执行,从而达到恶意的特殊目的。
XSS攻击涉及到三方:攻击者,用户,web server。用户是通过浏览器来访问web server上的网页,XSS攻击就是攻击者通过各种办法,在用户访问的网页中插入自己的脚本,让其在用户访问网页时在其浏览器中进行执行。攻击者通过插入的脚本的执行,来获得用户的信息,比如cookie,发送到攻击者自己的网站(跨站了)。
XSS属于被动式的攻击,因为其被动且不好利用,所以许多人常呼略其危害性。
在WEB2.0时代,强调的是互动,使得用户输入信息的机会大增,在这个情况下,我们作为开发者,在开发的时候,要提高警惕。
XSS的攻击手段(或者叫攻击的目的)
盗用Cookie获取到一些敏感的信息:在网页浏览中我们常常涉及到用户登录,登录完毕之后服务端会返回一个cookie值。这个cookie值相当于一个令牌,拿着这张令牌就等同于证明了你是某个用户
如果你的cookie值被窃取,那么攻击者很可能能够直接利用你的这张令牌不用密码就登录你的账户。如果想要通过script脚本获得当前页面的cookie值,通常会用到document.cookie。
破坏正常的页面结构:比如修改页面的样式等,在页面中插入一些其他的信息(比如色情,广告等)
劫持流量实现恶意跳转:就是在网页中想办法插入一句像这样的语句,那么所访问的网站就会被跳转到百度的首页
<script>window.location.href="http://www.baidu.com";</script>
早在2011年新浪就曾爆出过严重的xss漏洞,导致大量用户自动关注某个微博号并自动转发某条微博。
XSS的攻击方式—反射型
发出请求时,xss代码出现在URL中,作为输入提交到服务器端,服务器端解析后响应,xss代码随响应内容一起传回给浏览器,最后浏览器解析执行xss代码,这个过程像一次反射,故叫反射型xss。
也就是攻击相对于访问者而言是一次性的,具体表现在我们把我们的恶意脚本通过url的方式传递给了服务器,而服务器则只是不加处理的把脚本“反射”回访问者的浏览器而使访问者的浏览器执行相应的脚本。也就是说想要触发漏洞,需要访问特定的链接才能够实现。
反射型的攻击特点:XSS攻击代码非持久性,也就是没有保存在web server中,而是出现在URL地址中;非持久性,那么攻击方式就不同了。一般是攻击者通过邮件,聊天软件等等方式发送攻击URL,然后用户点击来达到攻击的;
反射型攻击的演示如下:首先创建一个express的node服务
安装依赖
接下来编写接口,在接口中解析url后面的参数内容
然后再模板中渲染url解析后的内容
PS:-号表示不对html进行转义,如果说这个标签内允许解析HTML的那么就用-号,这样不会对html标签进行转义。=号表示对html进行转义成字符串
接下来,运行项目,在url后面加上xss内容
这是正常的内容,那么我们输入一些具有攻击性的内容,在此之前先去掉浏览器的异常代码检测和拦截
这里浏览器进行检测到异常代码进行了拦截,导致页面不能访问,那么解除浏览器异常代码并拦截,需要在接口代码中加入下面一行代码
res.set('x-xss-Protection',0);
第一种,自动执行脚本攻击,这里使用一个img标签,当src为null,就会执行onerror事件
第二种引导用户点击,从而执行攻击代码
第三种,往页面中嵌入iframe,iframe是一个插入广告的代码
从上面实例中可以看到,当接口解析url的内容并且下发到模板中,这种反射型攻击就会生效,如果当接口不解析这个url内容的话或者去掉xss的内容下发,那么这个攻击就不会生效了,
XSS的攻击方式—存储型
存储型xss和反射型xss的差别仅仅在于,提交的代码会存储在服务器端(数据库,内存,文件系统等),下次请求目标页面时不用再提交xss代码
它与反射型XSS最大的不同就是服务器再接收到我们的恶意脚本时会将其做一些处理。
例如储存到数据库中,然后当我们再次访问相同页面时,将恶意脚本从数据库中取出并返回给浏览器执行。这就意味着只要访问了这个页面的访客,都有可能会执行这段恶意脚本,因此储存型XSS的危害会更大
比如评论区中通常就是储存型XSS。当有人在评论内容中插入恶意脚本时,由于服务器要像每一个访客展示之前的评论内容,所以后面的访客自然会接收到之前留言中的恶意脚本而不幸躺枪。
这个过程一般而言只要用户访问这个界面就行了,不像反射型XSS,需要访问特定的URL。存储型的攻击演示查看:XSS的防范措施实例
XSS的防范措施概述
认证cookie设置HttpOnly
编码:对用户输入的数据进行HTML Entity编码(就像html的字符编码一样)
过滤:移除用户上传的DOM属性,比如onerror,onclick等等,除非业务上有特殊的需求,一般是没有这样的需求的。用户上传的Style节点(会对页面的样式进行篡改),Script节点(引入外部的js文件,或者使用这个标签进行编写js代码进行操作页面的内容),Ifram节点和fram节点(防止用户引入其他页面比如广告等等)等
校正:避免直接对HTML Entity解码。使用DOM Parse转换,校正不配对的DOM标签
XSS的防范措施实例(通过构建Node服务和建立一个评论功能)
在此之前需要准备下面两个插件:domParse.js 和 encode.js 直接在最底下的github地址上下载即可
然后再需要使用的页面中引入这两个插件
<script src="/javascripts/encode.js" charset="utf-8"></script> <script src="/javascripts/domParse.js.js" charset="utf-8"></script>
接下来首先创建express项目来进行演示,创建一个express的node服务
安装依赖
然后在服务端构造两个接口,分别是提交评论的接口和获取评论的接口,并且模拟一个数据存储(实际项目中是将数据存储到数据库中并且数据库中拉取数据)
获取到评论数据后,需要一个编码的过程,编码后保存在缓存中(实际项目中是保存到数据库中)
var express = require('express'); var router = express.Router(); var comments = {};//设置缓存 //编码函数 function html_encode(str){ var s = ''; if(str.length == 0)return ""; s = str.replace(/&/g,">"); s = s.replace(/</g,"<"); s = s.replace(/>/,">"); s = s.replace(/\s/g," "); s = s.replace(/\'/g,"'"); s = s.replace(/\"/g,"""); s = s.replace(/\n/g,"<br>"); return s; } router.get('/', function(req, res, next) { res.render('index', { title: 'Express'}); }); router.get('/comment', function (req, res, next) { comments.v = html_encode(req.query.comment); console.log(comments.v) }); router.get('/getComment', function (req, res, next) { res.json({ comment:comments.v }) }) module.exports = router;
前端上有两个按钮和一个输入文本框
<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> <script src="/javascripts/encode.js" charset="utf-8"></script> <script src="/javascripts/domParse.js.js" charset="utf-8"></script> </head> <body> <textarea name="name" rows="8" cols="80" id="txt"> <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> </body> </html>
首先获取需要操作的dom
//获取元素 var btn = document.getElementById('btn'); var get = document.getElementById('get'); var txt = document.getElementById('text');
然后使用ajax与后端进行通信,提交评论
//监听评论按钮事件 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){ if(xhr.status == 200){ console.log(xhr); }else{ console.log('error'); } } } xhr.send(); });
在前端中获取后台数据需要明确两个点:解码和校验配对(这是必须的两个步骤)这个解码和校验配对的过程比较复杂,所以需要单独创建一个函数进行调用,并且在这个函数中需要将一些异常的标签进行过滤和过滤掉标签的属性
<script type="text/javascript"> var parse = function(str){ var results = ''; try{ HTMLParser(he.unescape(str,{strict:true}),{ start:function(tag,attrs,unary){ //过滤掉一些有威胁的标签 if(tag=='script'||tag=='style'||tag=='link'||tag=='iframe'||tag=='frame'){ return; } results += '<'+tag; //注释掉即可过滤标签的属性 // for(var 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>
然后在获取评论的代码中进行调用这个parser方法
//监听获取评论按钮事件 get.addEventListener('click',function(){ console.log('2'); var xhr = new XMLHttpRequest(); var url = '/getComment'; xhr.open('GET',url,true); xhr.onreadystatechange=function(){ if(xhr.readyState == 4){ if(xhr.status == 200){ var com = parse(JSON.parse(xhr.response).comment); var txt = document.createElement('span'); txt.innerHTML = com; document.body.appendChild(txt); }else{ console.log('error'); } } } xhr.send(); });
总结
在开发的时候,每当有用户输入的内容时,都要加倍小心。请记住两条原则:过滤输入和转义输出。前端xss防御整体代码实例如下
<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> <script src="/javascripts/encode.js" type="text/javascript"></script> <script src="/javascripts/domParse.js" type="text/javascript"></script> <script type="text/javascript"> var parse = function(str){ var results = ''; try{ HTMLParser(he.unescape(str,{strict:true}),{ start:function(tag,attrs,unary){ //过滤掉一些有威胁的标签 if(tag=='script'||tag=='style'||tag=='link'||tag=='iframe'||tag=='frame'){ return; } results += '<'+tag; //注释掉即可过滤标签的属性 // for(var 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> </head> <body> <h1><%= title %></h1> <p>Welcome to <%= title %></p> <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> <script type="text/javascript"> //获取元素 var btn = document.getElementById('btn'); var get = document.getElementById('get'); var txt = document.getElementById('text'); //监听评论按钮事件 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){ if(xhr.status == 200){ console.log(xhr); }else{ console.log('error'); } } } xhr.send(); }); //监听获取评论按钮事件 get.addEventListener('click',function(){ console.log('2'); var xhr = new XMLHttpRequest(); var url = '/getComment'; xhr.open('GET',url,true); xhr.onreadystatechange=function(){ if(xhr.readyState == 4){ if(xhr.status == 200){ var com = parse(JSON.parse(xhr.response).comment); var txt = document.createElement('span'); txt.innerHTML = com; document.body.appendChild(txt); }else{ console.log('error'); } } } xhr.send(); }); </script> </body> </html>
github地址为:https://github.com/SmileHong0121/xss
Ifram
来源:https://www.cnblogs.com/LO-ME/p/7291999.html