Automne's Shadow.

XMAN CTF Crypto WriteUp

2019/07/14 Share

Crypto

主要考察hash长度扩展攻击和CBC字节翻转的结合。

服务端代码如下

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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#coding=utf-8

import random
import sys
import string
from hashlib import sha256
import socketserver
from Crypto.Cipher import AES
import os
import hashlib
import time

secret = os.urandom(16)
admin_key = os.urandom(16).hex()
aes_key = os.urandom(16)
iv = os.urandom(AES.block_size)

wel = b'Welcome, somebody\nyou can:\n1. login\n2. registe\n'


class process(socketserver.BaseRequestHandler):
def justWaite(self):
time.sleep(3)

def _pad(self, s):
s = bytes(s, 'utf-8')
return s + (16 - len(s) % 16) * bytes((16 - len(s) % 16, ))

def _unpad(self, s):
return s[0:-s[-1]].decode('utf-8', 'ignore')

def mac(self, msg):
hmac = sha256(secret + msg).digest()
return (msg + hmac).hex()

def verifyMac(self, mac):
mac = bytes.fromhex(mac)
hmac = mac[-32:]
msg = mac[:-32]
if hmac == sha256(secret + msg).digest():
flag = True
else:
flag = False
return msg, flag

def encrypt(self, plain):
plain = self._pad(plain)
cipher = AES.new(aes_key, AES.MODE_CBC, iv)
ciphertext = iv + cipher.encrypt(plain)
return ciphertext

def decrypt(self, ciphertext):
cipher = AES.new(aes_key, AES.MODE_CBC, iv)
plain = cipher.decrypt(ciphertext[AES.block_size:])
return self._unpad(plain)

def adminLogin(self, username):
self.request.send(b'1. pic1\n2. pic2\n3. flag\n')
c = self.request.recv(2).decode('utf-8')[0]
if c == '1':
self.request.send(bytes('your gun:\n▄︻┻═┳一\n', 'UTF-8'))
elif c == '2':
self.request.send(bytes('your Tu Long Knife:\n━╋▇▇▇◤\n', 'UTF-8'))
elif c == '3':
with open('flag') as f:
flag = f.readline()
self.request.send(flag.encode('utf-8') + b'\n')
else:
self.request.send(b'wrong num')

def userLogin(self, username):
self.request.send(b'1. pic1\n2. pic2\n')
c = self.request.recv(2).decode('utf-8')[0]
if c == '1':
self.request.send(bytes('your gun:\n▄︻┻═┳一', 'UTF-8'))
elif c == '2':
self.request.send(bytes('your Tu Long Knife:\n━╋▇▇▇◤', 'UTF-8'))
else:
self.request.send(b'wrong num')

def handle(self):
# self.justWaite()

while True:
self.isAdmin = False
self.request.send(wel)
wel_choice = self.request.recv(2).decode('utf-8')[0]
if wel_choice == '1':
self.request.send(b'input your cookie:>>')
cookie = self.request.recv(513).decode('utf-8')[:-1]
msg, flag = self.verifyMac(cookie)
msg = self.decrypt(msg)
print(msg)
print(len(msg))
try:
for i in range(len(msg) - 3):
if msg[i:i + 3] == 'un=':
username = msg[i + 3:]
username = username.split(';')[0]
print(username)

for i in range(len(msg) - 3):
if msg[i:i + 3] == 'pw=':
passwd = msg[i + 3:]
passwd = passwd.split(';')[0]
print(passwd)

for i in range(len(msg) - 8):
if msg[i:i + 8] == 'isAdmin=':
if msg[i + 8:i + 13] == 'True;':
self.isAdmin = True
except Exception as e:
print('error detected\n')
print(e)

if flag == False:
self.request.send(b'dear %s, your cookie is broken\n' % (
username.encode('utf-8')))
break
self.request.send(b'input your password:>>')
pw_t = self.request.recv(37).decode('utf-8')[:-1]
if pw_t != passwd:
self.request.send(b'wrong passwd!')
break
self.request.send(b'welcome! %s\n' %
(username.encode('utf-8')))
if self.isAdmin:
self.adminLogin(username)
else:
self.userLogin(username)

