Jinjaのサーバーサイドテンプレートインジェクション(SSTI)

Jinjaのサーバーサイドテンプレートインジェクション

説明

外部から制御できるテキストをJinjaのテンプレートソースに渡すと、サーバーサイドテンプレートインジェクション(SSTI)が発生するおそれがあります。攻撃者は、サーバーが評価する{{ ... }}や{% ... %}などの式や文を挿入できます。影響は、テンプレートに公開するデータ、グローバル変数、呼び出し可能なオブジェクトによって異なります。

Jinjaの自動エスケープは、出力をHTMLの文脈で保護する機能です。信頼できないテキストがテンプレートソースとして解釈されるSSTIは防げません。

想定される影響

  • テンプレートに公開されたデータや設定情報が漏えいするおそれがあります。
  • 公開されたオブジェクトの副作用を伴うメソッドが呼び出され、条件によってはコードの実行につながる可能性があります。
  • 小さなテンプレートでも、大量の出力やCPU・メモリの過剰な消費を引き起こす可能性があります。

対処方法

  • テンプレートソースは開発者が管理してください。固定テンプレートを名前やファイルで読み込み、信頼できない値はコンテキストデータとしてのみ渡してください。
  • リクエストデータからテンプレートソースを組み立てたり、render_template_string(...)やstream_template_string(...)などの文字列レンダリングAPIに渡したりしないでください。区切り文字の削除、HTMLエスケープ、名前がsanitizeというだけの関数では十分な対策になりません。
  • 許可リストは、開発者が作成した完全なテンプレートから選択する用途に限定してください。任意のユーザー作成テンプレートを、文字の許可リストだけで安全にしようとしないでください。
  • ユーザー作成テンプレートが必要な場合は、まず機能を制限した専用言語を検討してください。Jinjaが必要であれば、SandboxedEnvironmentまたはImmutableSandboxedEnvironmentを使用し、副作用を伴うメソッドを含まない最小限のデータだけを公開してください。レンダリングエラーを処理し、隔離したサービスでCPU・メモリ・出力量を制限してください。Jinjaのサンドボックスだけでは完全な隔離は実現できません。

例

変更前

python
from flask import Flask, render_template_string, request

app = Flask(__name__)

@app.get("/greeting")
def bad_greeting():
    return render_template_string(request.args["template"])

変更後

python
from flask import Flask, render_template, request

app = Flask(__name__)

@app.get("/greeting")
def safe_greeting():
    return render_template(
        "greeting.html",
        name=request.args.get("name", ""),
    )

templates/greeting.htmlは、アプリケーションとともに配置する固定ファイルです。

html
Hello {{ name }}

説明:

  • 変更前: リクエストの値をテンプレートソースに渡すため、攻撃者がJinjaの構文を指定できます。
  • 変更後: 配置済みのファイルをテンプレートソースとして使い、リクエストの値はnameのコンテキストデータとしてのみ渡します。

テンプレートソースとデータ

source=には開発者が管理するテンプレートを渡してください。ユーザーの値をテンプレート文字列に連結せず、別のコンテキストデータとして渡してください。

python
from flask import request
from jinja2 import Environment

env = Environment()


def unsafe_keyword_source():
    return env.from_string(globals={}, source=request.args["template"])


def safe_keyword_context():
    return env.from_string(
        globals={"name": request.args["name"]}, source="Hello {{ name }}"
    )

2番目の呼び出しはテンプレート文字列を固定し、ユーザーの値をデータとして渡します。結果をHTMLとして返す場合は、自動エスケープなど出力の文脈に応じた保護も適用してください。

制約

ユーザー作成テンプレートを許可するサービスでは、サンドボックスの有無にかかわらず、公開データ、呼び出し可能なオブジェクト、例外処理、リソース制限、隔離を併せて検討する必要があります。固定テンプレートでも、HTMLなど出力の文脈に応じた保護は別途必要です。

参考資料