C#コードインジェクション

信頼できないC#コード文字列の実行またはコンパイル

説明

外部入力がデータではなく、実行可能なプログラムの構造として解釈されると、コードインジェクションが発生します。信頼できない文字列をRoslynスクリプト、CS-Script、動的コンパイルAPIに渡すと、評価や実行の際にアプリケーションの権限でファイル、ネットワーク、環境変数、プロセスへアクセスされる可能性があります。

EvaluateAsync、RunAsync、Eval、LoadCode、LoadMethodなどはコードを評価またはロードします。CSharpScript.Create、CompileCode、CompileAssemblyFromSourceは、その場では実行しなくても、後でロードや呼び出しが可能なスクリプトやアセンブリを生成します。どちらもコードの信頼性を確認する必要があります。

想定される影響

  • アプリケーションの権限で任意のコードやコマンドを実行される可能性があります。
  • ファイルやデータベースの機密性、完全性、可用性が損なわれる可能性があります。
  • 内部ネットワークへのアクセス、認証情報の窃取、権限昇格、サービス拒否につながる可能性があります。

対処方法

信頼できない入力をC#コードとして解釈しないでください。

  1. 入力は操作の識別子として受け取り、add、subtractなどの許可した値を固定のアプリケーション関数に対応付けてください。
  2. 式が必要な場合は、C#全体を制限しようとせず、許可する構文と演算を明示した専用の文法を解析してください。入力長、構文の深さ、実行時間、メモリも制限します。
  3. 既存の連携でスクリプトを選ぶ必要がある場合は、開発者が管理する変更不可の一覧から完全なスクリプトを選んでください。コードの断片に利用者の入力を連結しないでください。
  4. 信頼できないC#の実行が不可欠な場合は、アプリケーションと別のプロセスで実行してください。制限したアカウントのプロセス、コンテナー、仮想マシンを使い、OS側でファイル、ネットワーク、プロセス権限、CPU時間、メモリを制限します。

ScriptOptionsで参照やimportを減らしたり、unsafeを禁止したりするだけでは、セキュリティ境界にはなりません。C#と.NETのAPI全体を完全に制限するのは難しく、参照の制限だけでは無限ループやメモリ枯渇を防げません。

CodeDomProvider.CompileAssemblyFromSourceは従来の.NET Framework向けAPIです。Microsoftの文書では、.NET Coreと.NET 5以降では常にPlatformNotSupportedExceptionが発生するとされています。現行.NETでの対策や新しい実装例には使わないでください。

例

変更前

HTTPリクエストの本文をそのままC#スクリプトとして評価します。

csharp
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.CSharp.Scripting;

[ApiController]
[Route("api/scripts")]
public class ScriptController : ControllerBase
{
    [HttpPost]
    public Task<object> Run([FromBody] string code)
    {
        return CSharpScript.EvaluateAsync(code);
    }
}

変更後

入力は実行するコードではなく、固定の操作を選ぶデータとして扱います。

csharp
using Microsoft.AspNetCore.Mvc;

public record CalculationRequest(string Operation, decimal Left, decimal Right);

[ApiController]
[Route("api/calculations")]
public class CalculationController : ControllerBase
{
    [HttpPost]
    public IActionResult Calculate([FromBody] CalculationRequest request)
    {
        return request.Operation switch
        {
            "add" => Ok(request.Left + request.Right),
            "subtract" => Ok(request.Left - request.Right),
            _ => BadRequest("Unsupported operation")
        };
    }
}

参考資料