この大会は2018/6/16 9:00(JST)~2018/6/17 9:00(JST)に開催されました。
今回もチームで参戦。結果は401点で400チーム中43位でした。
自分で解けた問題をWriteupとして書いておきます。
Sanity Check (Misc)
参加確認問題。問題にフラグが書いてある。
matesctf{san1ty_ch3ck!}
web_token (Crypto)
Cookieのtokenは以下のようなデータ。
data = user + ":user" AES-ECB暗号(data + HMAC_SECRET(16バイト)) + MAC(data)
HMAC_SECRETの後ろを1文字ずつはみ出させ、1ブロック目に0\x0f....\x0fから順番にブルートフォースで一致するものを割り出し、HMAC_SECRETを求める。
その後、以下のような暗号データを作り出す。
0123456789abcdef ###############: adminHHHHHHHHHHH HHHHH
1つは以下の暗号の1ブロック目を取得する。
0123456789abcdef ###############: userHHHHHHHHHHHH HHHH
もう一つは以下の暗号の2-3ブロック目を取得する。
0123456789abcdef ###############: adminHHHHHHHHHHH HHHHH
あとはそのまま結果を結合したものがadminのtokenになる。以下、Cookieにセットするtokenを求める最終的なコード。
import requests import string from base64 import b64decode, b64encode from Crypto.Hash import HMAC BLOCK_SIZE = 16 pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * \ chr(BLOCK_SIZE - len(s) % BLOCK_SIZE) def query(user): url = 'http://ec2-13-229-142-46.ap-southeast-1.compute.amazonaws.com:9999/login' s = requests.session() res = s.post(url, {'name': user}) token = s.cookies.get('token') return token def get_mac(secret, user): data = user + ':admin' h = HMAC.new(secret.encode('utf-8')) h.update(data.encode('utf-8')) mac = h.hexdigest() return mac.encode('utf-8') chars = string.ascii_letters + string.digits #### get secret #### secret = '' for i in range(16): padding = '#' * (12 + i) correct = b64decode(query(padding)[:-32])[32:48] for c in chars: try_user = pad(c + secret)[:16] try_str = b64decode(query(try_user)[:-32])[:16] if try_str == correct: secret = c + secret print secret break print secret #### get encrypted admin data #### plain1 = '#' * 15 block1 = b64decode(query(plain1)[:-32])[:16] plain2 = pad('admin' + secret) block2 = b64decode(query(plain2)[:-32])[:32] mac = get_mac(secret, plain1) token = b64encode(block1 + block2) + mac print token
この結果、HMAC_SECRET = "QGxIOmkJxv4ojNhD"であることがわかり、###############ユーザのadmin判定されるtokenは以下の文字列であることがわかる。
PF9nsMg/+axwSmmJlEvgGDUG7h6hBBZoa/a+kFTYn5PB0lINDuxBu0loRtfKikhm4dd233ac961cb50af18e0b2b2b35414e
このtokenをクッキーに設定してアクセスすると、フラグが表示された。
Hi ############### Congratulation! Flag is matesctf{ECB_M0d3_1s_Ins3cur3}
matesctf{ECB_M0d3_1s_Ins3cur3}
Viettel Store (Crypto)
$ nc 13.251.110.215 10001 Viettel Store You were walking on the street. Suddenly, you found a wallet and there are 4060958 VND inside. You decided to go to Viettel Store to buy a new phone Your wallet: 4060958 VND 1. Phone list 2. Order 3. Pay 4. Exit 1 Your option: 1 0 - Samsung Galaxy S9: 19990000 VND 1 - Oppo F5: 5990000 VND 2 - iPhone X: 27790000 VND 3 - Vivo Y55s: 3990000 VND 4 - Itel A32F: 1350000 VND 5 - FLAG: 999999999 VND Your wallet: 4060958 VND 1. Phone list 2. Order 3. Pay 4. Exit 2 Your option: 2 Item ID: 5 Your order: product=FLAG&price=999999999×tamp=1529124244035583&sign=effc77ebfb79b73e538b98118029f374f15a88360b17459a937548acf734619e Your wallet: 4060958 VND 1. Phone list 2. Order 3. Pay 4. Exit
それぞれ選択すると、対応する関数が実行される。
■1: view_list 商品と価格を表示 ■2: order payment = 'product=%s&price=%d×tamp=%d' sign = sha256(signkey+payment).hexdigest() ■3: pay paymentとsignのペアが合っているかチェック
FLAGのpriceが所持金より高いので、priceを1にすることを考える。Hash Length Extension Attackで攻撃する。
import socket import hashpumpy s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('13.251.110.215', 10001)) data = s.recv(256) data += s.recv(256) print data + '2' s.sendall('2\n') data = s.recv(256) data += s.recv(256) print data + '5' s.sendall('5\n') data = s.recv(256) print data payment0 = data.split('\n')[1] sp = payment0.rfind('&sign=') sign = payment0[sp+6:] payment = payment0[:sp] for i in range(1, 50): h, d = hashpumpy.hashpump(sign, payment, '&price=1', i) data = s.recv(256) print data + '3' s.sendall('3\n') data = s.recv(256) data += s.recv(256) payment_flag = d + '&sign=%s' % h print data + payment_flag s.sendall(payment_flag + '\n') data = s.recv(256) print data if data != 'Invalid Order!': break data = s.recv(256) data += s.recv(256) print data + '4' s.sendall('4\n')
この結果、FLAGを購入でき、フラグが得られる。
matesctf{e4sy_3xt3nti0n_4tt4cK_x0x0}