第9章 ファイル対策
ファイルの別名検査
入力パラメータとしてファイル名またはパス名を扱う場合、細工されたファイル名が与えられて、所定のディレクトリの範囲外のファイルへユーザが不正にアクセスするおそれがある。
ひとつのファイルを識別するための名前──ファイル名、あるいはファイル名がディレクトリ修飾されたパス名──には異なる表現が複数通りあり得、攻撃者はそうした「別名」を使って予定外のファイルにアクセスしてくる。ファイル名の入力検査においては受け入れるべきでないパス名のパターンをすべて排除する必要がある。
ファイル名・パス名の一般的特性
(1) 通常のディレクトリ修飾
ファイルを特定する方法には、ディレクトリ修飾をする方法とファイル名のみでの方法の2種類がある。
1) 絶対パス(パス名の先頭が 「/」で始まる)
"/etc/passwd" "/bin/ls"
2) カレントディレクトリ(パス名の先頭が 「./」 または、「/」 を含まない)
"passwd" "ls"
(2) 特別なディレクトリ修飾
ファイルを特定するパス名のディレクトリ修飾には、上記(1)の通常の指定方法のほかにカレントディレクトリからの相対パスによって修飾する方法がある。
1) カレントディレクトリ以下にあるファイルやディレクトリを示す修飾
(カレントディレクトリを示す「./」を含む)
"./passwd" "./etc/passwd"
2) カレントディレクトリからの相対パスによる修飾
(ひとつ上のディレクトリを示す 「../」 を含む)
"../../etc/passwd" "foo/../../../etc/passwd"
(3) ディレクトリトラバーサル攻撃
パス名にディレクトリ修飾を許す場合、上記(2)のような相対パスが混入されて引きおこされる
ディレクトリトラバーサル攻撃を防ぐためにも、パス名の正規化チェックが必要となる。
※ディレクトリトラバーサル攻撃
ファイル名を受け取り処理するプログラムにおいて、相対パスを混入して、意図しないファイルを読み出したり、既存のファイルを破壊したりする攻撃。

