Info: Version 1.8.x is available.

English Page

Last modified: $Date: 2024-03-30 20:25:00 +0900 (Sat, 30 Mar 2024) $

TOMOYO Linux の世界
第5回:「アクセス許可に条件を付けてみよう」

今回の内容

今回は TOMOYO Linux の強力なパラメータチェック機能を活用するための「条件付きアクセス許可」について紹介します.

アクセス許可に条件を付ける

条件付きアクセス許可について

TOMOYO Linux には RBAC ( Role Based Access Control )機能はありませんが,ポリシーの中でユーザIDに基づく条件を付けることができます.これにより,システムアカウントのユーザIDに基づくアクセス制御が可能です.条件はアクセス許可の後ろに「 if 条件式」という形で指定します.利用できる条件について図1に,使い方の例を図2に示します.

■図1 条件指定に使えるパラメータ
変数意味
task.uid呼び出したプロセスのユーザID
task.euid呼び出したプロセスの実効ユーザID
task.suid呼び出したプロセスの保存ユーザID
task.fsuid呼び出したプロセスのファイルシステムユーザID
task.gid呼び出したプロセスのグループID
task.egid呼び出したプロセスの実効グループID
task.sgid呼び出したプロセスの保存グループID
task.fsgid呼び出したプロセスのファイルシステムグループID
task.pid呼び出したプロセスのプロセスID
task.ppid呼び出したプロセスの親プロセスのプロセスID
task.type呼び出したプロセスの種類( execute_handler か否か)
task.state[0]呼び出したプロセスの状態変数0
task.state[1]呼び出したプロセスの状態変数1
task.state[2]呼び出したプロセスの状態変数2
path1.uid1つめのパス名の所有者ID
path1.gid1つめのパス名のグループID
path1.ino1つめのパス名のiノード番号
path1.type1つめのパス名の種類(ファイル,ディレクトリ,ソケット等)
path1.perm1つめのパス名のパーミッション
path1.major1つめのパス名の存在しているデバイスファイルのメジャー番号
path1.minor1つめのパス名の存在しているデバイスファイルのマイナー番号
path1.dev_major1つめのパス名のパス名(デバイスファイル)のメジャー番号
path1.dev_minor1つめのパス名のパス名(デバイスファイル)のマイナー番号
path1.parent.uid1つめのパス名の親ディレクトリの所有者ID
path1.parent.gid1つめのパス名の親ディレクトリのグループID
path1.parent.ino1つめのパス名の親ディレクトリのiノード番号
path1.parent.perm1つめのパス名の親ディレクトリのパーミッション
path2.uid2つめのパス名の所有者ID
path2.gid2つめのパス名のグループID
path2.ino2つめのパス名のiノード番号
path2.type2つめのパス名の種類(ファイル,ディレクトリ,ソケット等)
path2.perm2つめのパス名のパーミッション
path2.major2つめのパス名の存在しているデバイスファイルのメジャー番号
path2.minor2つめのパス名の存在しているデバイスファイルのマイナー番号
path2.dev_major2つめのパス名のパス名(デバイスファイル)のメジャー番号
path2.dev_minor2つめのパス名のパス名(デバイスファイル)のマイナー番号
path2.parent.uid2つめのパス名の親ディレクトリの所有者ID
path2.parent.gid2つめのパス名の親ディレクトリのグループID
path2.parent.ino2つめのパス名の親ディレクトリのiノード番号
path2.parent.perm2つめのパス名の親ディレクトリのパーミッション
exec.argcプログラム実行時の引数の数
exec.envcプログラム実行時の環境変数の数
exec.argv[index]プログラム実行時のindex番目の引数
exec.envp["name"]プログラム実行時の環境変数name
exec.realpathプログラム実行時のシンボリックリンクを解決したパス
symlink.targetシンボリックリンク作成時のリンク先
■図2 条件指定の例
条件意味
if task.uid=0プロセスのユーザIDが 0 ( root )である場合
if task.uid!=0プロセスのユーザIDが 0 ( root )ではない場合
if task.uid=100-500プロセスのユーザIDが 100 以上 500 以下である場合
if task.uid=0 task.gid=0プロセスのユーザIDとグループIDの両方が 0 ( root )である場合
if task.uid!=0 task.gid!=0プロセスのユーザIDとグループIDの両方が 0 ( root )ではない場合
if task.uid=100-500 task.gid!=0プロセスのユーザIDが 100 以上 500 以下であり,かつ,グループIDが 0 ではない場合
if exec.argv[index]="value"index ( 0<=index<exec.argc )番目の引数の値が value である場合
if exec.argv[index]!="value"index ( 0<=index<exec.argc )番目の引数の値が value ではない場合
if exec.envp["name"]="value"環境変数 name が定義されており,値が value である場合
if exec.envp["name"]!="value"環境変数 name が定義されていないか,あるいは値が value ではない場合
if exec.envp["name"]!=NULL環境変数 name が定義されている場合
if exec.envp["name"]=NULL環境変数 name が定義されていない場合

