CTF | wargame

0ctf 2017 / integrity (AES_CBC IV취약점)

nopdata 2017. 3. 21. 09:08

Just a simple scheme.
nc 202.120.7.217 8221
integrity_f2ed28d6534491b42c922e7d21f59495.zip

소스코드 하나가 주어진다.
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
#!/usr/bin/python -u
 
from Crypto.Cipher import AES
from hashlib import md5
from Crypto import Random
from signal import alarm
 
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 
unpad = lambda s : s[0:-ord(s[-1])]
 
 
class Scheme:
    def __init__(self,key):
        self.key = key
 
    def encrypt(self,raw):
        raw = pad(raw)
        raw = md5(raw).digest() + raw
 
        iv = Random.new().read(BS)
        cipher = AES.new(self.key,AES.MODE_CBC,iv)
 
        return ( iv + cipher.encrypt(raw) ).encode("hex")
 
    def decrypt(self,enc):
        enc = enc.decode("hex")
 
        iv = enc[:BS]
        enc = enc[BS:]
 
        cipher = AES.new(self.key,AES.MODE_CBC,iv)
        blob = cipher.decrypt(enc)
 
        checksum = blob[:BS]
        data = blob[BS:]
 
        if md5(data).digest() == checksum:
            return unpad(data)
        else:
            return
 
key = Random.new().read(BS)
scheme = Scheme(key)
 
flag = open("flag",'r').readline()
alarm(30)
 
print "Welcome to 0CTF encryption service!"
while True:
    print "Please [r]egister or [l]ogin"
    cmd = raw_input()
 
    if not cmd:
        break
 
    if cmd[0]=='r' :
        name = raw_input().strip()
 
        if(len(name) > 32):
            print "username too long!"
            break
        if pad(name) == pad("admin"):
            print "You cannot use this name!"
            break
        else:
            print "Here is your secret:"
            print scheme.encrypt(name)
 
 
    elif cmd[0]=='l':
        data = raw_input().strip()
        name = scheme.decrypt(data)
 
        if name == "admin":
            print "Welcome admin!"
            print flag
        else:
            print "Welcome %s!" % name
    else:
        print "Unknown cmd!"
        break
 
 
 
cs

CBC 구조를 알면 보다 쉽게 풀 수 있는 문제였다.


문제 코드에 따라 그림을 그려보면 다음과 같다.



Hello를 넣었을 때 결과로 돌아오는 값이다. 문제는 hello가 아닌 admin을 보내야 하며, IV값은 매번 바뀌게 된다는 것이다.
이건 Ciphertext_1을 내가만든 admin의 IV값으로 생각하면 된다. 풀이 흐름을 그려보면 다음과 같다.

admin이 입력이 되지 않기 때문에 블록을 하나 늘려, 더미값을 넣어주면 된다. 여기서는 md5값이 무조건 앞에 추가되어야 하므로 md5값을 넣어주도록 한다.
그러면 결과로 IV값과 3개의 암호화된 블록 값이 돌아오는데 IV값은 버리고 1번째 블록 값을 IV값이라고 생각하면 admin을 암호화 했을 때와 같은 결과를 가질 수 있다.

풀이에 사용된 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import socket
 
sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect(('202.120.7.217',8221))
 
print sock.recv(1024)
print sock.recv(1024)
tmp = sock.send("r\n")
tmp = "218e2ab79d1ef718cc84a472450ba88f61646d696e"
tmp=tmp.decode('hex')
tmp = sock.send(tmp+"\n")
 
print sock.recv(1024)
res = sock.recv(1024)
res = res.split('\n')
res = res[1]
tmp = sock.send('l\n')
tmp = sock.send(res[32:]+'\n')
print sock.recv(1024)
print sock.recv(1024)
cs