elif wel_choice == '2':
self.request.send(b'welcome! are you an admin?(Y/N)\n')
reg_c = self.request.recv(2).decode('utf-8')[0]
if reg_c == 'Y':
self.request.send(b'input admin key:>>')
u_key = self.request.recv(33).decode('utf-8')[:-1]
if u_key != admin_key:
break
else:
self.isAdmin = True
self.request.send(b'input your name:>>\n')
username = self.request.recv(37).decode('utf-8')[:-1]
if 'admin' in username.lower():
self.request.send(b'no admin in username\n')
continue
self.request.send(b'input your pw:>>\n')
passwd = self.request.recv(37).decode('utf-8')[:-1]
if 'admin' in passwd.lower():
self.request.send(b'no admin in password\n')
continue
cookie = self.mac(self.encrypt(
'isAdmin=' + str(self.isAdmin) + ';pw=' + passwd + ';un=' + username))
self.request.send(b'your cookie:>>\n' +
cookie.encode('utf-8') + b'\n')
else:
break

self.request.close()


class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass


if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 10086
server = ThreadedServer((HOST, PORT), process)
server.allow_reuse_address = True
server.serve_forever()

发回来的cookie是经过AES-CBC-128加密后再经过hmac处理后的内容

1
2
cookie = self.mac(self.encrypt(
'isAdmin=' + str(self.isAdmin) + ';pw=' + passwd + ';un=' + username))

默认的self.isAdmin是False

消息认证码相关的函数,使用了HMAC(基于Hash函数的MAC),这里用了sha256是可以被哈希长度扩展攻击的。

1
2
3
4
5
6
7
8
9
10
11
12
13
def mac(self, msg):
hmac = sha256(secret + msg).digest()
return (msg + hmac).hex()

def verifyMac(self, mac):
mac = bytes.fromhex(mac)
hmac = mac[-32:]
msg = mac[:-32]
if hmac == sha256(secret + msg).digest():
flag = True
else:
flag = False
return msg, 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
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
##python3

from pwn import remote
from Crypto.Util.number import bytes_to_long, long_to_bytes
from os import urandom
from hashpumpy import hashpump

host = '127.0.0.1'
port = 10086


def xor(var, key):
return bytes(a ^ b for a, b in zip(var, key))


bs = 16


def pad(s):
return s + (bs - len(s) % bs) * bytes((bs - len(s) % bs, ))

l = remote(host, port)

l.recv()
l.send('2\n')
l.recv()
l.send('N\n')
l.recv()
username1 = '999999999'
l.send(username1 + '\n')
l.recv()
passwd = 'pw'
l.send(passwd + '\n')
l.recvline()
cookie1 = l.recvline()[:-1].decode('utf-8')
print("cookie1: %s" % cookie1)
hash_old = cookie1[-64:]
print("hash_old: %s" % hash_old)
original_data = bytes.fromhex(cookie1[:-64])
print("cipher length: %s" % len(cookie1[:-64]))
print("original_data: %s" % original_data)
# 'isAdmin=False;pw =pw;un=999999999'


l.recv()
l.send('2\n')
l.recv()
l.send('N\n')
l.recv()
#username2 = '9999999990000000000000000'
username2 = '9999999991111111111111111'
l.send(username2 + '\n')
l.recv()
passwd = 'pw'
l.send(passwd + '\n')
l.recvline()
cookie2 = l.recvline()[:-1].decode('utf-8')
print("cookie2: %s" % cookie2)
cipher_old = cookie2[:-64]
print("cipher_old length: %s" % len(cookie2[:-64]))
print("cipher_old: %s" % bytes.fromhex(cipher_old))
oldblock = bytes.fromhex(cipher_old[-64:-32])
print("oldblock: %s" % oldblock)
# 'isAdmin=False;pw =pw;un=999999999 0000000000000000'
print(xor(oldblock, 16*b'\x10'))

newblock = xor(xor(oldblock, 16*b'\x10'), b'isAdmin=True;'+3*b'\x03')
print("newblock: %s" % newblock)
print("seeit: %s" % bytes.fromhex(cipher_old[-32:]))

data_to_add = newblock + bytes.fromhex(cipher_old[-32:])
key_length = 16
h, c = hashpump(hash_old, original_data, data_to_add, key_length)

new_cookie = c.hex() + h
print(new_cookie)
l.interactive()
l.recv()
l.send('1\n')
l.recv()
l.send(new_cookie + '\n')
l.recv()
l.send('pw\n')

l.interactive()

注意上面的newblock是倒数第二组,所以还需要添加最后一组作为data_to_add

如果运行有问题,请参考上篇博文的处理方式安装适配的pwntools

运行效果如下

automne

CATALOG