高専SECCON2018 参加記録 Write-Up
高専セキュリティコンテスト2018に参加しました。
多くのwrite-upが既に上がっていますが、折角参加したので残しておこうと思います。
はじめに
僕は”おやまようちえん(おやまどうぶつえん)”というチームで参加しました。
結果は7位でした。僕自身CTF初心者なので10位以内に入れてよかったです。次参加するとしたらもう少しweb問題を練習しておきます。。。
[Binary 100]ログインしたい!
ltraceを実行するとstrcmpでパスワードを比較していることがわかる。
$ ltrace ./login __libc_start_main(0x565f165d, 1, 0xffdd6b94, 0x565f1840 <unfinished ...> puts("hoge\351\253\230\345\260\202 \346\210\220\347\270\276\347\256\241\347\220\206\343\202\267\343\202\271\343\203\206"...hoge高専 成績管理システム version 1.0 ) = 48 printf("\343\203\255\343\202\260\343\202\244\343\203\263\343\203\221\343\202\271\343\203\257\343\203\274\343\203\211: ") = 29 fgets(ログインパスワード: abcdef "abcdef\n", 128, 0xf7f8e5a0) = 0xffdd69cc strtok("abcdef\n", "\n") = "abcdef" strcmp("abcdef", "SCKOSEN{P@SSW0RD!}") = 1 puts("\343\203\221\343\202\271\343\203\257\343\203\274\343\203\211\343\201\214\351\226\223\351\201\225\343\201\210\343\201\246\343\201"...パスワードが間違えています ) = 40 +++ exited (status 0) +++
[Crypto 100]exchangeable if
問題の画像を開くと、4文字分抜け落ちたフラグがある。
問題の画像のexif情報にはmd5のハッシュ値が書いてある。
正しいフラグのmd5ハッシュ値がexif情報にあるハッシュと一致すると考え、抜け落ちた4文字分を総当たりで検索した。
import hashlib chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~" ans = "2009d1c114ed83f57cf8adde69fd6ca8" s0 = "SCKOSEN{sHDtF1" s1 = "NLTIWp}" for c0 in chars: for c1 in chars: for c2 in chars: for c3 in chars: string = s0 + c0 + c1 + c2 + c3 + s1 has = hashlib.md5(string.encode('utf-8')).hexdigest() if has == ans: print(string)
[Crypto 200]シンプルなQRコード
右半分のみのQRコードが与えられる。
与えられた部分からでもマスクパターンがわかる(チームメイトが見つけた)のであとはstrong-qr-decoderでマスクパターンを指定してデコードする。
[Crypto 250]CMA
2つのPublic Keyと、暗号文c1とc2が与えられる。
おそらく異なるeで同一の平文を暗号化しているのでタイトルからも明らかだが、Common Modulus Attackが可能。
import gmpy2 import math n = 69716374785・・・ c1 = 3565707597・・・ c2 = 5756696653・・・ e1 = 28403731 e2 = 17150467 # pow マイナス乗 対応版 def pow_ex(a, b, c): if b < 0: return pow(gmpy2.invert(a, c), -b, c) else: return pow(a, b, c) def main(): gcd, s1, s2 = gmpy2.gcdext(e1, e2) print("s1=%d,s2=%d" % (s1, s2)) v = pow_ex(c1, s1, n) w = pow_ex(c2, s2, n) m = v * w % n m = int(m) s = "".join(map(chr, int(m).to_bytes( math.ceil(m.bit_length() / 8), "big"))) print(s) if __name__ == "__main__": main()
[Network 100]Basic認証
Wiresharkでpcapを見ると、No14の部分でbasic認証の情報を送信していて、それに対してサーバからOKが返ってきているので、basic認証で用いたユーザIDとパスワードがここから手に入る。
更に読み進めるとNo16にはzipファイルのパスワードはjohnのパスワードであると書かれている。
そこでwiresharkのオブジェクトエクスポート機能でflag.zipを取得し、johnのパスワードで展開すると、flagが書かれたテキストファイルが手に入る。
[Network 150]ログインしてフラグを入手せよ
Digest認証の問題。問題文で与えられたmd5のハッシュとそのハッシュ化前の値はDigest認証のresponseである。
nonceが通信するたびに変化するため、pcapファイル内にあるresponseと異なる。
responseの構造は以下に示す。
A1 = username : realm : password
A2 = http method : URI
response = MD5( MD5( A1 ) : nonce : nc : cnonce : qop : MD5( A2 ) )
通信するたびに変化するのはnonceのみなので、サーバから受け取ったnonceを元にresponseを計算すればよい。
import urllib import urllib.request import hashlib url = 'http://digest.kosensc2018.tech/flag.txt' username = "tanaka" realm = "Digest Auth" nonce = "" uri = "/flag.txt" algorithm = "MD5" response = "" qop = "auth" nc = "00000001" cnonce = "ZmMyNDM3NzcyNWI3ZGI5NjQyNjhiNTAwZDkxZjM4YzQ=" md5a1 = "c932836c1feff27841c03453e81d5b13" a2 = 'GET:'+uri def getNonce(): try: data = urllib.request.urlopen(url) html = data.read() except Exception as e: res = e.info()['WWW-Authenticate'].split('"') nonce = res[3] return nonce def genMD5(str): return hashlib.md5(str.encode()).hexdigest() def genResponse(nonce): response = genMD5(md5a1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + genMD5(a2)) return response def genAuthorized(nonce, response): authorized = "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", cnonce=\"%s\", nc=%s, qop=%s, response=\"%s\", algorithm=\"%s\"" % (username, realm, nonce, uri, cnonce, nc, qop, response, algorithm) return authorized def main(): nonce = getNonce() response = genResponse(nonce) auth = genAuthorized(nonce, response) header = {'Authorization': auth} req = urllib.request.Request(url, None, header) try: data = urllib.request.urlopen(req) html = data.read() print("success") print (html.decode('utf-8')) except Exception as e: print (e.code) print (e.info()) if __name__ == '__main__': main()
[Web 100]サーバーから情報を抜き出せ!
画像が3つ掲載されたウェブページが表示される。
画像の参照先が”/image?filename=1.jpeg”となっていることから"1.jpeg"の部分をいじると"flag.txt"が表示されると推定。
しかし、"/image?filename=flag.txt"ではエラーになってしまうため、ディレクトリトラバーサルが有効だと考え、"/image?filename=../flag.txt"としたところ、flagが表示された。
どうやら"flag.txt"は画像の1つ上のディレクトリにあったらしい。
[Misc 50]サイトを見ていただけなのに
Webサイトを見ていただけなのに、謎のウイルスがダウンロードされて起動させられてしまった!
[Misc 100]謎のファイル
与えられたzipを展開すると"_rels"、"docProps"、"rename_me.xml"、"word"フォルダが出てきた。
何かのファイル構造で、”rename_me.xml”の名前を正しく直し、zip圧縮し直すことでファイルが読めるようになると考え、更に"word"フォルダがあるため、docxファイルであると推定した。
"rename_me.xml"について、docxのファイル構造を調べると、"[Content-Type].xml"という名前で保存されているべきなのでリネームした。
最後にもう一度zip(フォルダで圧縮ではなくすべてのフォルダとファイルを指定して)圧縮し、拡張子をdocxにすることでflagが表示される。