6.4. 特権を最小限に

以前にも書いたように、一般的な原則は、プログラムには処理に必要となる特権を 最低限にしか与えないことです(特権をできるだけ持たせない)。 そうすればそのプログラムが壊れても、ダメージは広がりません。 最も極端な例は、単に安全が必要となるプログラムをまったく書かないことです。 そうできれば、そうすべきです。 たとえば、可能ならプログラムに setuid や setgid をかけないでください。 ただの一般プログラムにして、管理者には動かす前にログをとるように依頼してくだ さい。

Linux や Unix において、まずプロセスの特権を決定するのは、そのプロセス id の組み合わせです。 プロセスそれぞれには、ユーザやグループ両者の実、実効、保存 id があります。 (古い Unix には「保存」id がないものもあります)。 Linux には特別な拡張機能として、ファイルシステムとは独立した uid と gid が、プロセスそれぞれに用意してあります。 これらの値を操作するのは、特権を最小限に抑える上で欠くことができませんし、 それらを最小限に抑える方法もいくつかあります(下記で論じます)。 chroot(2)も利用でき、プログラムから見えるファイルを最小限にできます。 Linux や Unix には、他にも特権を決める値がいくつかあります。たとえば、 POSIX ケイパビリティ(Linux 2.2 以上でサポートされ、他の Unix ライクな システムでもサポートしているものがあります)がそれに当たります。

6.4.1. 許可される特権を最小限に

もちろん最も効果的なやり方は、許可される最高の特権を素直に最小限にする方法 です。 特に、できるだけ root の特権を許可するのは避けてください。 ほんのわずかなファイル群にアクセスする必要があるだけで、プログラムに setuid root をかけないでください。 機能毎に独立したユーザやグループアカウントの作成を検討してください。

よく行う方法は、特別なグループを作成し、ファイルのグループのオーナーを そのグループに変更する方法があります。そしてプログラムをそのグループに setgid します。 できるなら setuid するよりも setgid した方が賢明です。 そうしておいて、グループのメンバーにはほとんど権限を認めないようにします (特にファイルのパーミッションを変更する権限を認めない)。

これは、ゲームソフトのハイスコアを記録する場合によく使われている方法です。 ゲームは普通 games に setgid して、スコア ファイルは games グループが所有しています。 そしてプログラム自体や設定ファイルは、別のユーザ(root 等)が所有している のが普通です。 こうしておけば、ゲームを通じて侵入者が入ってきても、ハイスコアをいじること はできたとしても、ゲームの実行形式や設定ファイルには手を付けられません。 後者は重大です。攻撃者がゲームの実行形式や設定ファイル(どの実行形式を動かす かを制御している)を変更できたなら、ゲームを動かしているユーザをコントロール できるかもしれないからです。

新しいグループを作るだけでは不十分な場合は、新しい仮のユーザ(実際に特別な 役割を持つ)を作成し、関連するリソースを管理してください。 これの典型的な例は Web サーバです。Web サーバは特別なユーザ(「nobody」)で 設定してあるので、他のユーザから独立していられます。 実際、Web サーバからは教えられることが多くあります。Web サーバは普通は起動時 に root の特権を必要としますが(80 番ポートを利用するからです)、起動してしまう とすべての特権を外して、「nobody」というユーザで動きます。 繰り返しますが、通常仮のユーザは最初に動かすプログラムを所有していないので、 アカウントに潜り込んでも、プログラム自体を変更できません。 結果的には、動作している Web サーバに侵入しても、それだけでシステムすべての セキュリティは侵せません。

データベースシステムを利用しているなら(つまり、そのクエリ・インタフェース を呼び出している)、そのアプリケーションを利用しているデータベース・ユーザの 権限に、制限をかけてください。 たとえば、ユーザが定義したほんのいくつかのクエリを使ったアクセスだけが必要な ユーザに対して、システムのストアド・プロシジャすべてにアクセスを許すような ことはしないでください。 実行できるのは、ストアド・プロシジャだけです。 そうしておけば、たとえ誰かが任意の文字列をクエリに無理やり入れ込んだとしても、 ダメージは限られた範囲で収まります。 直接標準的な SQL クエリをクライアントのデータとして提供しなければならないなら、 その動作を制限するようにラッパをかけてください(たとえば、sp_sqlexec のような)。 (データベースについてのアドバイスをしてくれた、SPI Labs に感謝します)。

