次のページ 前のページ 目次へ

6. プログラムの内部と入口の構造化

6.1 インタフェースを安全にする

インタフェースはできるだけ小さく(できる限り単純に),狭く(必要な機能し か提供せず),迂回できないようにすべきである. 信頼はできるだけしないようにすべきである. アプリケーションやデータのビューアは外部で作られたファイルの表示に使わ れることがあるので,安全な「砂箱」を作るだけの大量の作業をするつもりが ないのであれば,普通はプログラム(自動実行マクロを含む)の実行を許可して はならない.

6.2 与える権限をできるだけ小さくする

先に述べたように,プログラムに持たせるのは作業に必要最小限の権限だけに すべきであるという重要な一般原則がある. こうしておけば,プログラムが壊れても被害は抑えられる. もっとも極端な例は,安全にしなければならないようなプログラムを単に書か ないことである.それで済むのであれば,ぜひそうすべきである.

Linux では,プロセスのパーミッションを決める主要な要素は,プロセスが持っ ている ID である: 各プロセスはユーザとグループの両方について実 ID, 実効 ID, ファイルシステム ID, 保存 ID を持っている. これらの値をうまく処理することは,パーミッションを最小限に保つ上で非常 に重要である.

パーミッションは,以下で述べる複数の観点から最小化すべきである:

OS によっては,ひとつのプロセス内に信頼処理を行う層を複数個持っている ものがある.例えば,Multics のリング機構などである. 標準の UNIX や Linux は,単独のプロセス内で関数を使ってこのように 信頼処理のレベルを複数個に分割する方法を持っていない.カーネルに対する システムコールでパーミッションを向上させることはできるが,一つのプロセス は一つの信頼レベルしか持てない. Linux や他の UNIX 的 OS では,ひとつのプロセスを複数個のプロセスに fork させることよってこの機能を疑似的に実現できることもある. これを行うには安全な通信経路を設定し(普通は名前なしパイプが使われる), それから複数のプロセスへの fork を行い,そしてできる限り多くの パーミッションを捨てる. それから,単純なプロトコルを用いて信頼度の高いプロセスから信頼度の低い プロセスに動作を要求できるようにし,信頼度の高いプロセスは 限られた要求にしか対応しないことを確実に行う.

これは Java 2 や Fluke のような技術が優位性を持っている分野のひとつで ある. Java 2 では,細かく分けられたパーミッションが指定できる(例: 特定のファ イルだけをオープンできるパーミッション). しかし,普通は汎用の OS はこのような機能は持っていない.

Linux の各プロセスは Linux 固有の状態値を 2 つ持っている.つまり, ファイルシステムユーザ ID (FSUID) とファイルシステムグループ ID(FSGID) である. これらの値はファイルシステムのパーミッションをチェックするときに使われる. root 権限を持つプログラムは,ID を一般ユーザのもの切り替えてファイルシステムを アクセスするのではなく,単に FSUID と FSGID を変えるだけという方法も検 討すべきである. その理由は,プロセスの実行 UID を設定すると,その UID に対応するユーザ はそのプロセスにシグナルを送れるが,FSUID を変更しただけならばシグナル を送れないからである. この方法の利点は,このシステムコールは他の POSIX システムには移植でき ない点である.

6.3 デフォルトは安全な設定にする

インストール時は,ユーザに設定の機会を与えるまでは,プログラムは全ての アクセスを拒否すべきである. インストールされたファイルやディレクトリは世界中から書き込める状態であっ てはならない.また実際には,信頼するユーザ以外には読めなくしておくとよ いだろう. 設定を記述するところがあれば,デフォルトの設定はユーザが特別に許可して いなければアクセスを拒否する設定にしてあるべきである.

6.4 フェールオープン

安全なプログラムは常に「フェールオープン」であるべきである. つまり,プログラムが何か失敗すると,プログラムは全てのアクセスを拒否す るように設計されているべきである(「フェールセーフ」とも呼ばれる). もし何らかのおかしな動き(不正な入力,「なるはずのない状態に」なること 等)があるようならば,プログラムは即座にサービスを停止すべきである. 「ユーザの求めていることを調べ」ようとはせず,即座にサービスを禁止すること. このようなやり方は(ユーザから見た場合の)信頼性や便利さを損ねることがあ るが,セキュリティは向上する.

6.5 競合状態の回避

安全にすべきプログラムで,受け取ったリクエストを認めるべきかどうかを決 めなければならず,もし認めるべきであればリクエストを実行しなければなら ない. プログラムがリクエストを実行する前の意思決定には,信頼できないユーザが 何らかの影響を及ぼす手段があってはならない.

