ログ偽造

C#のMicrosoft.Extensions.Loggingでのログ偽造(CWE-117)

説明

信頼できないデータにログ形式で特別な意味を持つ文字が含まれると、ログ偽造(ログインジェクション)が起こる可能性があります。行単位のテキストログでは、CR、LFなどの改行文字によって、別のイベントに見える行を作られます。区切り文字を使うログ、端末、HTMLビューアー、自動パーサーにも、構造や表示を変える区切り文字や制御文字があります。

Microsoft.Extensions.Loggingのメッセージテンプレートと名前付きプロパティは、構造や検索性の維持に役立ちます。ただし、すべてのプロバイダーが値の改行を無害化するとは限りません。最終的なプロバイダー、転送形式、収集処理、ビューアーを合わせて確認してください。

想定される影響

  • 存在しない成功・失敗のイベントを挿入され、実際の操作を隠される可能性があります。
  • 監査ログの完全性や操作の証跡が損なわれ、調査や警告の判断を誤るおそれがあります。
  • レコードやフィールドの破損により、イベントの見落とし、処理失敗、後続のインジェクションが起こる可能性があります。
  • 長さを制限しない入力によって、保存領域や収集処理の容量を使い果たす可能性があります。

対処方法

出力形式に合わせてエンコード

可能なら、プロバイダーが構造化した形式を生成し、値をその形式に合わせてエンコードするように設定してください。例えば.NETのJSONコンソールフォーマッターは、Utf8JsonWriterで文字列を書き込みます。AddJsonConsoleを使っただけで判断せず、本番で有効なすべてのプロバイダーと変換処理を確認し、不正な文字を含む実際の出力をテストしてください。

メッセージテンプレートは定数にし、信頼できない値は名前付きプロパティとして渡してください。構造化ログやCA2254への対応に役立ちますが、行単位の出力でCR/LFを無害化する処理の代わりにはなりません。

テキストログの改行を無害化

.NET 6以降では、String.ReplaceLineEndings(" ")で認識される改行を空白に置き換えられます。CR、LF、CRLFのほか、NEL、LS、FF、PSも対象です。引数のないReplaceLineEndings()は実行環境の改行に正規化するだけで、改行自体は除去しません。

改行の置換は、行単位のテキストログのレコード分離を防ぐ対策です。CSVなどの区切り形式、端末の制御文字、HTMLビューアーには、それぞれの最終出力に合ったエンコードを追加してください。Sanitizeなどのヘルパー名だけでは、ログに適した処理か判断できません。

値がLOGIN、LOGOUTなどに限定される場合は、変更不可のリテラルの許可リストで検証できます。外部設定や利用者の値を含む一覧、検証後に変更された値、実行中に変わるコレクションでは同じ保証は得られません。入力長も制限し、パスワード、トークン、暗号鍵は記録しないでください。

例

変更前

csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

[ApiController]
[Route("audit")]
public sealed class VulnerableAuditController : ControllerBase
{
    private readonly ILogger<VulnerableAuditController> _logger;

    public VulnerableAuditController(ILogger<VulnerableAuditController> logger)
    {
        _logger = logger;
    }

    [HttpPost]
    public IActionResult Login([FromQuery] string user)
    {
        _logger.LogWarning("Failed login for {User}", user);
        return Ok();
    }
}

userにテキストログの新しいレコードを作る区切り文字が含まれていても、そのまま渡します。

変更後

csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

[ApiController]
[Route("audit")]
public sealed class SafeAuditController : ControllerBase
{
    private readonly ILogger<SafeAuditController> _logger;

    public SafeAuditController(ILogger<SafeAuditController> logger)
    {
        _logger = logger;
    }

    [HttpPost]
    public IActionResult Login([FromQuery] string user)
    {
        var safeUser = user.ReplaceLineEndings(" ");
        _logger.LogWarning("Failed login for {User}", safeUser);
        return Ok();
    }
}

改行を空白に置き換えます。実際のプロバイダーと後続の出力形式に必要な追加のエンコードも確認してください。

参考資料