第9章 ファイル対策
シンボリックリンク攻撃対策
Unix系およびGNU/Linux系のシステムでは、シンボリックリンク機能を悪用して重要ファイルを改ざんしたり秘密ファイルを漏えいさせる「シンボリックリンク攻撃」が起こり得る。
シンボリックリンク
シンボリックリンクは、ファイルやディレクトリに別名を持たせる特殊なディレクトリエントリである。シンボリックリンクはその内部にひとつのパス名を記憶している。通常の入出力APIを用い、プログラムがシンボリックリンクにアクセスしようとすると、実際にはそこに記憶されているパス名が表す対象へのアクセスが行われる。
シンボリックリンク作成への制限は厳しくなく、次のような特徴がある。
- シンボリックリンクを配置するディレクトリへの書き込み権限があるユーザなら誰でもシンボリックリンクを作成可能
- シンボリックリンク先の実体ファイル/ディレクトリにアクセスパーミッションを持たないユーザによる作成が可能
- シンボリックリンク先の実体ファイル/ディレクトリがまだ存在しなくても作成が可能
シンボリックリンク攻撃
いわゆる「シンボリックリンク攻撃」は、プログラムによるファイルへのアクセスに先回りしてシンボリックリンクを設定することによって、プログラムがオープンしようとするパス名の実体をシンボリックリンクにすり替えておき、想定外のファイルへのアクセスを起こさせる攻撃である。
特に、高い権限で動作するプログラムがアクセスするファイルがシンボリックリンクにすり替えられると、システムの重要なファイルが改ざんされたり破壊されるおそれがある。また、読み出しのアクセスにシンボリックリンク攻撃が仕組まれて秘密ファイルの内容が漏えいすることもあり得る。

上で述べたように、シンボリックリンクの作成に関する制限はかなり緩い。一般ユーザの書き込みパーミッションが与えられているディレクトリであればどこでも、ローカルユーザ(システムにログインしてシェルを使っているユーザ)がシンボリックリンク攻撃を仕掛けることができる。
その典型的な例は /tmp である。/tmp に気軽にテンポラリファイルを置くことは避けた方がよい。管理者権限(root等)で動作させるプログラムについては特に注意が必要である。
シンボリックリンク攻撃対策
可能であれば、オープンするファイルのパス名としてシンボリックリンクを受け入れない方針でソフトウェアを構築する。
シンボリックリンクのパス名の受け入れが避けられない場合は、そのパス名が指す実体に関する確認処理を行い、意図したファイル以外へのアクセスを未然に防ぐロジックをもつ必要がある。
(1) シンボリックリンクを受け入れないようにする場合
プログラムからファイルをオープンする場合、シンボリックリンクを受け入れないようにするにためは、ファイルオープンの前にシステムコール lstat() によりチェックすることができる。
1 #include <sys/stat.h> 2 3 ... 4 struct stat lstat_result; 5 int err; 6 7 if (lstat(pathname, &lstat_result) != 0) 8 return ERROR; // lstat()失敗 9 10 if ((lstat_result.st_mode & S_IFMT) == S_IFLNK) { 11 // pathname はシンボリックリンクである 12 ... 13 } 14 ...
この10行目は、用意されているマクロを使って次のようにも書ける。
10 if (S_ISLNK(lstat_result.st_mode)) {
※注意
ここで注意すべきことは、上記のコードのみでは対策として不十分であるということである。
マルチプロセス/マルチスレッドのシステムでは、ひとつのプログラムが lstat() でパス名がシンボリックリンクでないことを確認してから open() でそのパス名をオープンするまでの僅かの間に、別の存在によってディレクトリエントリの「すり替え」が起こり得る。
この問題への対処については、次の記事『ファイルレースコンディション対策』を見られたい。
(2) シンボリックリンクを受け入れる必要がある場合
ソフトウェアの仕様上シンボリックリンクを受け入れる必要がある場合は、与えられたパス名が指す対象を、シンボリックリンクが一切含まれない表現になるように変換し、変換後のパス名の正当性を検査する。
パス名が指す対象の、シンボリックリンクが含まれない表現を取得するには、ライブラリ関数の realpath() が利用できる。
1 #include <sys/param.h> 2 #include <stdlib.h> 3 4 ... 5 char result[PATH_MAX]; 6 result = realpath(pathname, result); 7 if (result == NULL) 8 return ERROR; // realpath()失敗 9 10 // ここでresultのパス名の妥当性を調べる 11 ...
realpath() 関数は、pathname に含まれる特殊な表現「./」や「../」およびシンボリックリンクを解決し、これらの表現を含まずに対象を指し示すパス名を result へ書き込む。result に得られたパス名が妥当なファイルやディレクトリを表しているか否かを調べるのである。
※補足
realpath() 関数には、2003年にバッファオーバーフロー脆弱性の存在が報告されている。最新でないオペレーティングシステムを使用する際は注意が必要である。
参考URL:http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2003-0466