この問題はファイルシステムでは繰り返し起きる. プログラムでは一般的に,リクエストを認めるかどうかを決めるために access(2) を用い,その後で open(2) を使うのは避けるべきである. なぜなら,2 つのシステムコールが呼ばれる合間にユーザがファイルを移動 させることができるからである. 安全なプログラムでは,この方法ではなく,実効 ID かファイルシステム ID を設定し,それから直接 open システムコールを呼び出すべきである. access(2) を安全に使うことも可能ではあるが,それはユーザがファイルおよ びファイルシステムのルートからのパス上にあるどのディレクトリにも影響を 及ぼせない時だけである.

6.6 信頼できる通信経路しか信頼しない

一般的には,信頼できない通信経路から得た結果を信頼してはならない.

ほとんどのコンピュータネットワーク(もちろん一般のインターネットの場合 も)認証されていない送信データはまったく信頼に値しない. 例えばインターネット上ではヘッダ値を含めて任意のパケットを偽造できるの で,パケットの認証を行える場合を除いて,これらの値をセキュリティ的な 判断を下すための主な基準にしてはならない. 場合によっては,「内部ネットワーク」から来たと主張しているパケットが 実際にそうであると断言できることもあるが(ローカルのファイアウォールが 外部からのなりすましを防ぐ場合など),ファイウォールが破られたり,別の 経路があったり,携帯機器を持ち込まれた場合にはこのような仮定も疑わしく なってしまう. 同様の話で,低位のポート番号(1024 未満)が信用できると仮定してはならない. ほとんどのネットワークではこのようなリクエストは偽造できるし,低位の ポート番号が使えるようなプラットフォームを作ることもできるからである.

標準であるが本質的に安全でないプロトコル(例: ftp や rlogin)を実装しよ うとしている場合は,安全なデフォルト設定を用意し,前述のような仮定を 明文化しておくこと.

ドメインネームシステム(DNS)はコンピュータの名前と IP(数値)アドレスを 結びつけるためにインターネット上で広く使われている. 「DNS 逆引き」と呼ばれる技術を使えば,一部の単純ななりすまし攻撃を防ぐ ことができ,ホストの名前を調べるときにも役立つ. しかし,この技術を信頼して認証の決定に使うことはできない. この問題は結局,DNS のリクエストは最終的には攻撃者が制御しているかもし れないどこかのリモートシステムに送られるという点である. したがって,DNS の参照結果を入力として扱う際には,検証を行う必要がある し,結果を信用して重要なアクセス制御に用いてはならない.

パスワードを求める場合は,信頼できる経路をできるだけ用意すること(例え ば,ログインの前に偽造できないキーを押させたり,LED の点滅のような偽造 できないパターンの表示を行うことが必要である). パスワードを扱うときには,パスワードの暗号化を接続の信頼できる端点で行 うこと.

また,どんな電子メール(「From 行」のアドレスを含む)でも偽造することが 可能である. 電子署名の利用は,このような攻撃の多くを阻止する手段となる. もっと簡単な防御手段は,ランダムに生成した特殊な値をつけた電子メールを やりとりすることである.公開メーリングリストの登録などの重要性の低いや りとりでは,普通はこの程度で十分である.

信頼できないネットワーク上に信頼できる通信経路を用意することが必要がな らば,何らかの種類の暗号サービス(最低限のところでは,暗号的に安全な ハッシュ)が必要である.詳しくは「暗号のアルゴリズムとプロトコル」の節 を参照すること.

クライアント/サーバモデル(CGI を含む)はどんな形のものであれ,サーバは クライアントはどんな値でも変更できることを想定していなければならない. 例えば,いわゆる「隠しフィールド」やクッキーの値も,CGI プログラムが受 け取る前にクライアント側で変更できる. クライアントが偽造を行えず,サーバがチェックできるような形で署名がなさ れているのでなければ,このような値を信用することはできない.

関数 getlogin(3) や ttyname(3) は,ローカルのユーザが制御できる情報を 返すので,セキュリティ上の目的でこれらの情報を信用してはならない.

6.7 内部的な整合性をチェックするようなコードを書く

プログラムは,関数呼び出しの引数と基本状態の仮定が正しいことを保証する ためのチェックを行わなくてはならない. C 言語では,assert(3) のようなマクロがチェックに役立つ.

6.8 自分自身でリソースを制限する

ネットワークデーモンの場合は,高すぎる負荷は切り捨てるか制限すること. limit 値を設定し(setrlimit(2) を使用),使われるリソースを制限すること. 少なくとも,「core」ファイルの生成は setrlimit(2) で起こらないように設 定すること.プログラムが異常終了したときに Linux が生成する core ファイル には通常は全てのプログラムメモリが保存されるが,このようなファイルには パスワードやその他の問題があるデータが入るかもしれないからである.


次のページ 前のページ 目次へ