整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JS-XSS - 抵御XSS攻击的守护者

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包括许多配置项:

  • whiteList:自定义白名单,不在白名单上的标签和属性都会被过滤处理。
// 只允许a标签,该标签只允许href, title, target这三个属性
var options = {
  whiteList: {
    a: ['href', 'title', 'target']
  }
};
// 使用以上配置后,下面的HTML
// <a href="#" onclick="hello()"><i>大家好</i></a>
// 将被过滤为
// <a href="#">大家好</a>
  • stripIgnoreTag:是否去掉不在白名单上的标签。默认为否,采取转义的方式处理。
  • stripIgnoreTagBody:是否去掉不在白名单上的标签及标签体;
// 只保留网页文本
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
  • allowCommentTag:是否允许HTML备注,默认为否;
  • onTag:自定义匹配到标签时的处理方法;
  • onTagAttr:自定义匹配到标签的属性时的处理方法;
  • onIgnoreTag:自定义匹配到不在白名单上的标签时的处理方法;
  • onIgnoreTagAttr:自定义匹配到不在白名单上的属性时的处理方法;
  • escapeHtml:自定义HTML转义函数;
  • safeAttrValue:自定义标签属性值的转义函数;

可以通过提供的自定义函数接口进行XSS过滤行为的扩展和自定义。

XSS攻击

总结

JS-XSS使用简单,在用户输入场景中,可以很方便地进对输入内容进行过滤处理,在前端就处理掉可能的XSS攻击脚本。同时JS-XSS可配置性强,可以通过白名单机制,和丰富的自定义处理函数,使JS-XSS可以在不同场景下进行XSS攻击的防御。

JS-XSS已在实践中广泛应用,示例丰富,且提供了在线测试功能,是一个值得使用的JS网络安全库。

面几篇文章我们讲到了跨站脚本(XSS)漏洞的几种类型和验证方法以及防御措施,有兴趣的朋友可以到我的主页翻看文章《十大常见web漏洞——跨站脚本漏洞》和《实操web漏洞验证——跨站脚本漏洞》,今天我们继续由易到难实战演示一下跨站脚本漏洞的形成,以便更好地了解漏洞的产生原理,进一步做好防御。

上一篇文章我们已经闯过了5关,今天我们继续。

html事件中的xss

html事件是在满足一定条件的用户行为发生时,所触发的的事件,例如当单击鼠标时的“onclick”以及当浏览器加载图像时的“onload”,我们可以将这些特定的html事件发生时,将JavaScript语句作为属性传递给特定的标签,从而构成一个或多个JavaScript命令或函数。

下图我们从网上搜索了一些html事件属性,有兴趣的可以自己搜索学习。

html事件属性

在这一关中,我们可以构造语句:

111" onmouseover="alert(document.domain);"

前边的“111"”是为了闭合标签,后边的“onmouseover”属性表示当鼠标移动到输入框时执行后边的语句,点击“search”按钮,将鼠标移动到输入框时,页面在弹出内容为当前页面域名的弹窗后,提示“恭喜!”,就可以顺利进入下一关了,如下图所示:

恭喜通关

空格分隔属性中的xss

这一关我们先来探测一下注入点的情况,我们和之前一样先闭合标签,输入“123456"”来闭合标签,找到对应的代码,发现我们输入的内容被另一对引号括住了,如下图所示:

页面代码

这表明我们输入的内容直接被实体化了,那我们不使用引号闭合,直接输入“123456 onmouseover=“alert(document.domain)””,发现只要是等号后边的参数都被引号括了起来,如下图所示:

页面代码

因此我们都不加引号,构造:

123456 onmouseover=alert(document.domain)

注意中间有个空格,点击“search”按钮时,页面在弹出内容为当前页面域名的弹窗后,提示“恭喜!”,就可以顺利进入下一关了,如下图所示:

恭喜通关

JavaScript伪协议触发XSS

有时候我们需要将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中的xss

这个比较简单,因为UTF-7绝大多数浏览器都已经不用了,我们很少会遇到,因此我们直接构造语句:

onclick=alert(document.domain);

我们按F12键,根据下图提示找到第三步位置,将以上语句写入到对应位置,再点击第二步的位置,如下图所示:

修改页面代码

页面在弹出内容为当前页面域名的弹窗后,提示“恭喜!”,就可以顺利进入下一关了,如下图所示:

恭喜通关

绕过过滤domain为空的xss

首先我们还是老办法构造闭合语句,如下所示:

"><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
  • XSS

CSRF

全称:Cross-site request forgery,跨站请求伪造。原理是:通过伪装成受信任用户的请求来攻击受信任的网站。

如何伪装?如何才算攻击?

生活中其实我们不缺这种例子,比如说我们经常接收到一些来历不明的垃圾短信,短信内容里面有个url链接,有些人手贱点开了链接,然后就发现钱不见了!!

我们从技术角度来复原一下这个过程,首先设定一些基础:

  • 垃圾短信里的链接(垃圾网):http://www.lajiwang.com/pianqian
  • 存了钱的网站(存钱网):http://www.cunqianwang.com/

然后用户动作是:点开了垃圾网的链接,但是存钱网里账户的钱不见了。既然是自己账户的钱不见了,所以这里其实有个前提:用户已经登录了存钱网!所以准确来说用户的动作是这样的:点开了垃圾网的链接,但是之前登录过的存钱网里账户的钱不见了!

两个网站毫无关联,为啥会造成这个让人意想不到的后果呢?

