説明
.NETの複合書式は、{index[,alignment][:formatString]}という形式の項目を解釈します。渡された引数のどれを何回表示するか、配置や値ごとの書式を決めます。外部入力をString.Format、StringBuilder.AppendFormat、TextWriter.Write・WriteLine、Console.Write・WriteLineの複合書式引数に使うと、攻撃者が出力構造を制御できます。
Cのprintfの書式文字列脆弱性とは影響が異なります。マネージド.NETの複合書式には%nのようなメモリ書き込み指示がなく、呼び出しスタックの任意の値も読みません。C/C++の任意メモリの読み書きや直接的なコード実行を、そのままこの問題の影響としないでください。
想定される影響
- インデックスの変更や繰り返しによって、同じ呼び出しに渡した他の引数を表示される可能性があります。機密の値を渡していれば漏えいにつながります。
- 閉じていない波括弧、範囲外のインデックス、不正な書式項目は
FormatExceptionを起こします。未処理ならリクエストの失敗や継続的なサービス障害につながります。 - 極端に大きい配置幅は出力やメモリ使用量を増やします。独自の
IFormatProviderやICustomFormatterを使う場合、アプリケーション固有の書式処理を選ばれる可能性もあります。 - ログ、応答、通知を改変されると、それを使うセキュリティ判断や運用で、パーサーや担当者が誤認する可能性があります。
対処方法
- 複合書式の構造をコードで管理するリテラルに固定し、信頼できない文字列は値の引数として渡してください。開発者が書いた文字列補間も、表示の構造を入力値から分離できます。
- 生の書式文字列を受け取らず、
compactやlabelledなどの識別子を変更不可のリテラル書式に対応付けてください。 - 生の書式が必要なら、少数の固定リテラルとの完全一致だけを許可してください。渡す値の種類と出力サイズも制限します。接頭辞や部分文字列の確認では、書式全体を制限できません。
CompositeFormat.Parseは文法を検証して結果をキャッシュしますが、有効な書式の中で引数や表示方法を選ばれる問題は残ります。安全性を保証する機能として扱わないでください。Sanitizeという名前のヘルパーや、片方の波括弧だけを除去する変換では安全性を確認できません。開き・閉じ両方の波括弧を除去すれば書式構文はなくなりますが、元の文字列を壊すため一般的な対策には向きません。
例
変更前
csharp
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("format")]
public sealed class FormatController : ControllerBase
{
[HttpGet]
public string Render(
[FromQuery] string format,
[FromQuery] string displayName)
{
string internalReference = HttpContext.TraceIdentifier;
return string.Format(format, displayName, internalReference);
}
}
formatに{1}を渡すと、表示する意図のないinternalReferenceの文字列表現が含まれる可能性があります。不正な波括弧や存在しない引数のインデックスはFormatExceptionを起こします。
変更後
リテラルの書式
csharp
using System.Globalization;
return string.Format(
CultureInfo.InvariantCulture,
"사용자: {0}",
displayName);
書式の構造とカルチャープロバイダーはアプリケーションが決め、入力は値としてだけ使います。Console.WriteLine(message)やTextWriter.Write(message)の単一文字列オーバーロードは、その値を複合書式として解釈しません。
定義済みの書式を選択
csharp
using System.Globalization;
static string SelectFormat(string style) => style switch
{
"compact" => "{0}",
"labelled" => "사용자: {0}",
_ => "사용자: {0}"
};
string format = SelectFormat(style);
return string.Format(CultureInfo.InvariantCulture, format, displayName);
利用者が選ぶのは限定した識別子であり、書式文字列そのものではありません。すべての分岐で開発者が書いたリテラルを返します。
固定の書式でも、引数の個数・型との一致と、独自フォーマッターの動作を確認してください。.NETの解析規則CA2241とテストを併用できます。
参考資料
- Microsoft: .NETの複合書式指定
- Microsoft:
String.Format(.NET 10) - Microsoft:
StringBuilder.AppendFormat(.NET 10) - Microsoft:
TextWriter(.NET 10) - Microsoft:
Console.WriteLine(.NET 10) - Microsoft:
CompositeFormat(.NET 10) - Microsoft:
CompositeFormat.Parse(.NET 10) - Microsoft コード分析規則CA2241
- CWE-134: 外部で制御される書式文字列の使用