条件を付与してみよう

例として,コンソールからログインしたユーザが, /bin/cat コマンドで自分の所有するファイルのみを閲覧できるようにする手順について説明します.説明用に, user1 と user2 というアカウントを使用します.コンソールからログインして,以降の操作を行ってください.まず,アカウントを2つ作成します(図3).

■図3 アカウントを2つ作成
# useradd -s /bin/bash user1
# useradd -s /bin/bash user2

パスワードを適当に設定してください(図4).

■図4 作成したアカウントのパスワードを設定
# passwd user1
# passwd user2

cat コマンド用のドメインを作成するために, /bin/cat コマンドを実行してください.ついでに,このコマンドが動作するドメインも表示してみます(図5).

■図5 cat コマンドが動作するドメインを表示
# cat /proc/ccs/self_domain
<kernel> /sbin/mingetty /bin/login /bin/bash /bin/cat

このドメインに学習用プロファイルを割り当ててください.この連載では学習用プロファイルの番号は1ですので,図6のようになります.

■図6 cat コマンド用ドメインに学習用プロファイルを割り当て
# /usr/sbin/ccs-setprofile 1 '<kernel> /sbin/mingetty /bin/login /bin/bash /bin/cat'

ログアウトして, user1 としてログインし直します.そして,図7の操作を行ってください.

■図7 user1 でログインし, /tmp/testfile1 を作成
$ echo "This file was created by user1" > /tmp/testfile1
$ cat /tmp/testfile1
This file was created by user1

ログアウトして, user2 としてログインし直します. user1 と同様に操作を行ってください(図8).

■図8 user2 でログインし, /tmp/testfile2 を作成
$ echo "This file was created by user2" > /tmp/testfile2
$ cat /tmp/testfile2
This file was created by user2

ログアウトして, root としてログインし直します. ccs-editpolicy を起動して,「 <kernel> /sbin/mingetty /bin/login /bin/bash /bin/cat 」ドメインのアクセス許可を確認してください.図9のようなエントリが含まれているはずです.

■図9 <kernel> /sbin/mingetty /bin/login /bin/bash /bin/cat ドメインのアクセス許可
allow_read /tmp/testfile1
allow_read /tmp/testfile2

このままではどちらのファイルもDAC(任意アクセス制御)のパーミッションにより許可されれば誰でも読むことができてしまいます.ここで,キーボードの「 A 」を押して,図10のように入力してください.

■図10 アクセス許可を追加
(※これらをまとめて, allow_read /tmp/testfile\+ if task.uid=path1.uid のように入力してもかまいません.)
allow_read /tmp/testfile1 if task.uid=path1.uid
allow_read /tmp/testfile2 if task.uid=path1.uid

それから,図11の内容をキーボードの「 D 」を押して削除します.

■図11 削除する内容
allow_read /tmp/testfile1
allow_read /tmp/testfile2

キーボードの「 Q 」を押して, ccs-editpolicy を終了します.そして, /bin/cat コマンド用のドメインに強制用プロファイルを割り当ててください.この連載では強制用プロファイルの番号は3ですので図12のようになります.

■図12 cat コマンド用ドメインに強制用プロファイルを割り当て
# /usr/sbin/ccs-setprofile 3 '<kernel> /sbin/mingetty /bin/login /bin/bash /bin/cat'

ログアウトして, user1 としてログインし直します.そして, /tmp/testfile1 と /tmp/testfile2 を /bin/cat コマンドで表示してみてください. /tmp/testfile2 へのアクセスが禁止されていることを確認できます(図13).

■図13 /tmp/testfile2 へのアクセスが禁止されている
$ cat /tmp/testfile1
This file was created by user1
$ cat /tmp/testfile2
cat: /tmp/testfile2: Operation not permitted

ログアウトして, user2 としてログインし直します. user1 と同様に操作を行ってください. /tmp/testfile1 へのアクセスが禁止されていることを確認できます.変数の代わりにユーザIDを直接指定することもできます.たとえば,ユーザ認証を行ってログインシェルを起動するための「 <kernel> /sbin/mingetty /bin/login 」ドメインに対して,図14のように変更してやると, root ユーザのログインを禁止することができます.

