KOSEN セキュリティ・コンテスト 2018 Write Up その他
福岡博多市で開催されたKOSEN セキュリティ・コンテストに insecure として,現地参加してきた.
今回イベントのHP -> KOSENセキュリティコンテスト2018
前日に猫カフェに行ったりしつつ福岡入り.ホテルでは何問か常設CTFの問題をやるなどした.
暇を持て余す集団の中にいる pic.twitter.com/8ARA4Xa5NY
— スラスト (@thrust2799) 2018年8月31日
弊チームの参加者はtheoldmoon0602 (ふるつき),ptr-yudai (師匠),yoshiking (よしキング),thrust2799 (スラスト,私).
今回は:pro:なふるつき氏と師匠氏,今回初参加で特訓を積み重ねたよしキングという同一研究室メンバーに加えてもらう形で参加させていただいた.
蓋を開けてみれば,ほとんど3氏が問題を解いて,私はNetworkしかsubmitできなかった....やはりよしキングは:pro:だった.
insecureは一位で優勝,ありがたいことにSECCON2018に出れます.やった.
そんなこんながあった大会の,Write Up. 一応,解けた順に,触ったものも含めて.
[Network 11] ログインしてフラグを入手せよ。 (Score: 150)
問題内容:
ヒント:
我々は秋葉原ラジオ会館上空に飛来したタイムマシンを用いて、未来の超高性能コンピュータを入手した。
そのコンピュータによると、MD5の値 b21424f30227ac8bc08c69216c30815 のハッシュ化前の値は以下であるらしい。
c932836c1feff27841c03453e81d5b13:oX3Ar2V0BQA=34c6176e8e33d6da83cc500028b8f9c8de95b91d:00000001:OWEyZWEyNmZhNzAyYTUwMzM0MzRjYzMxZDljZGY2OTU=:auth:71998c64aea37ae77020c49c00f73fa8
なんというSteins;Gate....これは布教ですわぁ(いいぞ).
問題としてはHTTPのDigest認証を突破するタイプ.
ちなみに研究室で勉強したり前日に復習したりしてsolverが手元にあった問題だった.なんという幸運!
wiresharkで問題のパケットを開いて,フィルタ (tcp.stream eq 0)でFollow TCP Stream.
やはり401エラーが返され認証が必要とされている.
また,WWW-Authenticateフィールドで必要そうなものが返されている.
'WWW-Authenticate'内容:
Digestrealm="Digest Auth" nonce="bt2zImd0BQA=c6284989d568c3b3483e649ed0d4b440918fb05e" algorithm=MD5 qop="auth"
次に,フィルタ (tcp.stream eq 1)でFollow TCP Stream.(見切れた部分は401エラーで先ほどと同じ)
今度は200で通り,フラグが存在するっぽいリンクが書かれたページがある.
どうやらDigest認証でhttp://digest.kosensc2018.tech/flag.txt
にアクセスしたいらしい.
ちなみに,HTTP送信ヘッダのHostフィールドはアクセス先のホスト名が格納されている.
他に有用な情報は得られなかったので,この情報で突破したい.
つまり,パスワード回避してDigest突破したい!
ここで,もう一度フィルタ(tcp.stream eq 1)でのTCP Streamを見てみる.
どうやら,認証に成功した状態にはヘッダの要素として次のものが挙げられそうである.
GET / HTTP/1.1 Host: digest.kosensc2018.tech Authorization: Digest username="tanaka", realm="Digest Auth", nonce="zwFHJGd0BQA=50c6205549fdb0f63aa3f780a7504cc82864010d", uri="/", cnonce="ZmMyNDM3NzcyNWI3ZGI5NjQyNjhiNTAwZDkxZjM4YzQ=", nc=00000001, qop=auth, response="dce3409758e948c5ba76fb121f089812", algorithm="MD5"
この情報のうち,WikipediaのDigest認証やQiita先生に聞いてみたところ,次の意味があるそう.
Digest username: ユーザー名 realm: 認証領域名 nonce: サーバー生成のランダム文字列 uri: 'http://digest.kosensc2018.tech'で表示されるページからの相対パス cnonce: クライアント生成のランダム文字列 nc: カウント qop: Digestの生成方法,bodyを含めるか,大体"auth"な気がする response: 'あるデータ'のalgorithmフィールドによる方法でのハッシュ algorithm: responceハッシュの生成方法,大体MD5な気しかしない
ちなみに,nonceは一度認証せずにアクセスしたときに返されるものをそのまま使う必要がある.
これはスクリプトを組んだら問題がなさそうなので,あとはresponce.
ここで,responseの構成をWikipediaさんから引っ張ってくる.
A1 = ユーザ名 ":" realm ":" パスワード
A2 = HTTPのメソッド ":" コンテンツのURI
response = MD5( MD5(A1) ":" nonce ":" nc ":" cnonce ":" qop ":" MD5(A2) )
ここのMD5()はMD5でハッシュを取った結果を意味している.
ここで,新たに出てきたのはA1の'ユーザ名 ":" realm ":" パスワード'だった.
パスワードが分かんねぇつってんだろ!?!?!?
詰みかと思ったときのヒント,実はA1が書いてあるのである.
ヒントにあるそれぞれの値は,
response : 2b21424f30227ac8bc08c69216c30815 MD5(user:realm:password) : c932836c1feff27841c03453e81d5b13 nonce : 34c6176e8e33d6da83cc500028b8f9c8de95b91d nc : 00000001 cnonce : OWEyZWEyNmZhNzAyYTUwMzM0MzRjYzMxZDljZGY2OTU qop : auth MD5(REQUEST:URI) : 71998c64aea37ae77020c49c00f73fa8
はい.
どうやらMD5(A1)は c932836c1feff27841c03453e81d5b13
を使いまわせそう.
ということで,必要な情報はそろったのでコーディングタイムです.
(ちなみにtypoで4回くらい401が返されたのは笑い話)
import requests import hashlib headers = requests.get('http://digest.kosensc2018.tech/flag.txt').headers HA1 = 'c932836c1feff27841c03453e81d5b13' HA2 = hashlib.md5('GET:/flag.txt'.encode('utf-8')).digest().hex() nonce = headers['WWW-Authenticate'][35:87] res = hashlib.md5((HA1 + ':' + nonce + ':00000001:OWEyZWEyNmZhNzAyYTUwMzM0MzRjYzMxZDljZGY2OTU=:auth:' + HA2).encode('utf-8')).digest() flags = requests.get('http://digest.kosensc2018.tech/flag.txt', headers = {'Host':'digest.kosensc2018.tech','Authorization':'Digest username="tanaka", realm="Digest Auth", nonce="' + nonce + '", uri="/flag.txt", algorithm=MD5, response="' + res.hex() + '", qop="auth", nc=00000001, cnonce="OWEyZWEyNmZhNzAyYTUwMzM0MzRjYzMxZDljZGY2OTU="'}) print(flags) flags.text
A . SCKOSEN{digest_auth_is_secure!}
ちなみに運営がフラグ設定間違っていたらしく,しばらく解答できなかった.おそらく1番乗り.
この問題のポイントは,responseがデコードされるか,'ユーザ名 ":" realm ":" パスワード'のハッシュ(サーバーの.htdigest)が漏れるとだめってお話でした.
[Network 10] Basic認証 (Score: 100)
問題内容:
パケットファイルを解析せよ
さて,Network2問目(1問目).こちらはパケットファイルを解析するだけなのか~?
というわけで,フィルタ(tcp.stream eq 0)でFollow TCP Stream.
いつもの.(401エラー)
やはり問題文通りBasic認証をしているっぽい.
次.フィルタ(tcp.stream eq 1)でFollow TCP Stream.
200が返されてなんかあるみたい.
flag.zipがあって,Johnさんのパスワードが解錠パスワード,と.
IPアドレスがHostフィールドにあって,アクセスはここからはできないみたいなので,パケットファイルにzipファイルが残っていそう.
Wiresharkさんにお願いしたいので,File -> Export Objects -> HTTP でパケット内のファイルオブジェクトを一覧表示.
予想通りflag.zipがあったのでエクスポートしていく.
さて,Johnさんのパスワード...|ω・`)
john:s8oX*zlcro8?#wlblpr4 (ユーザー名:パスワード)
Basic認証さん!?
そう,Basic認証ではBase64エンコードされたユーザー名とパスワードが格納されており,さらにWiresharkさんは勝手にデコードまでしてくれるのである.
あとはこれで解凍して解答(激寒ギャグ).
A . SCKOSEN{B@$ic_@uth_is_un$@fe}
Basic認証はパケット取られるだけでアウトなのであまり使わないでねっていう問題でした.
ここまでが自力でSubmitできた問題.
全然強くない人なのでここからは最初の発想とかだけ.
役割分担したと思いたい....
[Crypto 08] シンプルなQRコード (Score: 200)
問題内容:
半分のQRコードを入手した。なんとか復元できないだろうか。
最初の発想といっても,これはこれで自分で全部やったと言えない問題.
実は別のCTF大会で出てたWrite Upをそのまま流用してきた感じ.
先人様ありがとうございました.
SECCON CTF 2013 online予選 forensics 400
さて,問題のQRコードはこれ.
とりあえず,マーカーとかの何も考えなくても修復できるところを修復.
これだけでもうQRっぽいけど,読み取れない.
そこで,先ほどのWrite Upを頼りにマスクパターン,誤り訂正bitの15[bit]を復元.
さらに修復を進めたQRコードがこちら.
これでも読み取れず,ちょっと悩んでいたところで師匠氏からお声がけ.
そのまま解いてもらってSubmit.
ところで,あとでホテルで聞いたところによると,もう読み取るだけだったそう.
pythonのstrong-qr-decoderってものを使ったらしく,新幹線の中で使ってみた.
まず,こちらのGitHubからclone.
次に,これに掛けられるように画像から'X' (黒いドット)と'_' (白いドット)のテキスト形式に変換.
変換スクリプトはこちら.
from PIL import Image img = Image.open('broken_qr_.png') x, y = img.size qr = open('qr_data.txt', 'w') for v in range(y): if v % 10 != 0: continue for u in range(x): if u % 10 == 0: if img.getpixel((u, v)) == (0, 0, 0, 255): qr.write('X') else: qr.write('_') qr.write('\n')
で,出てきたQRがこちら.
XXXXXXX_______X_X_____XXXXXXX X_____X_______X_X_X___X_____X X_XXX_X_______XXXX_X__X_XXX_X X_XXX_X__________X__X_X_XXX_X X_XXX_X_X_____X___X___X_XXX_X X_____X_______X_XXX___X_____X XXXXXXX_X_X_X_X_X_X_X_XXXXXXX ________X______XX__X_________ __XX__XXX_________XX_XX_X____ _______________X_XX_X___XX_X_ ______X___________XXX___XXX_X ______________XXX_X_X_XX__XX_ ______X_______X_X__XX_X_XXX_X _______________X__X___X__XXX_ ______X_________X__X_XXXX_XXX ___________________XX_XXX__XX ______X_______X_X___X___XX_X_ _______________X__XX___XX___X ______X_______X_XX_____XXXX_X ________________XXX_XXX___XXX ______X________XX_XXXXXXXXX_X _________X__________X___X_XXX XXXXXXX__X______X__XX_X_XXXX_ X_____X_______XX___XX___XX_X_ X_XXX_X_______X___X_XXXXXXX__ X_XXX_X__X____X_X_XX__X__XX__ X_XXX_X__X_______XX__X__X_X_X X_____X_______X_XX_X_______XX XXXXXXX_________XX_X_XX_XX___
こちらをstrong-qr-decoderに掛けて,
A . SCKOSEN{remove_rs_qr}
100の実績,もらっていいよね?
[Crypto 08] RSA? (Score: 300)
問題内容:
電話レンジ(仮)でのDメールに失敗し、冪演算のない世界線へ来てしまった。
この世界線は今までいた世界線のRSA暗号に似た暗号で守られている。解読してみよう。
[+] Public key (n,e): (746149315120445105644911779735002615257864427638948809430146775899763900845401478319781237412694334410613686368021055483040278357991481684487813129494944899522460397699493087246505218096960286204364461639918177320793843718724747574381859928496072509749639870292090503328557688115529251888351927498428567126853657063832878452773780418783149866919316163560203180423276894765325460041738451797385343149303272504850808939009366426995340204717301911359661640524141292447180118808883848024348018576236114084330353144353115672904820149103681022683146560799116848906807498625698939954475819479000937838859292276566046219449122782347075051511004009561691484747596362940386608213329221089677679229620860810111092211338341215804260360529582967128623164620534814690277276443932913145186024258511492440354889009304938979173562941458331119511296138076261503871542382962671556195928643434506282416038086486816450594938254170549974935406839221438655021923764949572023494832426904174630809785240917462806646302389526945618245684059655484996935724994381587851879888451272335891503968849596157334527192713715105054266039301159419016747298458532769985135446278269957364121043506362499497443274900182869320930776855669486054000707293632271709427175565301,825821) [+] Ciphertext = 34196057544535966582914714160221953732017949669815971420694645835609518260754242418191180642793 [+] Dec(c) == m?: True
ということで,累乗のないRSA暗号.
掛け算かな?試してみよう.
d * e mod n ≡ 1 m * e mod n ≡ c c * d mod n ≡ m 11 * 5 mod 3 ≡ 1 2 * 5 mod 3 ≡ 1 1 * 11 mod 3 ≡ 2
いけそうである.
ここで,この情報をよしキングに投げて自分は別の問題へ行ってしまった.
解けた後で教えてくれたのだが,Ciphertextをeで割ってエンコードを「ていっ」ってするだけだったらしい.
これは自宅に帰ってから書いたスクリプト.
import codecs e = 825821 c = 34196057544535966582914714160221953732017949669815971420694645835609518260754242418191180642793 codecs.decode(hex(c // e)[2:].encode('utf-8'), 'hex_codec')
A . SCKOSEN{Extended_Euclide@n_@lg0rithm}
これも100の実績がほしい.
[Web 16] 進撃せよ (Score: 300)
問題内容:
求めるものは壁の向こう側にある。
巨人になったつもりで、進撃せよ。
これは進撃の巨人ですね,私わからんけど.
問題のサーバーにアクセスするとこんな感じ.
で,それぞれクリックするとこんな感じ.
どうやらWAF (Web Application Firewall)がflagを検知してforbidenしているらしい.
で,肝心のリクエストは http://waf.kosensc2018.tech/ist/ZmxhZy50eHQ=
って感じで,最後に何かBase64っぽい文字列.
試しにディレクトリトラバーサルな文字列をBase64して試した結果がこちら.
/etc/passwd
が見つかってしまった....
とりあえずこれをチームで共有,ここから /etc/passwd
の WWW-data
のユーザーディレクトリ等を参考にエスパー.
何とか /var/www/html/waf/files
に一覧されてるファイルがあることが分かって,さてどうするかってなった次第.
と,ここでふるつき氏から大勝利情報 (WAFのphpコード)が降ってきて,あれよあれよという間にsubmit.
どうやらWAF部は1回しかBase64デコードしていないのにファイル名抽出部は無限にBase64デコードする仕様になっていたらしい.
2回 flag.txt
をBase64エンコードしたものを投げて,Flag獲得.
A . SCKOSEN{beyond_the_wall...}
運営はエスパーしてBase64を2回するのを想定していたそう.
ちなみにふるつき氏はこちらみたいにエスパーせずにプロセス情報を見たり設定ファイルにあたり付けて獲得した模様.
やり方がとてもスマート!
さて,今回触れた問題はこんなところでした.
さすがにNetworkは得意分野なのでできるんですが,ほかの分野が全然なので結局周りのチームの解いている人よりも全然取れていないのが実情.
優勝したけど個人としては精進を続けるしかないと思った大会だった.(毎回思ってるだろとか言わないで...)
最後に,公開されているチームメイトのWrite Upは以下.
高専セキュリティコンテスト 2018 #kosensc Writeup - ふるつき