プログラムに root が確保している特権を持たせなければならないなら、POSIX ケイパビリティの利用を早急に検討 してください。そして、プログラムが利用できる特権を最小限に押えるようにして ください。 POSIX ケイパビリティは、Linux 2.2 や他の Unix ライクなシステムの多くで利用 できます。 起動後すぐに cap_set_proc(3)や Linux 固有の capsetp(3)ルーチン を呼び出せば、その後はずっとそのプログラムの機能を下げたままにして、本当に プログラムが必要としている機能に押えられます。 たとえば、ネットワーク時刻デーモン(ntpd)は、以前から root で実行してきました。 それは、現在の時刻をあわせるためです。 しかし、ntpd が CAP_SYS_TIME という 1 つのケイパビリティだけで動くパッチが 開発されました。パッチを当てれば、攻撃者が ntpd を乗っ取っても、そのプログラム につけ込むのが以前よりやや難しくなりました。

「ある程度制限をかけて」と言っているのには、理由があります。それは他に手段 を用いずに POSIX ケイパビリティを使って特権を維持すると、 プロセスが root ユーザの id を使い続けるからです。 重要なファイル(設定ファイルやバイナリ等)は root が所有しているケースが多い ので、攻撃者はケイパビリティで制限がかかっていても、依然としてプログラムを コントロールできます。つまり、システムの鍵となるファイルを修正でき、root レベルの特権をすべて取得できてしまいます。 Linux カーネルの拡張(2.4.X と 2.2.19+ のバージョンで利用できます)は、 利用可能な特権に制限をかけるのに、もっと優れた方法を提供しています。 プログラムを rootで起動し(POSIX ケイパビリティを使って)、本当に必要な ケイパビリティにまで絞りこんで、prctl(PR_SET_KEEPCAPS,1)を呼び出します。 そして、setuid() を使って root 以外のプロセスに変更します。 PR_SET_KEEPCAPS はプロセスにマークをつけ、プロセスが setuid を 0 以外 の値にした時に、ケイパビリティはクリアされません(通常はクリアされます)。 このプロセスの設定は、exec() するとクリアされます。 しかし、PR_SET_KEEPCAPS は Linux 特有の拡張機能で、最近のバージョンの Linux カーネルで採用されている点に注意してください。

Linux 特有のツールの 1 つに SuSE が開発した「コンパートメント」があります。 これを使えば、許可する特権を簡単に最小限にできます。 このツールは、ファイルシステムのルートや uid、gid、もしくはケイパビリティ を設定してからプログラムを動かします。 他のプログラムを修正することなしに、実に手軽に実行できます。 下記がバージョン 0.5 の書き方です。

Syntax: compartment [options] /full/path/to/program

Options:
  --chroot path   chroot to path
  --user user     change UID to this user
  --group group   change GID to this group
  --init program  execute this program before doing anything
  --cap capset    set capset name. You can specify several
  --verbose       be verbose
  --quiet         do no logging (to syslog)

つまり下記のようにすれば、より安全な anonymous ftp サーバが運用できます。
  compartment --chroot /home/ftp --cap CAP_NET_BIND_SERVICE anon-ftpd

このドキュメントを書いている時点では、まだ未完成で、代表的な Linux ディストリビューションでは利用できません。しかし状況はすぐに変わるでしょう。 このプログラムは、 http://www.suse.de/~marc から からダウンロードできます。

すべての Unix ライクなシステムが POSIX ケイパビリティを実装しているわけでは ない点と PR_SET_KEEPCAPS は現状では Linux 独自の拡張である点に注意してください。 つまり、この解決方法は移植性がありません。 しかし、利用できる環境下でオプションの安全策の単なる 1 つとして利用するなら、 この方法を採用することによって、実際には移植性は損なわれません。 また、Linux カーネルが 2.2 より新しいバージョンなら、低レベルのシステムコール は用意してあります。しかし、利用しやすい C レベルのライブラリをインストール していないディストリビューションもありますので、アプリケーションで使うには ちょっと面倒です。 Linux の POSIX ケイパビリティについてさらに詳しい情報は、 http://linux.kernel.org/pub/linux/libs/security/linux-privs を見てください。

