본문 바로가기

CTF

[RCTF 2018] cpushop

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
#!/usr/bin/env python
# encoding: utf-8
 
import random
import string
import signal
import sys
import os
import time
from hashlib import sha256
from urlparse import parse_qsl
 
os.chdir(os.path.dirname(os.path.abspath(__file__)))
signkey = ''.join([random.choice(string.letters+string.digits) for _ in xrange(random.randint(8,32))])
items = [('Intel Core i9-7900X'999), ('Intel Core i7-7820X'599), ('Intel Core i7-7700K'349), ('Intel Core i5-7600K'249), ('Intel Core i3-7350K'179), ('AMD Ryzen Threadripper 1950X'999), ('AMD Ryzen 7 1800X'499), ('AMD Ryzen 5 1600X'249), ('AMD Ryzen 3 1300X'149), ('Flag'99999)]
money = random.randint(100010000)
 
def list_items():
    for i,item in enumerate(items):
        print '%2d - %-30s$%d' % (i, item[0], item[1])
 
def order():
    n = input_int('Product ID: ')
    if n < 0 or n >= len(items):
        print 'Invalid ID!'
        return
    payment = 'product=%s&price=%d&timestamp=%d' % (items[n][0], items[n][1], time.time()*1000000)
    sign = sha256(signkey+payment).hexdigest()
    payment += '&sign=%s' % sign
    print 'Your order:\n%s\n' % payment
 
def pay():
    global money
    print 'Your order:'
    sys.stdout.flush()
    payment = raw_input().strip()
    sp = payment.rfind('&sign=')
    if sp == -1:
        print 'Invalid Order!'
        return
    sign = payment[sp+6:]
    try:
        sign = sign.decode('hex')
    except TypeError:
        print 'Invalid Order!'
        return
 
    payment = payment[:sp]
    signchk = sha256(signkey+payment).digest()
    if signchk != sign:
        print 'Invalid Order!'
        return
 
    for k,v in parse_qsl(payment):
        if k == 'product':
            product = v
        elif k == 'price':
            try:
                price = int(v)
            except ValueError:
                print 'Invalid Order!'
                return
 
    if money < price:
        print 'Go away you poor bastard!'
        return
 
    money -= price
    print 'Your current money: $%d' % money
    print 'You have bought %s' % product
    if product == 'Flag':
        print 'Good job! Here is your flag: %s' % open('flag').read().strip()
 
def input_int(prompt):
    sys.stdout.write(prompt)
    sys.stdout.flush()
    try:
        n = int(raw_input())
        return n
    except:
        return 0
 
def menu():
    print "CPU Shop"
    while True:
        print "Money: $%d" % money
        print "1. List Items"
        print "2. Order"
        print "3. Pay"
        print "4. Exit"
        sys.stdout.flush()
        choice = input_int("Command: ")
        {
                1: list_items,
                2: order,
                3: pay,
                4: exit,
        }.get(choice, lambda *args:1)()
        sys.stdout.flush()
 
if __name__ == "__main__":
    signal.alarm(60)
    menu()
 
cs

다른분들 많이 풀길래 열심히 잡아봤는데 결국 포기한 문제였다..ㅠㅠ

대회가 끝난 후 write up을 바로 봤는데 hash length extension attack 의 취약점을 이용한 문제인걸 알 수 있었다.

그래서 hash length extension attack에 대해 문서들을 조금 찾아보았다. 핵심내용을 요약해보자면 

key를 몰라도 encrypt_hash(key + plain_text)와 key의 길이를 알면 encrypt_hash(key + plain_txt + 원하는문자열) 의 hash값을 알아낼 수 있다는 것이다.


위의 내용이 해당 문제에서 왜 문제가 되냐면

37 : sp = payment.rfind('&sign=')

이 문제에서는 &sign= 의 문자열을 기준으로 앞뒤 문자열을 자르고 진행하기 때문이다

또한 필터링 부분이 제일 앞글자를 기준으로 순서대로 product 와 price에 값을 넣기 때문에 앞 부분에 살 수 있는 물품을 넣어두고 뒤에 추가로 원하는 문자열에 &product=Flag 을 넣어두면 마지막 product에 들어가는 물품의 이름은 Flag가 되고 price 는 앞 부분에 넣은 물품의 가격이 되기 때문에 Flag를 살 수 있다.

위쪽에서 말한 encrypt_hash(key + plain_txt + 원하는문자열)는 hashpumpy 모듈을 이용해서 알아낼 수 있다.


[Solve]

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
import hashpumpy
from pwn import *
 
= remote("cpushop.2018.teamrois.cn"43000)
 
p.recvuntil("Command: ")
p.sendline('2')
p.recvuntil("ID: ")
p.sendline('2')
p.recvuntil("Your order:\n")
payment = p.recvuntil("\nMoney")[:-7]
 
sp = payment.rfind('&sign=')
sign = payment[sp + 6:]
payment = payment[:sp]
p.recvuntil("Command: ")
 
for i in range(832):
    newsign, newpayment = hashpumpy.hashpump(sign, payment,'&product=Flag', i)
    newpayment += '&sign=%s' % newsign
    p.sendline('3')
    p.recvuntil("order:")
    p.sendline(newpayment)
    s = p.recvuntil("Command: ")
    if not "Invalid Order!" in s:
        print s
        break
p.close()
# Good job! Here is your flag: RCTF{ha5h_l3ngth_ex7ens10n_a77ack_1s_ez}



'CTF' 카테고리의 다른 글

[ISITDTU CTF 2018] write up  (0) 2018.07.29
[SCTF 2018] dingJMax  (0) 2018.07.14
[RCTF 2018] simple vm  (4) 2018.05.21
[ASIS CTF 2018] Density  (0) 2018.05.18
[Hitb-xctf 2018] multicheck  (0) 2018.05.13