この章では,トラステッド・プログラムを設計するための技法を紹介しています。この章では,次の内容について説明しています。
2.1 SUID プログラムと SGID プログラムの書き方
SUID (セット・ユーザ ID) プログラムと SGID (セット・グループ ID) プログラムでは,プロセスの実効 UID または GID が,プログラムの UID または GID に変更されます。これらのプログラムでは,ファイルの所有者のアクセス権がプロセスに与えられるため,システム・レベルのファイルやディレクトリへのアクセス制御に関する問題のソリューションとなります。
ユーザ ID に
root
が設定されるプログラムや,グループ ID にシステム・レベルのファイルへの書き込みアクセスが可能なグループが設定されるプログラムでは,セキュリティが脅かされる可能性が高くなります。作業を実行する方法が他にない場合以外は,ユーザ ID を
root
に設定するプログラムは作成しないでください。
chown
システム・コールでは,実行しているプロセスの RUID にゼロが設定されていない限り,ファイルの SUID や SGID ビットが自動的に削除されます。これにより,root
アカウントが所有する SUID プログラムまたは SGID
プログラムを誤って作成することを防止できます。詳細については,
chown
(2)
以下は,さらに安全な SUID プログラムおよび SGID プログラムを作成するためのアドバイスです。
access
システム・コールを使って,ユーザによって指定されたすべてのパス名をチェックします。
コア・ダンプを防ぐために,関連するシグナルすべてをトラップします。
エラー状態 (たとえば,システム・コールのリターン値やバッファのオーバフロー) をすべてテストします。
可能であれば,SUID プログラムではなく SGID プログラムとしてください。この理由の 1 つとして,ユーザのファイル・アクセスよりもグループのファイル・アクセスの方が一般的に制限されていることがあげられます。SGID プログラムが傷つけられた場合,ファイル・アクセスが制限されているため,攻撃者が行える動作の範囲が狭くなります。
別の理由として,SGID プログラムを実行したユーザが所有するファイルへのアクセスが比較的簡単であることがあげられます。ユーザが SUID プログラムを実行した場合,元の実効 UID をファイル・アクセスに使用することができなくなります。しかし,ユーザが SGID プログラムを実行した場合,ユーザの 1 次 GID はグループ・アクセス・リストの一部として,利用できる状態のままとなります。このため,SGID プロセスは,ユーザがアクセスできるファイルへのグループ・アクセス権を持ったままとなります。
すべての SUID プログラムのスタックは,省略時は実行可能ではありません。スタックが実行可能であることを前提としているユーザ・アプリケーションは失敗します。どうしても必要な場合は,省略時の設定を変更することができます。これにより,SUID プログラムのスタックを実行可能にすることができます。省略時のゼロ (実行不可) を実行可能に変更するには,次のコマンドを使います。
# sysconfig -r proc executable_stack=1
リブートしても変更が適用されたままにするには,sysconfigdb
コマンドを使って,エントリを
/etc/sysconfigtab
ファイルに追加します。
2.2 エラー処理
ほとんどのシステム・コールとライブラリ・ルーチンは,呼び出しの成功または失敗を示す,整数のリターン・コードを戻します。必ずリターン・コードをチェックして,ルーチンが正常終了したことを確認してください。呼び出しが失敗した場合は,グローバル変数
errno
を調べて,失敗の原因を見つけてください。
errno
変数が設定されるのは,システム・コールでエラーが発生したときです。この値を使用すると,エラー状態の詳細を知ることができます。この情報は,プログラムでの対応方法を決定したり,より詳しい診断メッセージを作成するのに役立ちます。このエラー・コードは,<errno.h>
内のエラー名に対応しています。詳細については,
errno
(2)
次の
errno
値は,セキュリティ侵害の可能性を示します。
ファイルの所有者またはスーパユーザだけが許されている方法で,所有者以外のユーザがファイルを変更しようとしたことを示します。また,スーパユーザだけが許されている方法で,ユーザが何らかの操作を行おうとしていたことを示す場合もあります。
マウントされたファイル・システムのアクセス許可が無効になっているときに,そのファイル・システム上のファイルにアクセスしようとしたことを示します。
特権システム・コールを使用するプログラムは,生成された実行可能プログラムにスーパユーザ特権がない場合,特権システム・コールを実行しようとすると失敗します。セキュリティ管理者が特権システム・コールの実行失敗を記録するように監査システムを設定している場合は,失敗が記録されます。
プログラムがセキュリティの侵害と考えられる現象を検出した場合,攻撃者がこのプログラムを攻撃するために役立つような診断メッセージを表示しないでください。たとえば,攻撃者の実ユーザ ID (UID) がアクセス対象ファイルの UID と一致しなかったためにプログラムが終了するというメッセージを表示したり,アクセス対象ファイルの名前を表示したりしないでください。この情報は,SUID root プログラムの場合は
audgen()
ルーチンを使い,その他のプログラムの場合は
syslog
を使うことで制限してください。この他,メッセージを表示する前に遅延を少し入れることで,さまざまな入力データを系統的に試してプログラムに侵入する,プログラム化された侵入操作を防ぐことができます。
2.3 ファイルの保護
プログラムが永続ファイル (たとえば,データベース) を使用する場合は,これらのファイルのアクセス許可を制限し,プログラムでアクセスを制御するようにしてください。これらの注意事項は,共用メモリ・セグメント,セマフォ,およびプロセス間通信のメカニズムに対しても適用されます。これらのすべてのオブジェクトには,制限された許可を設定してください。
プログラムは,実行中にデータを格納するため,一時ファイルを作成することがあります。一時ファイルを使う場合は,次の注意事項に従ってください。
プログラムが終了する前に,一時ファイルを削除するようにします。
一時ファイルの所有者だけに,読み取りおよび書き込みの許可を与えます。プログラムの先頭で
umask()
システム・コールを使用して,ファイル作成マスクに 077 を設定します。
一時ファイルは,所有者だけが書き込めるプライベート・ディレクトリまたは
/tmp
に作成します。/tmp
ディレクトリには,スティッキ・ビットが設定されています (モード 1777)。このため,このディレクトリ内のファイルは,ファイルの所有者,ディレクトリの所有者,またはスーパユーザだけが削除できます。
一般的な方法は,一時ファイルを作成し,そのファイルがまだオープンされている状態でリンクを解除 (unlink) することです。これにより,アクセスは,リンクを解除する前にファイルをオープンしたプロセスだけに限定されます。プロセスの終了時には,i ノードが解放されます。
NFS マウントされたファイル・システム上で
unlink
をこのように使用すると,少し異なる動作になります。クライアントのカーネルはファイルの名前を変更し,プロセスの終了時に NFS にのみリンク解除が送られます。他者からそのファイルにアクセスできなくすることはできません。ただし,プロセスが終了したときには,そのファイルは確実にアクセスできなくなります。いずれにしても必ず明示的に,プロセス終了時後には一時ファイルが残らないようにしてください。
2.4 安全な探索パスの指定
popen
,system
,または
exec*p
ルーチンを使用して
/bin/sh
または
/sbin/sh
を実行する場合は,パス名の指定やシェル変数
PATH
の定義を慎重に行ってください。
PATH
変数は,システム上のコマンドとスクリプトを実行するための探索パスを指定するため,セキュリティ上重要な変数です。詳細については,
environ
(7)popen
(3)system
(3)
PATH
変数に,絶対パス名を指定します。
公用ディレクトリ,一時ディレクトリ,他のユーザのディレクトリ,または現在の作業ディレクトリを探索パスに含めないでください。これらのディレクトリを含めると,誤ったプログラムを不注意で実行したり,不正なプログラムによってトラップされる可能性が高まります。
システムのディレクトリを,ユーザのディレクトリよりも前にリストします。これにより,システム・プログラムと同じ名前のプログラムをユーザが誤って実行するのを防ぎます。
パス・リストの構文 (特に空,小数点,およびコロンの使い方) を解析してください。パス・リスト内の空エントリまたは小数点エントリは,現在の作業ディレクトリを指定します。またコロンは,パス・リスト内のエントリを区切るために使います。このため,等号に続く最初のエントリをコロンで始めてはなりません。
パス・リストがコロンで終っている場合,一部のシェルと
exec*p
ルーチンは,現在の作業ディレクトリを最後に探索します。各種シェルがこの最後のコロンを異なる方法で解釈するのを避けるには,空エントリの代わりに小数点を使って現在の作業ディレクトリを参照します。
execve
システム・コールにはパス名を指定する必要があるため,exec*p
ルーチンではなく,execve
を使用する方が良い場合もあります。詳細については,
execve
(2)2.5 シグナルへの応答
Tru64 UNIX オペレーティング・システムは,特定のイベントに対する応答としてシグナルを生成します。イベントは,ユーザの端末 (たとえば,終了,割り込み,または停止),プログラムのエラー (たとえば,バス・エラー),または別のプログラム (たとえば,kill) により起動されます。
特に指定しなければ,ほとんどのシグナルで受信プロセスが終了します。しかし,いくつかのシグナルでは受信プロセスは停止するだけです。SIGQUIT や SIGTRAP などの多数のシグナルでは,デバッグの目的でコア・イメージがファイルへ書き込まれます。コア・イメージ・ファイルには,パスワードのような機密情報が含まれている可能性があります。
コア・イメージ・ファイル内の機密情報を保護し,キーボードからの入力による割り込みからプログラムを保護するには,SIGQUIT,SIGTRAP,または SIGTSTP などのシグナルを捕捉するプログラムを書きます。
signal
ルーチンを使って,シグナルに対するプロセスの応答を変えるようにします。これらのルーチンによってプロセスは,シグナルが送信されたときにそれを無視したり,サブルーチンを呼び出したりといったことが可能です。(SIGKILL シグナルおよび SIGSTOP
シグナルのキャッチ,無視,ブロックはできません。これらのシグナルは必ず,受信プロセスに渡されます)。詳細については,
signal
(3)sigvec
(2)
子プロセスは,親プロセスが
fork
を呼び出す前に設定したシグナル・マスクを継承します。execve
システム・コールは,捕捉済みのすべてのシグナルを省略時の動作にリセットします。無視されるシグナルはそのまま無視されます。
このため,fork
または
execve
を呼び出す前に,プロセスが適切にシグナルを処理するようにしてください。詳細については,
fork
(2)execve
(2)2.6 子プロセスでのオープン・ファイル記述子の使用
子プロセスは,その親プロセスのオープン・ファイル記述子すべてを継承することができます。このため,同じようにファイルにアクセスすることができます。この関係により,セキュリティ上の考慮が必要となります。
たとえば,次の処理を行う,セット・ユーザ ID (SUID) プログラムを作成するとします。
機密性の高い特権ファイルへのデータの書き込みをユーザに許可する。
非特権状態で実行される子プロセスを作成する。
親の SUID プロセスは書き込み用にファイルをオープンするため,子プロセス (または,子プロセスを実行しているすべてのユーザ) は,重要なファイルに書き込みを行うことができます。
機密性の高い特権ファイルを子プロセスのユーザから守るには,子プロセスを作成する前に,子プロセスに不要なファイル記述子をすべてクローズします。
子プロセスを作成する前にファイル記述子をクローズする効果的な方法は,fcntl
システム・コールを使うことです。
この呼び出しを使用すると,ファイルをオープンした後で,そのファイルに
close-on-exec
フラグを設定することができます。このフラグが設定されているファイル記述子は,exec
システム・コールで新たなプログラムをプロセスが開始するときに,自動的にクロースされます。
詳細については,リファレンス・ページの
fcntl
(2)2.7 X 環境でのセキュリティ上の考慮事項
以降の項では,X プログラミング環境でセキュリティを強化する方法について説明しています。
アクセス制御の制限
キーボード入力の保護
キーボードおよびマウスのイベントのブロック
デバイス関連イベントの保護
アクセス制御リストにリストされたホストへログインしたユーザは,XGrabKeyboard
関数を呼び出してキーボードの制御を行うことができます。クライアントがこの関数を呼び出すと,X サーバはすべてのキーボード・イベントをそのクライアントだけに送ります。攻撃者はこの呼び出しを使用して,あるウィンドウからの入力ストリームを取得し,別のウィンドウへ転送することができます。攻撃者は,シミュレートされたキーストロークをウィンドウへ戻し,このウィンドウを実行しているユーザをだますことができます。このためユーザは,不正が行われていることに気付かない可能性があります。
攻撃者がユーザのキーストロークを取り込むことができると,ワークステーション上に格納されたデータの機密性が脅かされます。
X ウィンドウには,セキュア・キーボード・モードがあります。このモードでは,ワークステーションのキーボードからのユーザの入力がすべて,1 つの安全なウィンドウへ送られます。ユーザがこのモードを設定するには,X ウィンドウの [Command] メニューから [Secure Keyboard] 項目を選択します。
機密データを扱うプログラムには,セキュア・キーボード・モードを含めてください。この予防策は,プログラムがユーザにパスワードの入力を求める場合に特に重要です。
セキュア・キーボード・モードを実現するためのガイドラインを,次に示します。
Xlib
の
XGrabKeyboard
呼び出しを使います。
セキュア・キーボード・モードが設定されていることを,ビジュアルな方法でユーザに知らせます。たとえば,画面上で反転表示を使います。
ユーザがウィンドウをアイコン化したときに,XUngrabKeyboard
関数を使ってキーボード・グラブを解放します。キーボードが解放されると,キーストロークを別のウィンドウへ転送できるようになります。
アクセス制御リストにリストされたホストは,ウィンドウの ID を知っていれば,どのウィンドウにもイベントを送ることができます。XSendEvent
呼び出しを使用すると,呼び出し側アプリケーションは,キーボードまたはマウスのイベントを指定のウィンドウへ送ることができます。攻撃者はこの呼び出しを使用して,破壊を行うためのデータをウィンドウへ送ることができます。たとえばこのデータにより,rm -rf *
コマンドを実行したり,機密ファイルの内容をテキスト・エディタを使って変更したりすることがあります。端末がアイドル状態の場合,ユーザはこれらのコマンドが実行されていることに気付かない可能性があります。
攻撃者が破壊を行うためのデータをワークステーションのウィンドウへ送ることができると,ワークステーション上に格納されたデータの完全性が脅かされます。
.Xdefaults
ファイル内の
allowSendEvents
リソースに
False
が設定されている場合,X ウィンドウは,別のクライアントから送られて来たキーボードおよびマウスのイベントをブロックします。
別のクライアントから送られて来たイベントをブロックするプログラムを書くこともできます。XSendEvent
呼び出しは,指定されたウィンドウへイベントを送り,イベント構造体の
send_event
フラグに
True
を設定します。プログラムでは,受け付けた各キーボードおよびマウスのイベントについて,このフラグをテストします。このフラグに
False
が設定されている場合,イベントはキーボードで生成されたものなので,受け付けても安全です。
2.7.3 デバイス関連イベントの保護
キーボードおよびマウスのイベントのようなデバイス関連イベントは,次の条件のいずれかを満たすまで,ソース・ウィンドウから祖先のウィンドウへと上方へ伝えられます。
X クライアントがイベント・マスクを設定することにより,ウィンドウに対してそのイベントを選択する。
X クライアントがそのイベントを
do-not-propagate
マスクに含めることにより,そのイベントを拒否する。
XReparentWindow
関数を使って,ウィンドウの親を変更することができます。この呼び出しは,ウィンドウの親を,同じ画面上の別のウィンドウに変更します。ウィンドウの親を変更する上で知っておく必要があるのは,ウィンドウ ID だけです。子のウィンドウ ID を使って,その親のウィンドウ ID を見つけることができます。
XReparentWindow
の呼び出しを誤って使用すると,ウィンドウ・システムのセキュリティが脅かされます。新しい親ウィンドウは,子ウィンドウが選択しなかったイベントを選択することができます。
この種の悪用から守るためには,次のような予防策をとってください。
子ウィンドウに,必要なデバイス・イベントを選択させます。この予防策は,子から上方へ伝達されたイベントが新しい親に傍受されるのを防ぎます。子ウィンドウのイベント処理を親ウィンドウに集中化する方法は,セキュリティ上大きな危険が伴います。攻撃者は,親ウィンドウを変更して,子ウィンドウに対するイベントを傍受することができます。このため,各子ウィンドウ自身で自分のデバイス・イベントを処理する方が安全です。子ウィンドウが明示的に選択したイベントは,伝達されません。
デバイス・イベントがウィンドウ階層内でそれ以上伝達されないよう,子ウィンドウに指定させます。指定するには,do-not-propagate
マスクを設定します。この予防策により,子ウィンドウがイベントを必要とするかどうかに関係なく,デバイス・イベントが親ウィンドウへ伝達されるのを防ぐことができます。
親ウィンドウが変更されたときに,子ウィンドウに通知されるようにします。通知を受けるには,子ウィンドウのイベント・マスクの
StructureNotify
または
SubstructureNotify
ビットを設定します。これらのイベント・マスクの設定についての詳細は,『X Window System:The Complete Reference to Xlib, X Protocol, ICCCM, XLFD』を参照してください。
機密データを処理するシェル・スクリプトを書くときは,スクリプトの本体より前で
PATH
変数を設定してエクスポートします。シェル・スクリプトには SUID や SGID は設定しないでください。