FreeBSD には jail()という関数があり、これで特権を制限しています。 詳しい情報は、 jail documentation を見てください。 特権を制限するのに、特別なツールや機能拡張がたくさんあります。 Section 3.10を見てください。

6.4.2. 特権を使っている時間を最小限に

早急に特権を永久に捨て去ってください。 Linux を含む Unix ライクなシステムの中には、「保存」id を実装して、「以前の」 値を記録しているものがあります。 最も単純な解決方法は、補助グループがどれも適切なら、そのグループを再設定 することです(たとえば、setgroups(2)を使って)。 setuid や setgid したプログラムは、特別な理由がない限り、普通は実効 gid と uid に実 id を設定してください。特に fork(2)した後には必ず。 root から他の特権に落とす場合は、まず gid を変更しなければいけないことを 忘れないでください。そうしないと動かなくなります。一度 root の特権を落として しまうと、それ以上変更のしようがなくなります。 あるシステムでは、プロセスが特権を持った補助グループに属していると、 グループの変更だけでは十分ではないケースがあるのも忘れないでください。

既知のバグで気をつけなければいけないものに、POSIX ケイパビリティを利用して、 権限の最小化を妨げるものがあります。 このバグは、Linux カーネルの 2.2.0 から 2.2.15 に影響があり、POSIX ケイパビリティを持っている他の Unix ライクなシステムの多くにもおそらく影響が あると思います。 http://www.securityfocus.com にある Bugtraq の id 1322 にさらに詳しい情報 があります。 要約を挙げておきます。

POSIX 「ケイパビリティ」は最近になって Linux カーネルに実装されました。 これらの「ケイパビリティ」は特権を制御する方法として加わったものの 1 つで、 特権を持つプロセスの実行に対して、きめ細かな制御をかけることができます。 ケイパビリティは 3 つ(かなり大きな)ビットフィールドとして実装して あり、ビットフィールドのそれぞれのビットが、特権を持つプロセスが実行できる 機能を表わしています。特定のビットを設定することで、特権を持ったプロセスの 動作を制御できます。必要となるプログラムの特定の一部に限定して、さまざまな 機能を利用するアクセスを許可できます。 これはセキュリティの指標となります。問題は、ケイパビリティは fork()を実行 するとコピーされる点にあります。つまり親プロセスがケイパビリティをいじると、 子が継承してしまいます。これに付け入るには、3 つのビットフィールドそれぞれで、 ケイパビリティすべてにゼロ(すべてのビットをオフにすることを意味します)を設定 する方法があります。そうしておいてから、コードを実行する前に特権を落とそうと する setuid したプログラムを root で実行します。これは危険です。sendmail が していることがまさにこれです。sendmail は setuid(getuid())を使って特権を 落とそうとしますが、そうするのに必要なケイパビリティのビットフィールドの設定 とその返り値のチェックをしなければ、その試みは失敗に終わります。そのまま スーパーユーザの特権を持ったまま実行し続け、あるユーザの .forward ファイルを root として動かすことが可能になり、非常に危ない状況に陥ります。

sendmail が使っている解決方法の一つに setuid(getuid())した後には setuid(0) を試みる、というものもあります。通常これは失敗するはずです。 成功したとしても、プログラムは停止してしまうでしょう。 さらに詳しい情報は、http://sendmail.net/?feed=000607linuxbug を見てください。 他のプログラムであれば、短期的には良いアイディアだと思いますが、長期的に みれば、信頼あるシステムへのアップグレードが好ましいのに違いありません。

6.4.3. 特権が有効な時間を最小限にする

setuid(2)や seteuid(2)、setgroups(2)やそれと関連した機能を使用する場合は、 プログラムがその特権を必要とする時だけに有効としているかを確認してください。 そして利用していない時には、一時的に特権を無効にしてください。 上記でも書いたように、ユーザの入力を解析している間に、これらの特権が無効に なっているかを確かめてもかまいません。もっと平たく言えば、本当に必要な時に だけ特権を有効にしてください。