■図14 root ユーザのログインを禁止する設定
[修正前]
allow_execute /bin/bash
[修正後]
allow_execute /bin/bash if task.uid!=0

また,図15のように変更してやると,ユーザIDが 500 以上 1000 以下のユーザだけがログインできるようにすることができます.SSH経由でのログインを制限したい場合に使うことができます.

■図15 ユーザID 500 以上 1000 以下のユーザのみログインできる設定
[修正前]
allow_execute /bin/bash
[修正後]
allow_execute /bin/bash if task.uid=500-1000

以上で条件付きアクセス許可の説明は終わりです.この説明のために作成したアカウントを削除します(図16).

■図16 図3で作成したアカウントを削除
# userdel user1
# userdel user2

プログラムの起動方法による振る舞いの変化について

ちょっと横道に逸れますが,ここでアクセス制御を行う場合に注意すべき点として,プログラムの起動方法による振る舞いの変化について触れておきたいと思います.システム( TOMOYO Linux カーネルで動作しているシステムである必要はありません)にログインして,図17のプログラムを /tmp/argv0.c という名前で保存してください.

■図17 起動方法を表示するCプログラム
#include <stdio.h>
int main(int argc, char *argv[])
{
        printf("I am running as %s\n", argv[0]);
        return 0;
}

その後,図18のようにコンパイルしておよびシンボリックリンク(またはハードリンク)を作成してください.

■図18 コンパイルしてシンボリックリンクを作成する
# gcc -o /tmp/argv0 /tmp/argv0.c
# ln -s /tmp/argv0 /tmp/cat
# ln -s /tmp/argv0 /tmp/passwd

そして,そのシンボリックリンク(またはハードリンク)を実行してください.すると,シンボリックリンクの名前が表示されます(図19).

■図19 シンボリックリンク経由で起動する
# /tmp/cat
I am running as /tmp/cat
# /tmp/passwd
I am running as /tmp/passwd

一部のコマンドは,そのコマンドが起動されたときの「名前( argv[0] と呼ばれているものです) 」によって振る舞いを変えるように作られています.たとえば, CentOS 5 において, /sbin/pidof は /sbin/killall5 へのシンボリックリンクであり, pidof という名前で起動された場合には指定されたプログラムのプロセスIDを表示し, killall5 という名前で起動された場合には自分のセッション以外のすべてのプロセスにシグナルを送信します.

TOMOYO Linux では /tmp/cat を実行すれば /tmp/cat のドメインで動作し, /tmp/passwd を実行すれば /tmp/passwd のドメインで動作します.では,図20のように /tmp/cat という名前で /tmp/passwd を実行した場合には,どのようになるでしょうか?(結果を予想してみてください)

■図20 passwd コマンドを cat という名前で実行する
# sh -c 'exec -a /tmp/cat /tmp/passwd'

図20の実行結果は図21となります.このことから,図20を認めることは図22を認めることと同じです.ただし,図22は /tmp/cat ドメインで /tmp/cat として動作するのを認めているのに対して,図20は /tmp/passwd ドメインで /tmp/cat として動作することを認めているという点に注意してください.

■図21 cat コマンドとして動作する
I am running as /tmp/cat
■図22 図20がもたらす結果
# sh -c 'exec /tmp/cat'

/usr/bin/passwd コマンドと /bin/cat コマンドが /sbin/busybox のハードリンクまたはシンボリックリンクとして提供されていると仮定します.この場合, TOMOYO Linux では /bin/cat を実行すれば /bin/cat のドメインで動作し, /usr/bin/passwd を実行すれば /usr/bin/passwd のドメインで動作します.パスワードを変更するための /usr/bin/passwd ドメインには /etc/shadow へのアクセスを認める必要がありますが,ファイルの内容を表示するための /bin/cat ドメインには /etc/shadow へのアクセスを認めたくありません.では,図23のように /bin/cat という名前で /usr/bin/passwd を実行した場合には,どのようになるでしょうか?

■図23 passwd コマンドを cat という名前で実行する
# sh -c 'exec -a /bin/cat /usr/bin/passwd /etc/shadow'

図23を実行すると, /usr/bin/passwd は /usr/bin/passwd ドメインで動作します.しかし,起動時の名前が /bin/cat であったため,その振る舞いは /bin/cat となります.結果として図24のような振る舞いを許可してしまったことになります.

■図24 パスワードファイルを閲覧できる
# sh -c 'exec /bin/cat /etc/shadow'

