SECCON2017国内決勝大会の振り返り
こんにちは、臼田です。
今回は個人的に参加してきたSECCON2017 国内決勝大会の振り返りとしてブログを残したいと思います。
完全に個人向け・チーム向け・参加した人向け程度の内容なので細かいところは説明しません。
サマリ
今回ずっと個人的に参加していたチーム「よかろうもん」で初めてSECCONの決勝大会に出ることができました。
SECCON2014から初めて、本戦(国際大会)ではないにしろ決勝に出れたことはとても嬉しかったです。
結果としては24チーム中12位という悔しい結果でした。特に11位以上と1問(100pt)以上差が開いていることから、まだまだ精進が必要だと感じました。
当日の動き
序盤は自分が得意なジャンル(Crypto)が無いことに落胆しつつ、梅田のDefenceポイントを稼ぐためのスクリプトを実装しました。
最終的なコードは下記の通り。かなり生々しいのであしからず。
import requests import re import json import time import datetime target_base = 'http://umeda.koth.seccon/photos/' mosted_num = '28847' target = target_base + '28847' mosted = 'http://umeda.koth.seccon/most-liked' flag_file = 'flagword.txt' flagword = "468339b255e76b47fe3f2b2f307d0a47" # name="csrf_name" value="csrf5a879d83a15e9"> re_csrf_name = re.compile(r'.*name="csrf_name" value="([a-z0-9]*)".*name="csrf_name" value="([a-z0-9]*)".*') # name="csrf_value" value="4180b70b4534af3be3454c7e87d2d0cf"> re_csrf_value = re.compile(r'.*name="csrf_value" value="([a-f0-9]*)".*name="csrf_value" value="([a-f0-9]*)".*') # <img class="photo-individual" src="/photos/200/raw"> re_mosted = re.compile(r'.*<img class="photo-individual" src="/photos/([0-9]+)/raw">.*') s = requests.Session() cookies = dict(PHPSESSID='eece7c193b808e6d8b49e4a68e050678') count = 0 while 1: if count == 0: try: # get mosted like r3 = s.get(mosted, cookies=cookies) o = re.match(re_mosted, r3.text) mosted_num = o.group(1) target = target_base + mosted_num print 'change: ' + target except Exception as e: print('err: check') # read flagword with open(flag_file, 'r') as f: for line in f: flagword = line.strip() print 'flagword: ' + flagword try: # get csrf token r = s.get(target, cookies=cookies) # print(r.text) m = re.match(re_csrf_name, r.text) csrf_name = m.group(2) n = re.match(re_csrf_value, r.text) csrf_value = n.group(2) # print csrf_name # print csrf_value # send payload = {'csrf_name': csrf_name, 'csrf_value': csrf_value, 'content': flagword} r2 = s.post(target + '/comment', data=payload, cookies=cookies) print r2.status_code, ':', mosted_num, ':', datetime.datetime.now().isoformat(), ':', flagword # time.sleep(1) count += 1 except Exception as e: print('err: flag') time.sleep(1) if count > 60: count = 0
急いで実装しているので、いろいろひどい…
ただ、欲しい部分をコメントに書いて、見ながら正規表現書くのはやりやすいと思いました。
序盤はmosted_likeのページが固定だったが、中盤から60回に1回ぐらい見に行くようにしました。
Defenceのflagwordも別のスクリプトで取得したものをflagword.txt
に保存しておいてそれを読み込むようにしました。これは余計なクエリを挟んでフラグを逃さないため。
1回のクエリあたり、平均的に1.5秒くらいかかってたからCSRFトークン取得とsubmitで3秒位、フラグを取れるかどうかは結構ギリギリなので、他のサーバ用としても使えるという意味でDefenceフラグの外出しは正しいと思います。
というか、今思ったらsubmitしたページのCSRFトークン使い回せばよかった…
途中でmosted_likeが死ぬことがあったので(PHPがエラーを起こす内容をsubmitしたか、photos/9が消えていたことからmosted_likeが削除されたことによるバグだと考えられた)、手離れがいいように全部tryに突っ込みました。後半は殆ど面倒を見ていなかったので良かったと思う。突貫工事のスクリプトほどtryが役立つところはないですね。
終盤は幕張の2つ目を探していたけど見つけることができませんでした。まさかCertの中にあるなんて…orz
後から考える戦略
上記を実装した後に少し迷ったけど、もう少し他チームを落とすためのmosted_likeの取り合いを頑張っても良かった気がしました。
結局手を付けられなかったが、新規アカウントの作成と、任意のページのlike自体は両方共簡単に実装できそうだったと思います。後半はmosted_likeはほとんど200のままだったので、余地はあった気がします。
梅田のDefenceポイントは5分おきに3ptsずつ入っていたように見受けられました。殆どのチームが梅田にフラグを立てていたように思えるので最大で3 * 20で60ptsの山分け、15チームだったとしても45pts と割といいポイント。
各チームのmosted_likeのチェック頻度によるし、後半には他チームも同じような事をしてもおかしくない(というか大会中に実際に行われていなかった事が不思議ですが)、少ない回数でも大量にポイントを獲得する機会が増えれば、1問解くよりも効率は良かったように思えました。
まあ、結果的にAtackを取れなかったのでよりそう思ったわけですが。
まとめ
Attackを頑張ること、スクリプトの実装速度をあげることも必要だと感じましたが、Defenceを増やしに行く戦略ももう少し頑張りたいと思いました。
次回は入賞したい!