MSC10-C. 文字の符号化 - UTF8 に関連する問題
UTF-8 は Unicode を表すための可変長の符号表現である。UTF-8 は、Unicode シンボルにもよるが、1文字につき1バイトから4バイトを使用する。UTF-8 には次のような性質がある。
- US-ASCII 文字(0 から 0x7f)はそのまま符号表現として扱われるため、ASCII 値で符号化されたファイルや文字列は、ASCII でも UTF-8 でも同一の符号表現となる。
- UTF-8 と固定幅文字表現である UCS-2、および UCS-4 の間での文字の変換は容易に行うことができる。
- UCS-4 文字列の辞書順の並びは維持される。
- UTF-8 を使って、2の21乗個の全ての UCS コードを符号化できる。
一般に、プログラムはまず UTF-8 データの妥当性を検証した後に、他の検査を行うべきである。有効な UTF-8 バイト列を以下の表に示す。
コードポイント | 1バイト目 | 2バイト目 | 3バイト目 | 4バイト目 |
---|---|---|---|---|
U+0000..U+007F | 00..7F | |||
U+0080..U+07FF | C2..DF | 80..BF | ||
U+0800..U+0FFF | E0 | A0..BF | 80..BF | |
U+1000..U+CFFF | E1..EC | 80..BF | 80..BF | |
U+D000..U+D7FF | ED | 80..9F | 80..BF | |
U+E000..U+FFFF | EE..EF | 80..BF | 80..BF | |
U+10000..U+3FFFF | F0 | 90..BF | 80..BF | 80..BF |
U+40000..U+FFFFF | F1..F3 | 80..BF | 80..BF | 80..BF |
U+100000..U+10FFFF | F4 | 80..8F | 80..BF | 80..BF |
UTF-8 は元々 Plan 9 の開発者によって作成されたが [Pike 93]、Plan 9 自身は文字セットの下位16ビットの範囲だけをサポートしている。一般に、"Unicode" システムの多くは、21 ビットの ISO 10646 コード領域全体ではなく、下位16ビットの範囲だけをサポートしている [ISO/IEC 10646:2012]。
セキュリティに関する問題
[Yergeau 98] には以下のように書かれている。
UTF-8 の実装者は、無効な UTF-8 シーケンスの取扱いに関してセキュリティ面への影響を考慮する必要がある。攻撃者が、UTF-8の構文上認められないオクテットの並びを使って UTF-8 パーサを攻撃するというのは大いにあり得る話だ。
UTF-8 符号化形式の入力に対するセキュリティチェックを行うパーサに、巧妙な形式のデータを渡すことにより、特定の無効なオクテットの並びを文字として解釈させる攻撃が考えられる。たとえば、1オクテットの並び 00 として符号化された null 文字は拒否するが、無効な2オクテットの並び C0 80 として符号化された null 文字は許可してしまうかもしれない。あるいは、2F 2E 2E 2F ("/../") というオクテットの並びは拒否するが、2F C0 AE 2E 2F という無効なオクテットの並びは許可してしまうという例も考えられる。
推奨される具体的な対策を以下に示す。
最短形式だけ受け付ける
UTF-8 の最短形式のみ許可すること。必要以上に長い符号化表現を許可してしまうデコーダは、潜在的に危険な入力の複数の表現に対応できないかもしれない。例えば次のような例が考えられる。
- プロセス A はセキュリティ検査を実施するが、非最短形式の UTF-8 かどうかの検査を行わない。
- プロセス B はプロセス A からバイト列を受け取り UTF-16 に変換する。この際、バイト列は最短形式でない可能性がある。
- UTF-16 テキストには、プロセス A で排除されるべき潜在的に危険な文字が含まれる可能性がある。このような UTF-8 の非最短形式表現を使った攻撃は、Microsoft IIS Web サーバなどの製品においてセキュリティ上の検証を迂回するために使われた。
Unicode 規格のCorrigendum #1:「UTF-8 最短形式」[Unicode 2006] では、Unicode規格バージョン3.0に対する変更として、非最短形式の解釈を禁止している。
無効な入力の処理
UTF-8 デコーダが無効な入力に遭遇したときの動作は一律には定められていない。無効なバイトシーケンスに遭遇した際に UTF-8 デコーダが取る動作の例を以下に示す。なお、これらの動作を実装する際にはセキュリティを考慮する必要があることに注意。
- 代替文字"U+FFFD"に置き換える。代替文字が利用出来ない場合は "?" などの「ワイルドカード」文字に置き換える。
- そのバイトを無視する(たとえば、検証前に無効なバイトを削除するなど。詳しくはUnicode Technical Report #36, 3.5 Deletion of Code Points を参照)。
- 別の文字コードとしてバイトを解釈する(ISO-8859-1 を使うことが多い。他のエンコーディング、たとえば Shift_JIS、ではクロスサイトスクリプティングを引き起こす可能性があり潜在的に危険であることが知られている)。
- 無効かどうかは関知せず UTF-8 文字列であるとしてデコードする。
- デコードを中止し、エラーを報告する。
John Viega の"Protecting Sensitive Data in Memory" [Viega 2003] から引用した以下の関数は、文字列中の無効な文字の並びを検出するが、非最短形式であっても拒否しない。文字列が正当な並びで構成されている場合は 1 を返し、それ以外の場合は 0 を返す。
int spc_utf8_isvalid(const unsigned char *input) { int nb; const unsigned char *c = input; for (c = input; *c; c += (nb + 1)) { if (!(*c & 0x80)) nb = 0; else if ((*c & 0xc0) == 0x80) return 0; else if ((*c & 0xe0) == 0xc0) nb = 1; else if ((*c & 0xf0) == 0xe0) nb = 2; else if ((*c & 0xf8) == 0xf0) nb = 3; else if ((*c & 0xfc) == 0xf8) nb = 4; else if ((*c & 0xfe) == 0xfc) nb = 5; while (nb-- > 0) if ((*(c + nb) & 0xc0) != 0x80) return 0; } return 1; }
サロゲートの破損
個別のサロゲート文字や、いずれかが破損しているサロゲート文字列の符号化は禁止しなければならない。破損したサロゲート文字列は Unicode データとして無効であり、Unicode データに含まれた場合にあいまいさを招く。破損したサロゲート文字列は、データ転送の不良やアプリケーションの内部バグを示す場合が多い。また、脆弱性検査のために意図的に使われることもある。
リスク評価
UTF-8 で符号化されたデータを適切に処理しないと、データの完全性が失われたり、サービス運用妨害攻撃が引き起こされることがある。
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
MSC10-C |
中 |
低 |
高 |
P2 |
L3 |
自動検出
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
LDRA tool suite |
V. 8.5.4 |
176 S |
実装済み |
関連ガイドライン
- CERT C++ Secure Coding Standard: MSC10-CPP. Character Encoding - UTF8 Related Issues
- ISO/IEC TR 24772 "AJN Choice of filenames and other external identifiers"
- MITRE CWE: CWE-176, "Failure to handle Unicode encoding" and CWE-116, "Improper encoding or escaping of output"
参考情報
- [ISO/IEC 10646:2003]
- [ISO/IEC 10646:2012]
- [Kuhn 2006]
- [Pike 1993]
- [Unicode 2006]
- [Viega 2003] Section 3.12, "Detecting illegal UTF-8 characters"
- [Wheeler 2003]
- [Yergeau 1998]
翻訳元
これは以下のページを翻訳したものです。
MSC10-C. Character Encoding - UTF8 Related Issues (revision 65)