概念上の部分
SELinuxの基本指針は侵入の防御でなく、侵入されても余計な権限を与えず被害範囲を最低限にすることです。すなわちバッファオーバーフロー等の攻撃には有効ですが、パスワードの漏洩等でsshなどを利用しログインされた場合にはSELinuxは機能しません。
読み(r)書き(w)実行(x)のパーミッションはDAC(Discretionary Access Control)と呼ばれ、Linuxを触った人々は知らない人はいないでしょう。
SELinuxではプロセスとリソースにSELinuxコンテキストと呼ばれるセキュリティーラベルを割り当て、このラベルに基づいて安全を確保しようとする。これをMAC(Mandatory Access Control)と呼ぶ。
加えて言うとRBAC (ロールベースアクセス制御)、TE(Type Enforcement)、およびオプションでMLS (Multi-Level Security) の組み合わせで実行される。MLSについてはオプションで、高度なセキュリティを求める場合に細かく指定するようだ。特に行政や国防などで設定することを想定しているとか。
SELinuxのポリシールールは大きく”Type Enforcement ルール“と”Labeling ルール“の2つに分けられます。
“Type Enforcement ルール”はAV(Access Vector)ルールやType Transition ルールなどを指します。AVルールはアクセス許可ルールとも言います。
“Labeling ルール”はリソースに対するコンテキストの割り当てルールです。
AVルールについては”search -A“コマンドで、Type Transition ルールについては”search -T“コマンド、Labeling ルールは”semanage fcontext -l“で確認できます。
# "AVルール"の確認
search -A
# "Type Transition ルール"の確認
search -T
# "Labeling ルール"の確認
semanage fcontext -l
ちなみに”getfattr”でファイルの拡張属性を表示でき、ファイルのSELinuxコンテキストはこの拡張属性に保存されている。
getattr -n security.selinux FILE_PATH
# 例えば、"security.selinux="system_u:object_r:etc_t:s0"
コンテキストの読み方
コンテキストは”SELinux:ロール:タイプ:レベル”で表現される。
コンテキストの確認方法は以下の通りです。
# ファイルのコンテキスト
ls -lZ
# 例えば、"unconfined_u:object_r:user_home_t:s0"などと出力される
# プロセスのコンテキスト
ps -Z
# 例えば、"system_u:system_r:httpd_t:s0"などと出力される
# ログイン中のユーザのコンテキスト
id -Z
# 例えば、"unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023"などと出力される
ユーザのコンテキスト
OS(Linux)のユーザはSELinuxユーザにマッピングされます。
SELinuxユーザの一覧は”sudo semanage user -l”で確認できます。各SELinuxユーザに割り当てられているロールは右端に示されるものとなります。
OSユーザに割り当てられているSELinuxユーザは”sudo semanage login -l”で確認できます。記述の無いユーザは”__default__”がマッピングされます。
ここからがちょっとよく分からないところ。
ログイン時のドメインはロールの”_r”を”_t”に書き換えたものになる、そうです。
注意したいのが、”su – other_user”などとしても前後でドメインは変わりません。元々(ログイン時)のシェルのドメインを継承するから、だと思います。
sudo semanage user -l
# SELinux ユーザー ラベリングプレフィックス MLS/MCSレベル MLS/MCS範囲 SELinuxロール
# unconfined_u user s0 s0-s0:c0.c1023 system_r unconfined_r
# sysadm_u user s0 s0-s0:c0.c1023 sysadm_r
# system_u user s0 s0-s0:c0.c1023 system_r unconfined_r
sudo semanage login -l
# ログイン名 SELinux ユーザー MLS/MCS 範囲 サービス
# __default__ unconfined_u s0-s0:c0.c1023 *
# root unconfined_u s0-s0:c0.c1023 *
ちなみにですが、ログイン時に割り当てられるロール(?)はログイン時の手段などによって変わるらしいです。
その定義がどこかの設定ファイルに載っていたはずなんですが、忘れてしまいました。“/etc/selinux/”あたりか?
リソースに対するプロセスのアクセス権
Nginxの動きを見てみよう。イメージとしてはNginxを介してWordpress等でサイトを運営していたとします。
以下のコマンドでNginxのドメインを確認すると”httpd_t”となっています。
ps auxZ | grep nginx
# system_u:system_r:httpd_t:s0 root 5669 0.0 0.0 12852 160 ? Ss 7月21 0:00 nginx: master process /usr/sbin/nginx
# system_u:system_r:httpd_t:s0 nginx 5670 0.0 0.3 17964 3120 ? S 7月21 0:05 nginx: worker process
# system_u:system_r:httpd_t:s0 nginx 5671 0.0 0.8 17940 6520 ? S 7月21 0:05 nginx: worker process
WordPressで利用するファイルに与えられるSELinuxコンテキストを以下のコマンドで確認してみましょう。ファイルのタイプは”httpd_sys_content_t”となっています。
先ほどの”httpd_t”と”httpd_sys_content_t”に何の関係があるのでしょうか?
sudo semanage fcontext -l |grep /var/www
# /var/www(/.*)? all files system_u:object_r:httpd_sys_content_t:s0
“sesearch –allow”コマンドでAVルールの内、“許可する“ルールが表示されます。
以下の出力結果としては許可ルールが2つ出力されました。
2行とも「”httpd_t”ドメインから”httpd_sys_content_t”タイプのリソースに対してどんな操作(”:”以降)を許可する」旨を示しています。
1行目は”ディレクトリに対してIOドライバが提供する機能”、”排他”、”ファイルの読み込み”を許可しています。
2行目はシンボリックリンクに対して”属性情報の読み込み”、”ファイルの読み込み”を許可しています。
sudo sesearch --allow |grep "httpd_t httpd_sys_content_t"
# allow httpd_t httpd_sys_content_t:dir { ioctl lock read };
# allow httpd_t httpd_sys_content_t:lnk_file { getattr read };
プロセスのドメイン遷移
いわゆる”Type Transitionルール”のお話です。
Nginx実行ファイルのSELinuxコンテキストの内”httpd_exec_t”ドメインとなっています。
ls -Z /usr/sbin/nginx
# system_u:object_r:httpd_exec_t:s0 /usr/sbin/nginx
ルールから”httpd_exec_t”に関するものを検索したモノの内、以下のような出力があります。
これは”init_t”ドメインのプロセスが”httpd_exec_t”タイプの実行ファイルを実行すると”httpd_t”でプロセスが起動します。こういった挙動をドメイン遷移といいます。
sudo sesearch -T | grep httpd_exec_t
# type_transition init_t httpd_exec_t:process httpd_t;
動作環境ではsystemdでNginxを起動しています。実際にsystemdのドメインを確認してみると、まさしく”init_t”でした。
system_u:system_r:init_t:s0 root 1 0.0 0.6 176872 5276 ? Ss 7月20 0:09 /usr/lib/systemd/systemd --switched-root --system --deserialize 31
危険行為!!!
sudoコマンドなどで直接Nginxを起動(以下のような実行)をした場合、実行シェルのドメインは多くの場合”unconfined_t”になるかと思います。この”unconfined_t”は無制限を意味します。
この状態でNginxを直接起動してしまった場合、Nginxのコンテキストも”unconfined_t”となり無制限なプロセスとなります。
結果としてプロセスの乗っ取りが行われた場合あらゆるリソースにアクセス可能となってしまい権限昇格などにより被害が拡大してしまいます。
/usr/sbin/nginx
ちなみに、SELinux Bool値の場合
SELinuxブール値は例えば以下のようなものです。
- httpd_can_network_connect
- httpd_can_network_connect_cobbler
- httpd_can_network_relay
これはAVルールを複数まとめて管理者がOn/Offの切り替えだけでルールの有効化・無効化を可能にしたものである。
特定のSELinuxブール値の詳細なルールについては以下のコマンドで確認できます。出力結果は以下の形式で出力されます。
“RULE_STATUS:RULE_KIND:DOMAIN:TYPE:CLASS:OPS_KIND:SELinux Boolean”
- RULE_STATUS:有効や無効を示す2文字(例:”DT”)
- RULE_KIND:”allow”/”auditallow”/”dontaudit”(アクセス許可やログ記録の必要に応じて選択)
- DOMAIN:プロセスのドメイン
- TYPE:リソースのタイプ
- CLASS:リソースの種類
- OPS_KIND:読み込み/書き込み/属性値読み込み等(複数ある場合は”{}”で囲む)
- SELinux Boolean:そのルールに割り当てられたSELinuxブール値(”[]”で囲む)
RULE_KINDは3種類の選択肢があるが、”ルールで定義されていない全てのアクセスは許可せず監査ログに記録”されるため計4パターンの挙動が存在する。
- “allow”:アクセスを許可し、ログに記録しない
- “auditallow”:アクセスを許可し、ログに記録する
- “dontaudit”:アクセスを拒否し、ログに記録しない
sesearch --allow --auditallow -C -b BOOLEAN_NAME
# 以下"httpd_can_network_connect"の例
sudo sesearch --allow --auditallow -b httpd_can_network_connect
# allow httpd_suexec_t client_packet_type:packet recv; [ httpd_can_network_connect ]:True
# allow httpd_suexec_t client_packet_type:packet send; [ httpd_can_network_connect ]:True
# allow httpd_suexec_t httpd_suexec_t:tcp_socket { accept listen }; [ httpd_can_network_connect ]:True
# allow httpd_suexec_t port_type:tcp_socket name_connect; [ httpd_can_network_connect ]:True
# allow httpd_suexec_t port_type:tcp_socket { recv_msg send_msg }; [ httpd_can_network_connect ]:True
# allow httpd_suexec_t port_type:udp_socket recv_msg; [ httpd_can_network_connect ]:True
# allow httpd_suexec_t port_type:udp_socket send_msg; [ httpd_can_network_connect ]:True
# allow httpd_sys_script_t client_packet_type:packet recv; [ httpd_can_network_connect && httpd_enable_cgi ]:True
# allow httpd_sys_script_t client_packet_type:packet send; [ httpd_can_network_connect && httpd_enable_cgi ]:True
# allow httpd_sys_script_t httpd_sys_script_t:tcp_socket { accept listen }; [ httpd_can_network_connect && httpd_enable_cgi ]:True
# allow httpd_sys_script_t node_t:tcp_socket node_bind; [ httpd_can_network_connect && httpd_enable_cgi ]:True
# allow httpd_sys_script_t node_t:udp_socket node_bind; [ httpd_can_network_connect && httpd_enable_cgi ]:True
# allow httpd_sys_script_t port_type:tcp_socket name_connect; [ httpd_can_network_connect && httpd_enable_cgi ]:True
# allow httpd_sys_script_t port_type:tcp_socket { recv_msg send_msg }; [ httpd_can_network_connect && httpd_enable_cgi ]:True
# allow httpd_sys_script_t port_type:udp_socket recv_msg; [ httpd_can_network_connect && httpd_enable_cgi ]:True
# allow httpd_sys_script_t port_type:udp_socket send_msg; [ httpd_can_network_connect && httpd_enable_cgi ]:True
# allow httpd_t port_type:tcp_socket name_connect; [ httpd_can_network_connect ]:True
上記出力結果の内、”port_type”のようにタイプ名の末尾が”_t”でないものがあります。これはアトリビュートと呼ばれ、複数のタイプやドメインをまとめたものになります。
DockerでSELinuxってどうなの?
ちょっと詳しくは分からない。
ただあるバージョン以降ではDockerデーモンの実行にオプションで有効にするフラグがあるとかなんとか。
何はともかく有効にはできるそうだ。