バッファオーバーフロー攻撃には、攻撃が成功するとプログラムに任意のコードを 実行させてしまうものがあります。そしてそのコードは、一時的に落としていた 特権を再び有効にできてしまいます。 つまり一時的に特権を無効にしたとしても、対応できない攻撃がたくさんあるということです。常に安全なのは、速やかに特権を完全に落として しまう方法です。 対処できない攻撃が多いという理由で、「seteuid()は有害と見なす」とまで言う 人もいます。 そうであっても、一時的にパーミッションを無効化することで、すべての種類の攻撃 を阻みます。 このテクニックは攻撃を防ぐケースが多いので、プログラムの該当する部分でずっと 特権を落とせないならば、やってみる価値はあります。

6.4.4. 特権を認めるモジュールを最小限にする

わずかなモジュールにだけ特権を認めているなら、そのモジュールが安全かどうか を判断するのはそれほど難しくありません。 1 つの方法として、特権を使うモジュールをただ 1 つにしてしまう方法があります。 そうして特権を落としておけば、他のモジュールが後から呼び出されても特権を 間違って使うようなことはありません。 もう 1 つの解決方法は、独立した実行形式で独立したコマンドにしてしまう方法 です。 もう一つの解決方法は、独立した実行形式のコマンドにしてしまう方法です。 コマンド 1 つが複雑なツールになっていて、特権ユーザ(たとえば root)がそれを 使っておびただしい作業をしているかもしれませんし、一方他のツールは setuid して あるものの、小さく単純なツールで、ほんのわずかな一部のコマンドだけを許している かもしれません。 小さく単純なツールは、入力をさまざまな受け入れ基準に合致しているかチェックし、 その入力を受け入れるのかどうかを判断します。その後、入力を正しいと判断すると、 複雑なツールにデータを渡します。 小さく単純なツールは、徹底的に入力をチェックし、複雑なツールに渡すデータを 制限しなければいけません。さもないとそれが脆弱さになってしまいます。 これらの解決方法は、いくつかの方法を積み重ねて実行できます。たとえば、複雑な ユーザのツールが、たった 1 つの setuid した「ラッパー」プログラム(入力が安全 な値かどうかをチェックする)を呼び出し、そのラッパーが他の複雑な信頼できる ツールに情報を渡せます。 この方法は、GUI ベースのシステムにとって特に有効です。GUI の一部を一般ユーザ で動かし、セキュリティ関連の要求があった場合に特権を持ったプログラムへ実際の 実行をまかせます。

アプリケーションには、問題をより小さく分割して、相互に信頼関係を持たない プログラムとして開発するのが一番良い場合があります。 単純な方法として、問題を独立したプログラムに分散し、ファイルシステムの機能を 使ったり、プログラム間で問題が起こらないよう、外に見えないようにしたりして、 (安全に)1 つのことしか行わないようにする方法があります。 もっと複雑な相互関係が必要とされているなら、複数のプロセスに fork()するという 手もあります。分かれたプロセスがそれぞれに特権を持ちます。 情報の通信経路はいろいろと設定可能です。まず「マスター」となるプロセスが通信 経路(名前なしパイプや名前なしソケット)を作ってしまう方法があります。作った 後に別々のプロセスへ fork したなら、それぞれのプロセスでできるだけ特権を 落とします。 こうすると、デッドロックに注意する必要があります。 単純なプロトコルを使って、信頼性が低いプロセスが信頼性が高いプロセスに対して 要求を行えるようにします。そして、より信頼性の高いプロセスだけが、限定した 要求をサポートするようにします。 ユーザやグループのパーミッションを設定して、他の誰かがサブプログラムを起動 さえできないようにして、入り込むのを困難にします。

