POS01-C. ファイルを操作するときにはリンクかどうかを確認する
Windows や UNIX など一般的な OS の多くは、ハードリンク、シンボリック(ソフト)リンク、仮想ドライブを含む、ファイルへのリンクを使用できる。ハードリンクは、UNIX では ln
コマンドで、Windows では CreateHardLink()
関数を呼び出して作成できる。シンボリックリンクは、UNIX では ln -s
コマンドを使って作成できる。Windows では NTFS のディレクトリジャンクションまたは Linkd.exe (Win 2K リソースキット)、あるいはフリーウェアの "junction" を使って作成できる。仮想ドライブは、Windows では subst
コマンドで作成できる。
ファイルを開く操作によって実際には別のファイルが開かれる可能性があることを考慮していないプログラムでは、ファイルへのリンクによってセキュリティ上の問題が生じることがある。特に、脆弱性のあるプログラムが、より高い権限で実行されている場合は非常に危険である。
多くの場合、この問題は他の方法で解決できるため、シンボリックリンクの有無を検査する必要はない。たとえば既存のファイルを開く際、最も単純な解決法は、プログラムの権限を当該ユーザの権限レベルに下げることである。この解決法では、アプリケーションのユーザに許可されていないファイルへのアクセスを防止しつつリンクを使用できる。
新しいファイルを作成する際は、既存のファイルがない場所に限り新規ファイルの作成を実行するような関数を使用する方法がある。この方法を使えば、ファイルの作成時にアプリケーションが既存のファイルを上書きしてしまうのを防止できる。(「FIO03-C. fopen() やファイル作成時の動作について勝手な想定をしない」を参照。)
まれに、プログラムが別のディレクトリにある異なるファイルではなく、意図したファイルから読み取り操作を行っていることを確認するために、シンボリックリンクまたはハードリンクの有無を検査する必要が生じる。このような場合、シンボリックリンクの有無を検査する際に競合状態が生じるのを避ける必要がある(「POS35-C. シンボリックリンクの存在をチェックするときには競合状態を避ける」を参照)。
違反コード
次のコード例では、文字列 file_name
で指定されたファイルを読み書き用にオープンし、ユーザによって指定されたデータを書き込んでいる。
char *file_name = /* 何らかのファイル */; char *userbuf = /* 何らかのデータ */; unsigned int userlen = /* userbuf 文字列の長さ */; int fd = open(file_name, O_RDWR); if (fd == -1) { /* エラー処理 */ } write(fd, userbuf, userlen);
プロセスが高い権限で実行されている場合、攻撃者はファイルを認証ファイル /etc/passwd
へのシンボリックリンクに置き換えることで、このコードを悪用できる。攻撃者はその後、パスワードファイルに保存されているデータを上書きし、パスワードが不要な新しい root アカウントを作成できる。ゆえに、この攻撃は脆弱なシステム上での root 権限の獲得に使用される可能性がある。
適合コード (Linux 2.1.126 以降、FreeBSD、Solaris 10、POSIX.1-2008 O_NOFOLLOW
)
この問題を緩和するのに役立つ O_NOFOLLOW フラグを提供するシステムもある。このフラグは、POSIX.1-2008 標準では必須となっており、今後可搬性が向上すると考えられる [Open Group 2008]。このフラグが設定され、指定した file_name
がシンボリックリンクである場合、ファイルをオープンする操作は失敗する。
char *file_name = /* なんらかのファイル */; char *userbuf = /* なんらかのデータ */; unsigned int userlen = /* userbuf 文字列の長さ */; int fd = open(file_name, O_RDWR | O_NOFOLLOW); if (fd == -1) { /* エラー処理 */ } write(fd, userbuf, userlen);
この適合コードにハードリンクの検査は含まれていない点に注意。
適合コード (lstat
-fopen
-fstat
)
この適合コードは、「FIO05-C. 複数のファイル属性を使用してファイルを特定する」で説明されている lstat
-fopen
-fstat
の構文を使用している。
char *file_name = /* なんらかの値 */; struct stat orig_st; if (lstat( file_name, &orig_st) != 0) { /* エラー処理 */ } if (!S_ISREG( orig_st.st_mode)) { /* 通常のファイルではないか、シンボリックリンクである */ } int fd = open(file_name, O_RDWR); if (fd == -1) { /* エラー処理 */ } struct stat new_st; if (fstat(fd, &new_st) != 0) { /* エラー処理 */ } if (orig_st.st_dev != new_st.st_dev || orig_st.st_ino != new_st.st_ino) { /* 競合状態の発生時にファイルが不正操作されている */ } /* ... 適切なファイルである。fd に対する操作を実行する ... */
このコードは依然として TOCTOU 競合状態を引き起こすが、ファイルに対する操作を実行する前に、事前に検査したファイルと実際に開いたファイルが同じものであるかどうかを(ファイルのデバイスおよび i ノードを検査することで)確認している。したがって、競合状態が発生している間に攻撃者がファイルを不正に操作したとしても、このコードでは不正な操作を認識して適切に対応できる。
このコードにハードリンクのチェックは含まれていないことに注意。
ハードリンク
ハードリンクは問題を引き起こしやすい。1 つのファイルに複数のハードリンクがある場合に、本来のリンクと悪意のある攻撃者によって作成されたリンクを区別できないためである。
ハードリンクに対処する 1 つの方法は、複数のハードリンクがあるファイルを開かないことである。以下のコードを前のコード例に挿入すると、ファイルに複数のハードリンクがあるかを識別できる。
if (orig_st.st_nlink > 1) { /* ファイルに複数のハードリンクが設定されている */ }
リンクとリンク先ファイルが別々のデバイス上に存在していればハードリンクを作成できないため、多くのプラットフォームは、システムの重要なファイルとユーザによる編集が可能なファイルを別々のデバイスに配置している。たとえば、/
ディレクトリ(/etc/passwd
などの重要なファイルが保存される)を配置するハードドライブと、/home
ディレクトリ(ユーザによる編集が可能なファイルが保存される)を配置するハードドライブを別々にするなどである。これにより、/etc/passwd
へのハードリンクがユーザによって作成されるのを防止できる。
リスク評価
リンクの有無の検査を行わないと、重要なシステムファイルが上書きされ、データの完全性が侵害されるおそれがある。
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
POS01-C |
中 |
高 |
高 |
P6 |
L2 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
Compass/ROSE |
|
|
|
Related Guidelines
MITRE CWE | CWE-59, Failure to resolve links before file access (aka "link following") CWE-362, Race condition CWE-367, Time-of-check, time-of-use (TOCTOU) race condition |
参考資料
[Austin Group 2008] | |
[Open Group 2004] | open() |
[Open Group 2008] | |
[Seacord 2013] | Chapter 8, "File I/O" |
翻訳元
これは以下のページを翻訳したものです。
POS01-C. Check for the existence of links when dealing with files (revision 81)