テンプレートインジェクション

信頼できない入力がC#のテンプレートソースとして処理される問題

説明

サーバーサイドテンプレートインジェクション(SSTI)は、入力値がデータではなく実行可能なテンプレートソースとして解釈される問題です。Razorにはサーバー側のC#の指示や式を記述できるため、本文を制御する攻撃者が、エンジンに公開されたオブジェクトやプロセスの権限を悪用するおそれがあります。

次の値は区別してください。

  • テンプレートソース: エンジンがコンパイル・解釈する本文です。ここに信頼できない入力を渡すと危険です。
  • テンプレートキー: 登録済みテンプレートを選ぶ識別子です。固定された信頼できるテンプレートに対応付ければ、利用者はテンプレートのコードを書けません。
  • モデルの値: テンプレートに表示するデータです。入力を本文に連結せず、モデルで渡してください。

想定される影響

  • サーバーの秘密情報やアプリケーションデータの漏えい
  • ファイル、ネットワーク、アプリケーション機能へのアクセス
  • CPUやメモリの枯渇によるサービス拒否
  • エンジンと実行環境によっては任意のコード実行

対処方法

  1. Razorの本文は、ソースコード、配布ファイル、管理者が制御する保存先だけから取得してください。
  2. 利用者の識別子を固定された信頼できるテンプレート一覧に対応付けてください。信頼できないコレクションや、信頼する設定管理の外で変更できる一覧を許可リストにしないでください。
  3. 入力はモデルの値として渡してください。最終的なHTML出力に必要なエンコードは別途適用します。
  4. 文字の削除、エスケープ、sanitizeという名前の関数だけでRazorソースが安全になると考えないでください。入力テンプレートが不可欠なら、完全な固定テンプレートの許可リストなど、判定が明確な方法を使ってください。
  5. 従来のRazorEngineのプロセス内Code Access Security(CAS)やAppDomainをセキュリティ境界として信頼しないでください。.NET 5以降では大半のCAS APIが廃止扱いとなり、何もしない呼び出しもあります。

利用者がテンプレートを書く必要がある場合は、RazorのようにC#を実行するエンジンではなく、保守されている制限型エンジンを検討してください。Scribanでは、参照先の実行制限回避を修正した7.2.6以降を使います。新しいTemplateContextに明示的なScriptObject/ScriptArrayデータと必要な関数だけを公開し、反復、再帰、出力、正規表現の時間、キャンセルの制限を設定してください。機能の多い.NETオブジェクトやファイルを読むITemplateLoaderを無制限に公開しないでください。安全なランタイムもプロセスのサンドボックスではありません。強い境界が必要なら、最小権限の別プロセスやコンテナーで実行してください。

例

変更前

csharp
using Microsoft.AspNetCore.Mvc;
using RazorLight;

async Task<string> Render(
    [FromBody] string template,
    RazorLightEngine engine)
{
    return await engine.CompileRenderStringAsync(
        "user-template",
        template, // 信頼できない入力がRazorソースとしてコンパイル・レンダリングされる
        new { Name = "Ada" });
}

変更後

csharp
using Microsoft.AspNetCore.Mvc;
using RazorLight;

async Task<string> Render(
    [FromBody] string name,
    RazorLightEngine engine)
{
    const string trustedTemplate = "Hello @Model.Name";
    return await engine.CompileRenderStringAsync(
        "welcome",
        trustedTemplate,
        new { Name = name }); // 入力はテンプレートソースではなくモデルの値
}

解説:

  • 変更前: CompileRenderStringAsyncの第2引数はテンプレートのcontentです。リクエスト本文を渡すと、呼び出し元がRazorソースを書けます。
  • 変更後: テンプレート本文は配布者が管理する定数です。リクエストの値はモデルのNameだけに渡します。

参考資料