オペレーティングシステムには、信頼性を複数の層にするコンセプトを持って いるものがあります。たとえば、Multics のリング構造がこれに当たります。 標準的な Unix や Linux には単独のプロセス中で機能毎に複数のレベルで信頼性 を持つ手段を持ち合わせていません。下記のような感じになっています。 カーネルに呼び出しをかけるには特権を上げますが、そのプロセスはたった 1 つの 信頼性のレベルしか持っていません。 これが Java 2 や C#(Java のやり方のまね)、Fluke(セキュリティ強化版 Linux の基盤)の長所になっています。 たとえば、Java 2 はある特定のファイルだけをオープンするパーミッションと いうような、きめの細かいパーミッションを指定できます。 しかし、汎用的なオペレーティングシステムでは、現状一般的にそういった機能を 備えていません。これは近い将来にかわるかもしれません。 Java についての詳しい話は Section 9.6 を見てください。

6.4.5. fsuid を使って、特権を制限することを検討する

Linux のプロセスはそれぞれ固有の状態値を 2 つ持っています。ファイルシステムの ユーザ id(fsuid)とフィルシステムのグループ id(fsgid)がそれです。 これらの値はファイルシステムのパーミッションに対してチェックをかける場合 に使用します。 不特定ユーザ用ファイルサーバ(たとえば NFS サーバ)を操作するようなプログラム を作成するなら、この Linux の拡張機能の利用を検討してみてください。 これらを使うと root の特権を維持しながら、一般ユーザの代理でファイルアクセス する前に fsuid と fsgid を変更します。 この拡張はかなり便利で、ファイルシステムのアクセス権を(おそらく必要な)他の 権限を削除ぜずに制限をかける仕組みを提供します。 fsuid(euid は設定せずに)を設定するだけで、ローカルユーザはそのプロセスに シグナルを送れなくなります。 また、この状況下では競合状態を避けやすくなります。 しかし欠点として、これらの呼び出しが他の Unix ライクなシステムに対して移植性が ないという点が挙げられます。

6.4.6. 利用できるファイルを最小限にするため chroot の利用を検討する

chroot(2) を使えばプログラムから見えるファイルを制限できます。 この機能を活かすには、ディレクトリ(「chroot jail(chroot の牢獄)」と呼ばれて います)の設定を注意深く行い、設定した通りにそのディレクトリに入り込むように する必要があります。 これはプログラムのセキュリティを向上するのに、かなり効果的な方法と言えます。 見えないファイルに干渉するのは困難だからです。 しかし、すべてをこの前提に頼ってはいけません。注意しなければいけないのは、 プログラムには root の特権を持たしてはいけないこと、root の特権をどんな方法 を使っても取得できないこと、そして chroot jail を確実に設定することです。 使って意味がある場所にchroot(2)することを推奨します。しかし、これだけに頼って はいけません。そのかわりに、複数の層からなる防御手段の一部として位置づけて ください。 chroot(2)の利用方法について、いくつか覚え書きを書いておきます。

6.4.7. アクセスできるデータを最小限にすることを検討する

ユーザがアクセスできるデータ量を最小限にすることを検討してください。 たとえば、CGI スクリプトなら、ユーザが直接データを見なければならない理由がない 限り、CGI スクリプトが利用するデータはすべてドキュメントツリーの外に置いて ください。 リンクを公開していなければ、誰もデータにアクセスできない、と誤解している人も います。しかしこれは絶対に間違っています。

6.4.8. 利用できるリソースを最小限にすることを検討する

プロセスが利用できるコンピュータのリソースを最低限にするように配慮して ください。そうすれば、あるプロセスが「めちゃくちゃ」になってもダメージの範囲 が狭くなります。 これは、サービス拒否攻撃を防ぐのに必須の方法です。 ネットワーク系のサーバでは、それぞれのセッションに対して独立したプロセスを 設定するのが一般的なやり方です。それぞれのプロセスはセッションが使える CPU 利用時間等の総量に制限をかけます。 こうすれば、攻撃者がメモリを食い潰すような要求をだしたり、CPU を 100% 使い 切ったりしようとしても、制限が働いて単独のセッションが他のタスクに支障を来す のを防ぎます。 もちろん、攻撃者はたくさんのセッションを張れますが、これは少なくとも攻撃に とって障害となります。 どのように制限をかけるかについては Section 3.6 に詳しい情報が あります(たとえば、ulimit(1))。