リソースインジェクション

C#のデータベースリソースインジェクション(CWE-99)

説明

信頼できない値によって、アプリケーションが使うリソースの識別子や接続情報を選ばれると、リソースインジェクションが発生します。.NETのデータアクセスでは、接続文字列や構成要素が次の情報を決めます。

  • SQL Server、PostgreSQL、MySQLのサーバーやネットワークアドレス
  • データベース名やカタログ名
  • SQLiteのファイルパス
  • ODBCのDSN・ドライバー、OLE DBのプロバイダー
  • ユーザー名、パスワード、認証方式

SQL構文を変えなくても、意図しないサーバー、データベース、ローカルファイルに接続させられます。SQLインジェクションとは別の問題で、実際の影響は構成した接続やデータソースを開いたり使ったりするときに生じます。

SqlConnectionStringBuilderやNpgsqlConnectionStringBuilderは引用符や区切り文字を正しくシリアル化しますが、選んだホスト、データベース、ファイル、認証情報の利用権限は確認しません。例えばDataSource = userInputは区切り文字の注入を防いでも、攻撃者が接続先サーバーを選ぶ問題は残ります。

想定される影響

  • 攻撃者のサーバーへクエリ、データ、認証情報を送られる可能性があります。
  • より強い権限のデータベースや他のテナントの保存先を選ばれ、アクセス制御を回避される可能性があります。
  • SQLiteのパス、DSN、ドライバーを通じ、意図しないローカルファイルやプロバイダーを使われる可能性があります。
  • 接続失敗、長い待機、接続プールの枯渇を繰り返され、可用性が低下する可能性があります。

対処方法

接続情報をサーバー側で管理

完全な接続文字列は、サーバーが管理する設定やシークレットの保存先から取得してください。パスワードやトークンをソースコード、URL、ログに入れないでください。デプロイ用の環境変数では、変更できる主体を制限し、シークレット管理方針を適用してください。

入力をそのまま設定キーに使わないでください。configuration[userInput]では、値の保存先が適切でも、想定外のキーを選ばれる可能性があります。

論理識別子の権限を確認して対応付け

リソースを選ばせる場合は、primary、reportingなど意味を限定した識別子だけを受け取ってください。呼び出し元の利用権限を確認してから、コードが管理する固定マップで、信頼した設定名や接続情報に変換します。マップの内容と変更権限も管理してください。

個別の構成要素を動的に指定する場合は、各値を業務規則で検証してから、プロバイダーのConnectionStringBuilderに渡してください。ビルダーは検証後の構文を正しく組み立てるもので、検証や認可の代わりにはなりません。Sanitize、Encrypt、Hashというヘルパー名だけで判断せず、実際の処理を確認してください。

サポートされたプロバイダーと安全な認証を使用

  • 新しいSQL Serverのコードには、非推奨のSystem.Data.SqlClientではなくMicrosoft.Data.SqlClientを使ってください。
  • 対応する環境では、パスワードよりマネージドID、統合認証、短期アクセストークンを優先してください。
  • データベースアカウントを最小権限にし、テナントや業務の境界も別途適用してください。
  • 通信の暗号化とサーバー証明書の検証を有効にしてください。接続文字列ビルダーだけではTLSを保証できません。
  • SQLiteでは、ファイル名を固定マップで選び、正規化後のパスが専用の基準ディレクトリ内にあるかも確認してください。

例

変更前

csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;

[ApiController]
[Route("database")]
public sealed class VulnerableDatabaseController : ControllerBase
{
    [HttpPost("connect")]
    public IActionResult Connect([FromQuery] string connectionString)
    {
        using var connection = new SqlConnection(connectionString);
        connection.Open();
        return Ok();
    }
}

呼び出し元が接続文字列全体を指定するため、サーバー、データベース、認証方式などをまとめて変更できます。

変更後

csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;

[ApiController]
[Route("database")]
public sealed class SafeDatabaseController : ControllerBase
{
    private readonly IConfiguration _configuration;

    public SafeDatabaseController(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    [HttpPost("connect")]
    public IActionResult Connect([FromQuery] string databaseId)
    {
        string? connectionName = databaseId switch
        {
            "primary" => "PrimaryDatabase",
            "reporting" when User.IsInRole("ReportReader") => "ReportingDatabase",
            _ => null
        };
        if (connectionName is null)
        {
            return Forbid();
        }

        string? connectionString =
            _configuration.GetConnectionString(connectionName);
        if (string.IsNullOrWhiteSpace(connectionString))
        {
            return Problem("Database configuration is unavailable.");
        }

        using var connection = new SqlConnection(connectionString);
        connection.Open();
        return Ok();
    }
}

変更後は識別子を限定し、reportingにはロールの確認を加えます。入力を設定キーとして直接検索せず、接続文字列はサーバー側の設定だけから取得します。本番では接続を試すためのエンドポイントを公開せず、実際の業務処理に同じ選択・認可の方針を適用してください。

参考資料