このような脅威を防ぐために, TOMOYO Linux ではプログラムのパス名と argv[0] の組み合わせをチェックする機能を備えており,プログラムの実行許可に対しては自動的に exec.realpath および exec.argv[0] という形でシンボリックリンクを解決したパス名と argv[0] も一緒にチェックするようになっています.図23のような脅威も /usr/bin/passwd コマンドを /bin/cat という名前で起動することを禁止することで防ぐことができるようになります.

アクセスログからポリシーを生成する(上級者向け)

学習モードで生成されるポリシーに含まれているアクセス許可には exec.realpath と exec.argv[0] と symlink.target だけしか含まれていませんが,アクセスログには図1で示した全ての変数が含まれています.そのため,アクセスログからポリシーを生成してやれば,最も厳格な条件付きアクセス許可を定義することができます.

自動的にアクセス許可を追加しないように設定する

学習モードでは,一度ポリシー違反が発生すると,そのポリシー違反を起こさないようにするために必要なアクセス許可が自動的に追加されてしまいます.しかし,自動的に追加されるアクセス許可には図1で示した全ての情報が含まれているわけではありません.そのため,プロファイルを図25のように編集して自動的に追加されないようにします.

■図25 /etc/ccs/profile.conf の変更箇所
[修正前]
PREFERENCE::learning={ verbose=no }
[修正後]
PREFERENCE::learning={ verbose=no max_entry=0 }

保存したら,図26のコマンドを実行して変更を反映してください.

■図26 変更を反映する
# /usr/sbin/ccs-loadpolicy p

プログラムを動作させる

この状態でプログラムを動作させます.ポリシー違反は全てアクセスログとして保存されていきます.

図25で max_entry=0 という指定をしたため,図27のようなメッセージが大量に表示されますが無視してください.

■図27 学習する件数の上限に達したことを示すメッセージ
WARNING: Domain 'ドメイン名' has so many ACLs to hold. Stopped learning mode.

アクセスログから必要な部分を抽出する

grep を用いて mode=learning を含むログを抽出します(図28).前後に空白を含めていることに注意してください.空白を入れ忘れると,ファイル名などと一致してしまう可能性があります.

■図28 学習モードのアクセスログだけを抽出する
# grep -F -A 2 " mode=learning " /var/log/tomoyo/reject_log.conf > /var/log/tomoyo/learning_log.txt

抽出したら,条件付きのアクセス許可に変換します(図29).

■図29 条件付きのアクセス許可に変換する
# /usr/lib/ccs/convert-audit-log < /var/log/tomoyo/learning_log.txt > /var/log/tomoyo/policy.tmp

変換したら,ドメイン単位でソートすることにより圧縮します(図30).

■図30 ドメイン単位でソートする
# /usr/sbin/ccs-sortpolicy < /var/log/tomoyo/policy.tmp > /var/log/tomoyo/policy.txt

加工してからポリシーとして追加する

/var/log/tomoyo/policy.txt はそのままドメインポリシーとして /etc/ccs/domain_policy.conf に追加することが可能ですが,図31のように条件が厳密すぎて使い物にならない(例えばプロセスIDやiノード番号などは毎回変わるため、指定しない方が良い)ので,テキストエディタを用いて条件を緩めて(不要と思われる条件部分を除去して)から追加してください.

■図31 厳密すぎるアクセス許可
allow_execute /usr/bin/id if task.pid=4641 task.ppid=4637 task.uid=48 task.gid=48 task.euid=48 task.egid=48 task.suid=48 task.sgid=48 task.fsuid=48 task.fsgid=48 task.state[0]=0 task.state[1]=0 task.state[2]=0 task.type!=execute_handler path1.uid=0 path1.gid=0 path1.ino=603159 path1.major=8 path1.minor=1 path1.perm=0755 path1.type=file path1.parent.uid=0 path1.parent.gid=0 path1.parent.ino=589834 path1.parent.perm=0755 exec.realpath="/usr/bin/id" exec.argc=1 exec.envc=7 exec.argv[0]="id" exec.envp["TERM"]="linux" exec.envp["PATH"]="/sbin:/usr/sbin:/bin:/usr/bin" exec.envp["PWD"]="/usr/share/horde/admin" exec.envp["LANG"]="en_US.UTF-8" exec.envp["SHLVL"]="3" exec.envp["LANGUAGE"]="en_US.UTF-8" exec.envp["_"]="/usr/bin/id"

次回予告

今回は条件付きアクセス許可について紹介しました.次回は, TOMOYO Linux の特徴である階層化されたドメイン遷移を利用して,近年急増しているSSHブルートフォース攻撃への対策と管理者権限の分割方法について紹介します.どうぞお楽しみに.

第4回へ戻る 第6回へ進む


目次へ戻る

sflogo.php