其实呀,垃圾网的人为了达到攻击的目的,偷偷在网页上嵌入了存钱网的链接,所以打开垃圾网时候顺便也触发了存钱网的转账的链接,整体逻辑如下:

  • 1、用户登录成功存钱网,于是浏览器中产生了网站cookie
  • 2、用户在没有退出存钱网的情况下,访问了垃圾网
  • 3、垃圾网要求访问存钱网的转账url,转账url带上存钱网的cookie去访问服务器
  • 4、存钱网服务器验证转账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在前一个步骤中获取。

逻辑如下:

  • 服务器发送给客户端一个token;
  • 客户端提交的表单中带着这个token。
  • 如果这个 token 不合法,那么服务器拒绝这个请求。

3、添加图片验证码、短信验证等

重要步骤添加验证码认证后才能操作。脑补,略略略略~

学会攻击

好了,作为一名出色的黑客,必须要知道自己攻击手段的漏洞在哪,怎么防御,绝不做无用功!既然预防手段我知道了,那么接下来就是我展现真正技术的时候了。

嘿嘿,很多公司在一开始的时候为了节约成本,选择用开源项目作为基础,然后再二次开发。虽说开发快,但其实未必安全,一些开源项目如果没有做csrf的预防,那么漏洞就一直存在。

经过我多天的研究,终于发现了某个商城用的是开源项目二次开发的,没有csrf预防。商城的积分可以直接赠送给别人,我立马搞了个网页,嵌入网站赠送积分的链接。

于是有了我和我朋友的对白。

  • 我:小明呀,你的A商城还有多少积分呀?
  • 小明:2000多吧?
  • 我:这么多?我不信!你登录让我看看!
  • 小明去登录A网站给我看积分,果然2000多。
  • 我:小明呀,我开发了个网站,我发给你看看能不能打开
  • 小明打开网站,小明的积分到我账户了~

当黑客感觉真好,小明,你是个好人~

XSS

全程:Cross Site Scripting,中文:跨域脚本攻击。原理:不需要你做任何的登录认证,通过合法的操作(比如在url中输入、在评论框中输入),向你的页面注入脚本(可能是js、hmtl代码块等),类似于SQL注入。

通俗点讲就是:恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意用户的特殊目的。

讲再细点其实就是:利用输入内容来闭合对应的html标签,从而执行输入内容的脚本。

攻击形态

xss有两种形态(网友总结):

  • 1、反射型


发出请求时,XSS代码出现在url中,作为输入提交到服务器端,服务器端解析后响应,XSS代码随响应内容一起传回给浏览器,最后浏览器解析执行XSS代码。这个过程像一次反射,所以叫反射型XSS。

  • 2、存储型


存储型XSS和反射型XSS的差别在于,提交的代码会存储在服务器端(数据库、内存、文件系统等),下次请求时目标页面时不用再提交XSS代码。

攻击手段

不管是什么类型,你get到关键点没有?关键点以及技术难点其实在于如何往页面中嵌入恶意的代码。

下面我们来写个例子模拟一下:首先我页面写了个form表单:

  • index.ftl
<form
 
action
=
"/submit"
 
method
=
"post"
>
 名称:
<input
 
name
=
"name"
 
value
=
"${name}"
>
 
<input
 
type
=
"submit"
>
</form>

controller中有个基本跳转,还有form表单的提交:

  • com.example.IndexController#index
@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中,用于标签展示当前浏览文章标题。

加入说,我们的页面是这样展示的:

  • title.ftl
<!DOCTYPE html>
<html>
<head>
 
<title>
${title}
</title>
</head>
<body>
 这是内容 - ${content}
</body>
</html>

而controller中传过来的内容如下:

  • IndexController
@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项目是怎么解决这个问题的:

  • renren-fast
#识别攻击脚本、并删掉对应可执行脚本的标签
HTMLFilter
#全局过滤器,包装request
XssFilter
#包装request,重写request的几个重要方法,比如getParameter等
XssHttpServletRequestWrapper

所以renren-fast项目的设计逻辑是加入一个全局过滤器,然后通过包装请求的request,重写request的getParameter、getHeader、getInputStream等方法,在这些方法里面都进行一遍过滤,从而去掉所有的攻击脚本。看看重要代码:

  • io.renren.common.xss.XssFilter
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);
 }
 ...
}
  • io.renren.common.xss.XssHttpServletRequestWrapper
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的解决方法:

  • mblog
#通用控制器
BaseController
#自定义编辑器
StringEscapeEditor

mblog项目其实是通过注册所有controller的自定义编辑器,在提交表单时候对所有字段都进行一层get和set,在set的过程中对输入内容进行一番检查,如果有脚本就进行替换等操作。

详细代码如下:

  • BaseController
@InitBinder
public
 
void
 initBinder(
ServletRequestDataBinder
 binder) {
 
/**
 * 防止XSS攻击
 */
 binder.registerCustomEditor(
String
.
class
, 
new
 
StringEscapeEditor
(
true
, 
false
));
 ...
}

@InitBinder用于表单到方法的数据绑定的,这里绑定了一个自定义编辑器StringEscapeEditor。

  • StringEscapeEditor


可以看到setAsText中就是对脚本进行过滤等操作的。

这两种方法都学会了吗?其实逻辑都是对脚本进行过滤替换删除等操作。

学会攻击

好了,又到了黑客show time,某个知名博客平台没防御xss攻击,这时候我发布了一篇文章,title中包含了脚本