Windowsにおけるファイル名とパス名の特性
Windowsにおけるファイル名とパス名には、上記ファイル名・パス名の一般的特性の(1)および(2)にあるようなディレクトリ修飾に加えて、次のような特性がある。
(1) 8.3形式のショートネーム
8.3形式のショートネームは、8byteの名前と3byteの拡張子からなるファイル名である。
Windows95以降では、255byte(UNIXの場合は256byte)までのファイル名が使用可能になったが、以前の古いソフトウェアとの互換性を考慮して、OS内部では自動的に生成した8文字の名前を保持している。
2006/11/10 11:56 370 foo1.txt
2006/11/10 11:57 426 LONG_F ̄1.TXT LONG_FILE_NAME.TXT
↑8.3形式 ↑ロングネーム
8.3形式のショートネームのパス名からロングネームのパス名を取得する Win32 API には、GetLongPathName が用意されている。
(2) ファイル名末尾の「.」や半角スペース
ファイル名の末尾の「.」や半角スペースは、省かれるため、下記の表記はいずれも同じ実体のファイル 「foo2.txt」として扱われる。
1. "foo2.txt." 2. "foo2.txt " 3. "foo2.txt. . ."
(3) Windows/ファイルのストリーム
Windows のNTFSでは、ファイル名中の「:」はNTFSファイルの「ストリーム」を表す区切り記号として意味をもつ。
ストリームを使うと、あるファイルの付随するデータをもたせることができる。
次の cygwin 環境の例では、最初に foo.txt を作成し付随するデータをストリーム stm1, stm2にもたせている。
$ echo hello > foo.txt ← foo.txt を作成
$ ls -l foo.txt
-rw-r--r-- 7 Nov 10 13:48 foo.txt ← ファイルサイズは 7 byte
$
$ echo "Streem TEST." > foo.txt:stm1 ← foo.txtにストリームstm1を追加
$ echo "Streem TEST2." > foo.txt:stm2 ← foo.txtにストリームstm2を追加
$
$ cat foo.txt
hello
$ cat foo.txt:stm1
Streem TEST.
$ cat foo.txt:stm2
Streem TEST2.
$
$ ls -l foo.txt
-rw-r--r-- 7 Nov 10 13:50 foo.txt ← ファイルサイズは 7 byte のまま
日付のみ更新されたように見える
Unix, GNU/Linux におけるパス名検査
(1) パス名をチェックする
プログラムのアクセスするディレクトリを制限させるには、下記のようにパス名をチェックする対策を行う必要がある。
- 相対パス、絶対パスを受け付けないようにする場合
パス名に相対パス、絶対パスを受け付けないようにするには、パス名の文字列中に「/」が無いことをチェックすれば良い。この場合、カレントディレクトリの「./」も受け付けられなくなってしまうが、以下の対策 2、3 は不要となる。 - 相対パスを受け付けないようにする場合
パス名の文字列に相対パスを意味する「../」が含まれないことをチェックする。 - 絶対パスを受け付けないようにする場合
パス名の文字列の先頭が絶対パスを意味する「/」でないことチェックする。 - 相対パスを受け付けるようにする場合
相対パスを受け付ける場合、パス名に realpath(3) を使用し相対パスから絶対パスへの形式に変換し、プログラムが許可するパスであるかをチェックする。
(2) シンボリックリンクをチェックする
扱うファイル名にシンボリックリンクを許す場合とそうでない場合がある。いずれの場合も、ファイル名が指し示すファイルの実態が、所定のものであることを確認するロジックが必要である。
シンボリックリンクに関する話題は『シンボリックリンク攻撃対策』を見られたい。
Windows におけるパス名検査
Windows の場合も上記Unix, GNU/Linux におけるパス名検査の(1) 1 〜 4の方法が有効である。ただし、次の点を考慮に入れる。
- ファイル名の大文字と小文字の区別がない
- パスの先頭に「C:\」等のドライブ名が付けられる
- ディレクトリの区切り文字「\」と「/」は同じ意味をもつ
- 8.3形式のショートネームが別名にある
- ファイル名末尾の「.」、半角スペースは無視される
- 予約されたデバイス名(AUX等)がファイルのようにアクセスできる
- ファイルに後ろに「:」を付けてストリームとして使用できる
(1) パス名解釈における拡張機能の不活性化
多くの自由度をもつWin32のパス名を相手にするには複雑なロジックのプログラムが必要になるが、パス名の解釈に伴う「拡張機能」の多くを不活性化する特殊な指定方法がある。それはパス名の先頭に\\?\という4文字を付け加えることである。例えば、
pfile = fopen ("d:\\dir\\data.txt", "r");
の代わりに
pfile = fopen ("\\\\?\\d:\\dir\\data.txt", "r");
のようにするのである。
\\?\から始まるパス名については拡張機能が次のようにはたらかなくなる。
- ディレクトリ区切り文字「/」が使用できない
- ディレクトリ区切り文字の重複「\\」(C/C++の文字列定数内では「\\\\」)が許されない
- パス名の途中に \.\ を差し挟めない
- パス名の途中に \..\ を差し挟めない
- パス名の末尾の「.」や半角スペースが無視されない
(2) 予約デバイス名の回避
Windowsのファイル名には、下記のような予約されたデバイス名がある。パス名の構成要素──ファイル名あるいはディレクトリ名──のひとつでもこれらの綴りと一致していると、そのパス名はデバイスを表すものと解釈され、ファイルではなく、デバイスがオープンされ、プログラムは限りなく入力待ちになる。したがって、これらの綴りがパス名の構成要素に現れていないことを検査する必要がある。
AUX, CON, NUL, PRN, COM1〜COM9, LPT1〜LPT9
なお、AUX.txt のように拡張子がついた綴りもデバイス名と解釈されるので注意が必要である。
まとめ
通常のディレクトリ修飾やファイル名のみでの指定を期待していると、相対パス修飾による想定していないファイルへの不正アクセスを許してしまうおそれがある。これは、ファイル名の表現が複数通りあり得ることが原因である。
対策としては、このようなファイル名の「別名」を指定できないように排除する必要がある。また、Windowsでは、Windowsならではの特性もあるので、この特性にも注意を向ける必要がある。