2009-09-10
UTF-8の冗長なエンコードとは何で、なんでそれがセキュリティ的に危ないのか?を文字コード知識レヴェル3くらいの凡プログラマが考えてみる
ってあるように、いまいち文字コードの不正な判定による危険性ってのが分かってない。
SJISの問題は、第5回■注目される文字コードのセキュリティ問題 - SQLインジェクションを根絶!セキュア開発の極意:ITproの記事がわかりやすかった。
というか、やっぱりPHP使ってると誰でも一度は「なんじゃこの『¥』は?」って思うもんなんで。
なるほど、確かに↓の図のように「あるバイト」が2つの意味を持つっていう文字コード形態はやばいんだなと。

EUC-JPはそんなことはしないで、1つのバイトには1つの意味しか取らせない。

だけど、これでも文字化けが起こることがある。経験的には、「マルチバイトをXX文字で切り落としたい」とかやった場合。ちゃんと文字コードを判定してくれるPHPでいえばmb_substr何かを使えば問題ないけど、バイト数で切っちゃうsubstrだと、奇数文字で切っちゃう時は1文字が半分にちぎれる可能性がある。
んー、じゃあUTF-8は何が危ないの?
そのあたりの問題とか、他言語で文字マップ領域が被るっていう問題を解消したのがUincodeで、その実装の一つであるUTF-8なら問題ないんじゃないの〜っていう感じなんだけど、それは違うみたい。
それが書かれた記事が「本当は怖い文字コードの話:第4回 UTF-8の冗長なエンコード|gihyo.jp … 技術評論社」っていう記事。
この記事を指して最初に引用したohgakiさんのブログでは
このよう事は簡単に理解できると思っていたので
と書かれているが、これ全然簡単じゃないよ・・・って感じで色々考えてみたのがこのエントリ。(前置き長っ)
あくまで、「そうなんじゃないかな〜」って自分が考えただけなんで、間違ってたら突っ込みなどどうぞ。
そもそも、UTF-8ってどうやって文字を判別してるのか?
UTF-8は、マルチバイト1文字を表すのに、1バイト〜4バイトの可変なバイト数領域を使う。
固定にすれば良いのに、何でそんな面倒なことするんかっていうと、ASCII文字と互換性を持たせるためらしい。
で、本当は怖い文字コードの話:第4回 UTF-8の冗長なエンコード|gihyo.jp … 技術評論社によると、
| Unicode文字範囲 | UTF-8でのバイト列 |
|---|---|
| U+0000〜 U+007F | 0xxxxxxx (00〜7F) |
| U+0080〜 U+07FF | 110xxxxx 10xxxxxx (C2〜DF) (80〜BF) |
| U+0800〜 U+FFFF | 1110xxxx 10xxxxxx 10xxxxxx (E0〜EF) (80〜BF) (80〜BF) |
| U+10000〜 U+10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (F0〜F7)(80〜BF)(80〜BF)(80〜BF) |
ってなってる。
自分が、「単に1と0のデータを文字として出力するマシン」になったつもりで、「このデータはUTF-8の文字列ですよ」ってデータが来た場合に
「あ、これは『あ』だ」
「これは、『\』だ」
とか見分けるために、「何バイトで1文字を表現してるのか」を判定するにはどうしたらいいかを、↑の表を見て考えてみた。
すると、UTF-8はすこぶる簡単に「これは3バイトで1文字だな」とかが分かるようになっている。
- 最初の1バイトの1ビット目が0なら「1バイトで1文字」。
- 最初の1バイトの1ビット目と2ビット目が1なら「2バイトで1文字」なので、次の1バイトも合わせて1文字を出力する。
- 最初の1バイトの1〜3ビット目が1なら「3バイトで1文字」なので、次と次の次の2倍とも合わせて1文字を出力する。
という感じ。
つまり、最初の1バイトは「何バイト一緒に食えばいいか」というのを教えてくれてる。
そして、「10」のビットで始まるバイトは「文字の中間」を構成するバイトということになる。
ところで、上のテーブルは110xxxxx (C2〜DF)ってなってるけど、C0〜DFじゃないかな?)
冗長なエンコードとは?
じゃあ、冗長なエンコードって何?ってことなんだけど。
それは「本来なら1バイトで済むデータを3バイト必要なように見せる」っていうことだと思う。
その例として、本当は怖い文字コードの話:第4回 UTF-8の冗長なエンコード|gihyo.jp … 技術評論社によると、「/」を挙げているのだけど。
| 正しいエンコード | 0x2F |
|---|---|
| 不正なエンコード |
0xC0 0xAF (2バイト表現) 0xE0 0x80 0xAF (3バイト表現) 0xF0 0x80 0x80 0xAF (4バイト表現) |
何でこんなんが全部「/」として認識されるんだ!?って感じだけど、実際にやってみる。PHPで。
<?php echo $sl1 = hex2bin('002f')."\n"; echo $sl2 = hex2bin('c0af')."\n"; echo $sl3 = hex2bin('e080af')."\n"; function hex2bin($hex_str) { return pack("H*" , $hex_str); }
ってやってコマンドラインから実行してみる。ターミナルの出力はUTF-8。なので、ターミナルが「UTF-8である」と解釈された文字が私の目に映るはず。
果たして
というように、3つの「/」が出力された。
じゃあ、これが本当にバイナリとして同じデータなのかどうかを確かめてみる。簡単に、別の文字コードに変換してみればいい。
EUC-JPに変換して出力してみると、
ってなった。
つまり、最初の「/」は本物の1バイトのスラッシュだけど、後の2つは1バイトのスラッシュではないということ。
SJISに変換しても
となる。
これは何が悪いのかというと、それは「変なUTF-8エンコードを見抜けないターミナル」(このキャプチャの場合はTeraTermPro UTF-8版)なんだけど、世の中にはそういうアプリが多いらしい。それはブラウザも多分にもれず、FirefoxやI.Eも同じ勘違いをしてしまうらしい。
なんでそんな勘違いをするの?
じゃあ、何でそんな勘違いを?って感じだけど、それはなんとなくだけど・・・
本当は怖い文字コードの話:第4回 UTF-8の冗長なエンコード|gihyo.jp … 技術評論社にある
![]()
の絵を見て、「0xE0 0x80 0xAF」を処理するときの動作として。。。
Firefox「現在の文字コードはUTF-8。次のバイトは、0xE0 ・・・1110....とすると3バイトで1つの処理だな」
Firefox「3バイトを1つに並べてみると、1100000 10000000 10101111・・・・」
Firefox「???最初の2バイトは意味のある部分のビットが全て0・・・最後の1バイトは『0101111』これは『00101111』を表してるのかな?」
ということなんだろうか?
この辺はよーわかりませんが、なんとなくそんな感じかも。
これがXSSにつながるって言うのは
要するに、「3バイト食いますよ〜」っていうフラグを立てておきながら(つまり1100000のバイトを送る)、2バイトしか送らなかった場合、次の文字も巻き込んでしまうんじゃないかってことかな?これって、EUC-JPやSJISであったのと同じ問題で。それがUTF-8でも起きるっていう。
そうすると、HTMLの閉じタグとかクォートを食っちゃえばXSSが簡単に成立するてことで。
しかし、これを「このよう事は簡単に理解できると思っていたので、」と言っちゃうohgakiさんには
| ○ | r‐‐、
_,;ト - イ、 ∧l☆│∧ セキュリティ エバンジェリストの諸君!
(⌒` ⌒・ ¨,、,,ト.-イ/,、 l われわれPHPerにとって大切なのは
|ヽ ~~⌒γ⌒) r'⌒ `!´ `⌒) すぐ使えるサンプルか、コピペで動くコードだけだ。
│ ヽー―'^ー-' ( ⌒γ⌒~~ /| PHPerを買いかぶらないでもらいたい
│ 〉 |│ |`ー^ー― r' |
│ /───| | |/ | l ト、 |
| irー-、 ー ,} | / i
| / `X´ ヽ / 入
と、言いたい。
- 1687 http://gigazine.net/index.php?/news/comments/20090911_headline/
- 648 http://b.hatena.ne.jp/hotentry
- 505 http://reader.livedoor.com/reader/
- 288 http://b.hatena.ne.jp/hotentry/it
- 268 http://search.yahoo.co.jp/search?p=エンコードとは&search.x=1&fr=top_ga1_sa&tid=top_ga1_sa&ei=UTF-8&aq=0&oq=エンコード
- 256 http://d.hatena.ne.jp/
- 239 http://b.hatena.ne.jp/
- 206 http://www.google.co.jp/reader/view/
- 174 http://www.google.com/reader/view/
- 160 http://b.hatena.ne.jp/entry/d.hatena.ne.jp/tohokuaiki/20090910/encoding




