BSidesSF 2019 CTF Writeup

この大会は2019/3/4 2:00(JST)~2019/3/5 10:00(JST)に開催されました。
今回もチームで参戦。結果は763点で1092チーム中21位でした。
自分で解けた問題をWriteupとして書いておきます。

futurella (Web 1)

Webページにはalien cipherの文字で書かれているが、ソースを見るとフラグがわかった。フォントでそのように操作されていたようだ。

CTF{bring_it_back}

Trivia 1 (101 2)

問題は「My voice is my ________. Verify me.」。穴埋め問題。インターネットで調べるとすぐわかる。

passport

kookie (101, Web 10)

cookie / monsterでログインすると、Cookieのusernameにcookieがセットされている。adminに変更すると、フラグが表示された。

CTF{kookie_cookies}

zippy (Forensics 50)

TCP Streamを見ると、以下のコマンドが実行されていることがわかる。

nc -l -p 4445 > flag.zip
unzip -P supercomplexpassword flag.zip
Archive:  flag.zip
  inflating: flag.txt

No.9のパケットにflag.zipのデータがあるので、エクスポートする。それから上記と同じようにコマンドを実施する。

$ unzip -P supercomplexpassword flag.zip
Archive:  flag.zip
  inflating: flag.txt                
$ cat flag.txt
CTF{this_flag_is_your_flag}
CTF{this_flag_is_your_flag}

table-tennis (Forensics 50)

ICMPパケットが流れている。問題タイトルからもこのデータにフラグが隠されていそう。各パケットからデータ部分を抜き出す。

e0p1c3RB
UzBuZ0Fi
MHV0UDFu
Z1Awbmd9

全部結合して、Base64デコードしてみる。

$ echo e0p1c3RBUzBuZ0FiMHV0UDFuZ1Awbmd9 | base64 -d
{JustAS0ngAb0utP1ngP0ng}
CTF{JustAS0ngAb0utP1ngP0ng}

decrypto (Crypto, Web 300)

何もしないと以下のように表示される。

Welcome to the mainframe!
...
It looks like you want to access the flag!
Please present user object
...
...
...
...scanning
...
...scanning
...
Scanning user object...
...your UID value is set to 56
...
...
...your NAME value is set to baseuser
...your SKILLS value is set to n/a
...
ERROR: ACCESS DENIED
...
...
UID MUST BE '0'

クッキーの状態は以下の通りで、リセットするとパラメータが変わるようだ。

signature
809f6357940b81340fc147830f56f344fe1999316d4b34d8908d784a170869b6

user
e79b54a46aae832c6cb5e9c5cf3903d18046172a49d243198560272d397af792ab7d683425445e75666f3a6b2086d83c7197c9cff3a5c1f9df64c28714fb3a4d

rack.session
BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiRWYyZWVlMzljZDBjMjg2ZDE2MWFm%0AZDI2OTdiODkwOTE2M2RkYmJlOTVkYjU5YzgzNmY5NDcwOWEyZGZlMmU5OGYG%0AOwBGSSILc2VjcmV0BjsARiINJmUAQX1%2BsONJIghrZXkGOwBGIiUFvW1HPcr3%0AaB%2ByY2RPWz%2BprqfdSaqNNT%2BfZCfk1obFOQ%3D%3D%0A--1bc34de559dc8cfd69cf2d8a117e1c7f6bec6eaf

UIDを0にする必要があるが、文章に書かれている内容から、signatureについてはHash Length Extension Attackで何とかなりそう。
問題はuserだが、文字列長からAESの暗号と思われる。rack.sessionの%0Aの前までをBase64デコードしてみると、最後の32バイトが鍵だと推測できる。それを鍵として、復号してみる。

from Crypto.Cipher import AES

def unpad(s):
    return s[:-ord(s[-1])]

enc = 'e79b54a46aae832c6cb5e9c5cf3903d18046172a49d243198560272d397af792ab7d683425445e75666f3a6b2086d83c7197c9cff3a5c1f9df64c28714fb3a4d'.decode('hex')

with open('session', 'rb') as f:
    data = f.read()
key = data[-32:]

iv = enc[:16]
enc = enc[16:]
aes = AES.new(key, AES.MODE_CBC, iv)
dec = unpad(aes.decrypt(enc))
print dec

実行した結果は以下のとおり。

UID 56
NAME baseuser
SKILLS n/a

このデータ形式でHash Length Extension Attackを行い、得られたデータで暗号化したものをクッキーのuserに設定すればよい。

from Crypto.Cipher import AES
import hashpumpy

def pad(s):
    n = 16 - len(s) % 16
    return s + chr(n) * n

def encrypt(key, s):
    iv = '0123456789abcdef'
    aes = AES.new(key, AES.MODE_CBC, iv)
    enc = aes.encrypt(pad(s))
    return (iv + enc).encode('hex')

sign = '809f6357940b81340fc147830f56f344fe1999316d4b34d8908d784a170869b6'
base = 'UID 56\nNAME baseuser\nSKILLS n/a\n'

h, d = hashpumpy.hashpump(sign, base, '\nUID 0\n', 8)
print 'signature:', h

with open('session', 'rb') as f:
    data = f.read()
key = data[-32:]

user = encrypt(key, d)
print 'user :', user

実行結果は以下の通り。

signature: d94e43b063c5e9e4334e2080f7207986792276cf34a260d8ec0c40eff4e03dc0
user : 303132333435363738396162636465661da120fecc4dfcd79b5fe6efb58b8a43054dac681fb99dfe53abd775bf2d1bcede42992c655039ef77071b396fede73ff33b551a9226ac4949b19860e253a23f

それぞれクッキーに設定して画面を更新すると、以下の通りフラグが表示された。

Welcome to the mainframe!
It looks like you want to access the flag!
...
Please present user object
...
...scanning
...
...scanning
...
...
...
...
Scanning user object...
...your UID value is set to 0
...your NAME value is set to baseuser
...
...
...
...your SKILLS value is set to n/a
...your ����������������������@ value is set to 
FLAG VALUE: CTF{parse_order_matters}

CTF{parse_order_matters}