本当は怖い文字コードの話

第4回UTF-8の冗長なエンコード

今回は、文字コードに関連するセキュリティの話題では古参ともいえるUTF-8の冗長なエンコードというテーマについて紹介します。

UTF-8とは

UTF-8は、各文字を1~4バイトの可変長で表現するUnicodeの符号化方式のひとつです。

U+0000からU+007Fの範囲の文字を0x00から0x7Fの1バイトで表現しているため、US-ASCIIと互換性がある、バイト列の途中からでも文字の先頭バイトを簡単に検出できる、多バイト文字の途中に0x00や0x5C(\⁠⁠、0x2F(/)などが現れない、などの特徴があります。

UTF-8での文字のビットパターンは表1のようになります。

表1 UTF-8でのビットパターン
Unicode文字範囲UTF-8でのバイト列
U+0000~ U+007F0xxxxxxx
(00~7F)
U+0080~ U+07FF110xxxxx  10xxxxxx
(C2~DF⁠⁠ ⁠80~BF)
U+0800~ U+FFFF1110xxxx  10xxxxxx  10xxxxxx
(E0~EF⁠⁠ ⁠80~BF⁠⁠  ⁠80~BF)
U+10000~
U+10FFFF
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
(F0~F7)⁠80~BF)⁠80~BF)⁠80~BF)

たとえば、ひらがなの「あ」をUTF-8で表現すると、図1のように0xE3 0x81 0x82というバイト列になります。

図1 ⁠あ」のUTF-8でのエンコード方法
図1 「あ」のUTF-8でのエンコード方法

冗長なエンコードとは

先に述べたとおり、UTF-8ではU+0000からU+007Fまでの範囲の文字はUS-ASCIIと互換を持ち、0x00~0x7Fとなりますので、多くのOSでのパス区切り記号として使われる「/」⁠U+002F)は0x2Fとなります。

ところが、これを表1のU+0000~U+007F以外の欄に無理やり当てはめて、1バイト以外の形式で表現することができてしまいます表2⁠。

表2 ⁠/」のUTF-8でのエンコード
正しいエンコード0x2F
不正なエンコード0xC0 0xAF  ⁠2バイト表現)
0xE0 0x80 0xAF  ⁠3バイト表現)
0xF0 0x80 0x80 0xAF ⁠4バイト表現)

図2に、3バイトで「/」をエンコードするときのビットパターンを示しておきます。

図2 ⁠/」の不正なエンコード方法(3バイトでの表現)
図2 「/」の不正なエンコード方法(3バイトでの表現)

このように、UTF-8では特定の文字を複数の形式のバイト列で表現できるため、

  1. 処理A=UTF-8のデータ中に「/」等の文字が含まれていないか検査を行う
  2. 処理B=処理AからUTF-8のデータを受け取り、UTF-16等に順次解釈しながら処理する

のような流れのときに、処理Aが冗長なUTF-8を意識せずに検査していると、UTF-16に変換したデータ中に処理Aでフィルタリングされるべき文字が含まれてしまうことになります。

このような問題が発生することを防ぐため、現在のUnicode仕様ではバイト数が最小になるもの以外は不正なバイト列であるとして、UTF-8として解釈することを明確に禁止しています。

これまでにこのUTF-8の冗長なエンコードの問題の影響をもっとも大きく受けたのは、おそらく2001年のNimdaウイルスによる被害のときでしょう。Nimdaウイルスは複数の感染経路を持っていましたが、そのうちの1つがIISの冗長なUTF-8のリクエストによるパストラバーサル(実際には、当時すでにMS00-057というパッチが提供されていましたが)でした。

対策

現在のほとんどのOSやライブラリ、フレームワークなどのミドルウェアでは、このような冗長なUTF-8表現は禁止されていると考えられます。そのため、冗長なUTF-8による検査の漏れを防ぐもっとも最善の方法は、UTF-8の検査や他の符号化形式への変換をライブラリやフレームワークに任せ、「自前でUTF-8を処理しない」ということに尽きます。

また「CVE-2008-2938: Apache Tomcat におけるディレクトリトラバーサルの脆弱性」のように、比較的最近でもUTF-8の冗長なエンコーディングによる問題は発生していますが、当然ながらこういった問題は個々のWebアプリケーションではなくフレームワーク側で修正すべき問題ですので、使用するフレームワーク等に問題があることが事前に判明しているのではない限り、自前でUTF-8の検査や他の符号化形式への変換を行うのではなくフレームワーク等に任せるべきです。

UTF-8からUTF-16などへの変換は、比較的簡単にビット操作などで書くことができてしまうため、つい自前で実装してしまいたくなりますが、よほどの理由がない限りそういった処理はライブラリやフレームワークなどの信頼できる処理系に任せるべきでしょう。

おすすめ記事

記事・ニュース一覧