SS(跨站脚本攻击),是最普遍的Web应用安全漏洞。这类漏洞使攻击者能够嵌入恶意脚本代码到页面中,当正常用户访问时,就会导致恶意脚本的执行,从而达到攻击用户的目的。那有什么办法能够防止被攻击呢?JX-XSS就是你忠实的守护者。
XSS攻击
JS-XSS,是 leizongmin 在Github 上开源的防御XSS攻击的JS库,仓库地址为 https://github.com/leizongmin/js-xss,目前版本为 1.0.7。JS-XSS 通过对用户输入的内容进行过滤来保护网站免受XSS攻击,支持通过白名单控制允许的HTML标签及各标签的属性,支持通过自定义处理函数对任意标签及其属性进行处理,还提供了一系列的接口以便用户扩展,比其他同类模块更为灵活。JS-XSS 主要用于论坛、博客、网上商店等等一些可允许用户录入页面排版、 格式控制相关的HTML的场景,能有效过滤恶意XSS攻击脚本。
JS-XSS库
JS-XX可以在Node.js中使用,使用 NPM 或 Bower 安装:
npm install xss
bower install xss
也可以在浏览器中使用:
<script src="https://raw.github.com/leizongmin/js-xss/master/dist/xss.js"></script>
在Node.js中使用时,使用函数 xss 进行过滤:
var xss = require("xss");
var html = xss('<script>alert("xss");</script>');
console.log(html);
而在浏览器中,则使用 filterXSS 函数,用法一样:
<script>
var html = filterXSS('<script>alert("xss");</scr' + 'ipt>');
alert(html);
</script>
可以看到,输入是一个HTML的script标签,如果未经处理就在页面上渲染,标签中的脚本就会运行,发出“xss”的提示,这是一个最简单的XSS攻击。而通过JS-XSS的处理后,运行结果如下:
JS-XSS示例运行结果
JS-XSS把script标签的尖括号进行了转义,使得XSS攻击脚本不再执行,阻止了XSS攻击。
我们也可以对过滤规则进行自定义,通过传入options参数实现:
html = filterXSS(input, options);
我们也可以创建一个FilterXSS实例来避免每次都传入参数:
myxss = new xss.FilterXSS(options);
html = myxss.process('<script>alert("xss");</script>');
参数options包括许多配置项:
// 只允许a标签,该标签只允许href, title, target这三个属性
var options = {
whiteList: {
a: ['href', 'title', 'target']
}
};
// 使用以上配置后,下面的HTML
// <a href="#" onclick="hello()"><i>大家好</i></a>
// 将被过滤为
// <a href="#">大家好</a>
// 只保留网页文本
var source = "<strong>hello</strong><script>alert(/xss/);</script>end";
var html = xss(source, {
whiteList: [],
stripIgnoreTag: true,
stripIgnoreTagBody: ["script"]
});
console.log("text: %s", html);
// 输出:
// text: helloend
可以通过提供的自定义函数接口进行XSS过滤行为的扩展和自定义。
XSS攻击
JS-XSS使用简单,在用户输入场景中,可以很方便地进对输入内容进行过滤处理,在前端就处理掉可能的XSS攻击脚本。同时JS-XSS可配置性强,可以通过白名单机制,和丰富的自定义处理函数,使JS-XSS可以在不同场景下进行XSS攻击的防御。
JS-XSS已在实践中广泛应用,示例丰富,且提供了在线测试功能,是一个值得使用的JS网络安全库。
面几篇文章我们讲到了跨站脚本(XSS)漏洞的几种类型和验证方法以及防御措施,有兴趣的朋友可以到我的主页翻看文章《十大常见web漏洞——跨站脚本漏洞》和《实操web漏洞验证——跨站脚本漏洞》,今天我们继续由易到难实战演示一下跨站脚本漏洞的形成,以便更好地了解漏洞的产生原理,进一步做好防御。
上一篇文章我们已经闯过了5关,今天我们继续。
html事件是在满足一定条件的用户行为发生时,所触发的的事件,例如当单击鼠标时的“onclick”以及当浏览器加载图像时的“onload”,我们可以将这些特定的html事件发生时,将JavaScript语句作为属性传递给特定的标签,从而构成一个或多个JavaScript命令或函数。
下图我们从网上搜索了一些html事件属性,有兴趣的可以自己搜索学习。
html事件属性
在这一关中,我们可以构造语句:
111" onmouseover="alert(document.domain);"
前边的“111"”是为了闭合标签,后边的“onmouseover”属性表示当鼠标移动到输入框时执行后边的语句,点击“search”按钮,将鼠标移动到输入框时,页面在弹出内容为当前页面域名的弹窗后,提示“恭喜!”,就可以顺利进入下一关了,如下图所示:
恭喜通关
这一关我们先来探测一下注入点的情况,我们和之前一样先闭合标签,输入“123456"”来闭合标签,找到对应的代码,发现我们输入的内容被另一对引号括住了,如下图所示:
页面代码
这表明我们输入的内容直接被实体化了,那我们不使用引号闭合,直接输入“123456 onmouseover=“alert(document.domain)””,发现只要是等号后边的参数都被引号括了起来,如下图所示:
页面代码
因此我们都不加引号,构造:
123456 onmouseover=alert(document.domain)
注意中间有个空格,点击“search”按钮时,页面在弹出内容为当前页面域名的弹窗后,提示“恭喜!”,就可以顺利进入下一关了,如下图所示:
恭喜通关
有时候我们需要将JavaScript代码添加到客户端中,这时就需要JavaScript伪协议来帮助,它的格式为JavaScript:url,例如:JavaScript:alert("hello word!"),就是一个简单的通过JavaScript伪协议来执行alert("hello word!")语句,它表示在页面显示“hello word!”。
因此我们可以构造语句
JavaScript:alert(document.domain);
点击“Make a Link”按钮时,可见输入框下边出现一个URL超链接,我们点击这个链接,如下图所示:
URL超链接
页面在弹出内容为当前页面域名的弹窗后,提示“恭喜!”,就可以顺利进入下一关了,如下图所示:
恭喜通关
这个比较简单,因为UTF-7绝大多数浏览器都已经不用了,我们很少会遇到,因此我们直接构造语句:
onclick=alert(document.domain);
我们按F12键,根据下图提示找到第三步位置,将以上语句写入到对应位置,再点击第二步的位置,如下图所示:
修改页面代码
页面在弹出内容为当前页面域名的弹窗后,提示“恭喜!”,就可以顺利进入下一关了,如下图所示:
恭喜通关
首先我们还是老办法构造闭合语句,如下所示:
"><script>alert(document.domain);</script>
点击“search”,按F12,找到如下图红框中的位置,发现我们上边构造的语句中“domain”被删除了,如下图所示:
页面代码
既然被删除了,这时我们可以通过双写来绕过domain被删除这种情况,我们可以构造:
"><script>alert(document.dodomainmain);</script>
注意我们在单词domain中间又加了一个单词domain,这时系统在删除一个单词domain后,还会留下一个domain,这样我们就成功执行了语句了。
点击“search”按钮时,页面在弹出内容为当前页面域名的弹窗后,提示“恭喜!”,就可以顺利进入下一关了,如下图所示:
恭喜通关
当然我们也可以通过编码的方法来绕过,我们可以构造:
"><script>eval(atob('YWxlcnQoZG9jdW1lbnQuZG9kb21haW5tYWluKTs='));</script>
其中“eval”是用来执行字符串,其后边的内容会当成JavaScript语句执行,“atob”表示将加密的base64密文,转换成原文,而里边的一串乱码就是通过base64加密过的的“alert(document.domain)”语句,关于加密,有兴趣的可以阅读我的文章《Web渗透测试——密码学基础》,其实和上边的语句一样,这样就可以避免domain被删除了。
以上就是跨站脚本(XSS)漏洞实战演示——由易到难第二部分的全部内容,希望对你了解XSS漏洞有所帮助,欢迎关注@科技兴了解更多科技尤其是网络安全方面的资讯和知识。
天呀,我想当一名黑客,去黑别人的网站!我有两三技能,独乐不如众乐乐,今天我也把这个几个攻击手段教给你,咱们一起搞事情去。
首先我们来了解一下攻击手段,也是比较常见的两种攻击手段了:
CSRF
全称:Cross-site request forgery,跨站请求伪造。原理是:通过伪装成受信任用户的请求来攻击受信任的网站。
如何伪装?如何才算攻击?
生活中其实我们不缺这种例子,比如说我们经常接收到一些来历不明的垃圾短信,短信内容里面有个url链接,有些人手贱点开了链接,然后就发现钱不见了!!
我们从技术角度来复原一下这个过程,首先设定一些基础:
然后用户动作是:点开了垃圾网的链接,但是存钱网里账户的钱不见了。既然是自己账户的钱不见了,所以这里其实有个前提:用户已经登录了存钱网!所以准确来说用户的动作是这样的:点开了垃圾网的链接,但是之前登录过的存钱网里账户的钱不见了!
两个网站毫无关联,为啥会造成这个让人意想不到的后果呢?
其实呀,垃圾网的人为了达到攻击的目的,偷偷在网页上嵌入了存钱网的链接,所以打开垃圾网时候顺便也触发了存钱网的转账的链接,整体逻辑如下:
说到这里,你发现漏洞在哪里没有?大家都知道cookie代表用户身份,每次发起请求,请求头里都会附上用户的cookie信息,既然cookie是存在浏览器的,我偷不到你的cookie,那么我就让你在不知道到的情况下让你自己去操作。
举个例子:假如一家银行转账操作的URL地址如下:
http: //www.cunqianwang.com/zhuanzhang?account=A&for=B&amount=500
那么,一个垃圾网中可以放置如下代码
<img src = "http://www.cunqianwang.com/zhuanzhang?account=A&for=B&amount=500" >
好了,原理和攻击手段我们都懂了,那么我们来说说几种常见的预防手段:
1、检查referer字段
HTTP头中有一个Referer字段,这个字段是用来标明请求来源于哪一个网址。当网站A去访问网站B的资源时候,链接上的请求头上就会有Referer字段。注意是在不同域名下才有。
我随意打开hao123.com的首页,一些图片不是放在hao123.com域名下的,所以会在header中带上Referer字段表示请求源是hao123.com。
那么服务器可以通过判断Referer字段来判断请求的来源。所以在垃圾网站里访问存钱网,Referer的值就是垃圾网的域名,就能判断是不是合法的操作啦。
java代码里获取Referer字段值代码是:
String referer = request.getHeader( "Referer" );
这种方法简单易行,但也有其局限性。http协议无法保证来访的浏览器的具体实现,可以通过篡改Referer字段的方式来进行攻击,所以就要看你用的浏览器高级不高级了,如果你用的浏览刚好是骗子开发的浏览器,嘿嘿~~
2、Token 验证
既然我们要判定用户行为的合法性,那么我就给用户颁发一个合法token,除了带上cookie,还得带上token才行,token在前一个步骤中获取。
逻辑如下:
3、添加图片验证码、短信验证等
重要步骤添加验证码认证后才能操作。脑补,略略略略~
学会攻击
好了,作为一名出色的黑客,必须要知道自己攻击手段的漏洞在哪,怎么防御,绝不做无用功!既然预防手段我知道了,那么接下来就是我展现真正技术的时候了。
嘿嘿,很多公司在一开始的时候为了节约成本,选择用开源项目作为基础,然后再二次开发。虽说开发快,但其实未必安全,一些开源项目如果没有做csrf的预防,那么漏洞就一直存在。
经过我多天的研究,终于发现了某个商城用的是开源项目二次开发的,没有csrf预防。商城的积分可以直接赠送给别人,我立马搞了个网页,嵌入网站赠送积分的链接。
于是有了我和我朋友的对白。
当黑客感觉真好,小明,你是个好人~
XSS
全程:Cross Site Scripting,中文:跨域脚本攻击。原理:不需要你做任何的登录认证,通过合法的操作(比如在url中输入、在评论框中输入),向你的页面注入脚本(可能是js、hmtl代码块等),类似于SQL注入。
通俗点讲就是:恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意用户的特殊目的。
讲再细点其实就是:利用输入内容来闭合对应的html标签,从而执行输入内容的脚本。
攻击形态
xss有两种形态(网友总结):
发出请求时,XSS代码出现在url中,作为输入提交到服务器端,服务器端解析后响应,XSS代码随响应内容一起传回给浏览器,最后浏览器解析执行XSS代码。这个过程像一次反射,所以叫反射型XSS。
存储型XSS和反射型XSS的差别在于,提交的代码会存储在服务器端(数据库、内存、文件系统等),下次请求时目标页面时不用再提交XSS代码。
攻击手段
不管是什么类型,你get到关键点没有?关键点以及技术难点其实在于如何往页面中嵌入恶意的代码。
下面我们来写个例子模拟一下:首先我页面写了个form表单:
<form action = "/submit" method = "post" > 名称: <input name = "name" value = "${name}" > <input type = "submit" > </form>
controller中有个基本跳转,还有form表单的提交:
@GetMapping ( "" ) public String index( HttpServletRequest request) { request.setAttribute( "name" , "公众号:java思维导图" ); return "index" ; } @PostMapping ( "/submit" ) public String submit( HttpServletRequest request) { String name = request.getParameter( "name" ); System . out .println( "name---------->" + name); // 假装只有名字为“求关注”才能通过 if (!name.equals( "求关注" )) { request.setAttribute( "name" , name); } return "index" ; }
初始效果如下:
ok,基本逻辑也写好了,一个简单的表单提交,提交之后如果数据不对,或格式不对就会返回表单页面,同时回显表单数据。
加入我想嵌入脚本如下:
<script> alert( 1 ); </script>
那么我该怎么样才能往这个页面上嵌入代码呢?我打开F12,研究一下
要是这个这个脚本能提到input的外面,value能提前结束就好了。嘿嘿,突然想到,既然我改不了原来的,那么我就创造一个。
于是我改了一下输入的值成:
"><script>alert(1);</script>
这">不就跑到前面了嘛,哈哈哈,天才,我赶紧试试。谷歌浏览器测试结果如下:
脚本的确跑到外面了,但是alert(1)怎么不见了呀,我赶紧调试一下:
不是后端在搞事情,那么真相就只有一个,谷歌浏览器在搞事情,谷歌果然强大,还能辨别我的脚本并和谐掉。
我换个Edge浏览器再试试:
哇,果然Edge你最帅,我想要的你都给我~ F12看下:
没毛病,原声原味的alert(1);
好了上面我们已经弄懂了xss的嵌入脚本的方式,我们输入是合法的,只是内容有点取巧,这就是xss的攻击手段。
除了这个input标签,其实还有很多标签比较常用,比如title、a、img、script等。
上面这个一般都是反射性的xss攻击,我们再来看看一个存储类型的title的例子。
在很多博客中,我们都可以发布文章,我们需要写文章标题,文章内容等,文章标题一般我们还会放在我们的head的title中,用于标签展示当前浏览文章标题。
加入说,我们的页面是这样展示的:
<!DOCTYPE html> <html> <head> <title> ${title} </title> </head> <body> 这是内容 - ${content} </body> </html>
而controller中传过来的内容如下:
@GetMapping ( "/title" ) public String title( HttpServletRequest request) { request.setAttribute( "title" , "</title><script>alert('公众号java思维导图');</script>" ); request.setAttribute( "content" , "内容是关注公众号:java思维导图" ); return "title" ; }
最后我们的得到的页面展示这样子:加载时候先执行弹窗:alert("公众号java思维导图");然后再加载内容。
因为一般我们文章标题内容都是保存到数据库的,所以每次渲染都会执行脚本,所以是个存储型xss攻击。
解决方法
好了,看了我们的例子项目,我们已经意识到了xss攻击的可怕性,一单发布文章都可以写脚本,那么所有的用户打开这篇文章都会被执行脚本,影响可就大了。那么有什么好的解决方法吗?
这里给大家介绍几个解决方法。我们先来看renren-fast项目是怎么解决这个问题的:
#识别攻击脚本、并删掉对应可执行脚本的标签 HTMLFilter #全局过滤器,包装request XssFilter #包装request,重写request的几个重要方法,比如getParameter等 XssHttpServletRequestWrapper
所以renren-fast项目的设计逻辑是加入一个全局过滤器,然后通过包装请求的request,重写request的getParameter、getHeader、getInputStream等方法,在这些方法里面都进行一遍过滤,从而去掉所有的攻击脚本。看看重要代码:
public class XssFilter implements Filter { public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain) throws IOException , ServletException { XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper ( ( HttpServletRequest ) request); chain.doFilter(xssRequest, response); } ... }
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { @Override public String getParameter( String name) { String value = super .getParameter(xssEncode(name)); if ( StringUtils .isNotBlank(value)) { value = xssEncode(value); } return value; } ... }
可以看到上面的xssEncode就是进行过滤脚本的方法;xssEncode方法代码如下:
private String xssEncode( String input) { return htmlFilter.filter(input); }
ok,相信你已经弄明白了。
我们来看看另一个博客项目mblog的解决方法:
#通用控制器 BaseController #自定义编辑器 StringEscapeEditor
mblog项目其实是通过注册所有controller的自定义编辑器,在提交表单时候对所有字段都进行一层get和set,在set的过程中对输入内容进行一番检查,如果有脚本就进行替换等操作。
详细代码如下:
@InitBinder public void initBinder( ServletRequestDataBinder binder) { /** * 防止XSS攻击 */ binder.registerCustomEditor( String . class , new StringEscapeEditor ( true , false )); ... }
@InitBinder用于表单到方法的数据绑定的,这里绑定了一个自定义编辑器StringEscapeEditor。
可以看到setAsText中就是对脚本进行过滤等操作的。
这两种方法都学会了吗?其实逻辑都是对脚本进行过滤替换删除等操作。
学会攻击
好了,又到了黑客show time,某个知名博客平台没防御xss攻击,这时候我发布了一篇文章,title中包含了脚本
*请认真填写需求信息,我们会在24小时内与您取得联系。