【図解】よく分かるSELinux入門〜仕組みとメリットを基礎から解説〜

スポンサーリンク
スポンサーリンク

SELinuxとは ~概要と仕組み~

SELinux とはSecure-Enhanced Linux の略で、セキュリティ機能を強化するモジュールです。SELinux では『プロセスがOS管理下のリソースへアクセスすることを監視・制御すること』ができます。

原理的にはプロセスは全てのOS管理下リソースへのアクセスを制御されますが、実装としてはファイルシステムへのアクセス、および、TCP/UDPポートの利用に関する制御が中心的に組み込み設定されています。

多くはアプリケーションからシステムコールを受ける際に、カーネル機能のLSM(Linux Security Module)でフックして許可ルールとの照合を行います。システムコールの量も許可ルールも多いため、照合はAVCというキャッシュで判断することで負荷を抑えています。

また、一部のSELinuxを意識して作られたアプリケーション(SELinux-Aware なアプリ)ではシステムコール経由ではなくアプリ自身がSELinuxのライブラリを使ってアクセス制御を行うこともできます。(ただしまだ実装は少ない。SE-postgreSQL等がある。)

SELinux のアーキテクチャ概要を以下に示します。

SELinuxでは全てのプロセス(Source)とOS管理リソース(Target)に”コンテキスト(context)”というラベルを付けます。プロセスのコンテキストを Source Context(scontext)、ファイル等のリソースのコンテキストをTarget Context(tcontext)と呼びます。

一方、SELinuxの許可ルールはAV(Access Vector)ルールと呼ばれ、Source Context から Target Context へどのような操作(Access Vector と言います)を許可するかを定めています。このAVルールで許可されていない場合、プロセスはリソースへのアクセスを拒否されます。

スポンサーリンク

コンテキストのフォーマットは [ User : Role : Type/Domain : Sensitivity range ] となっています。3つ目の属性は Source Context つまりプロセスのときはドメイン(Domain)と呼び、Target Context つまりファイル等のリソースのときはタイプ(Type)と呼びますが、基本的に呼び名が違うだけで扱い方は同じです。

本記事で扱う範囲

本記事ではSELinuxのデフォルト設定である「SELINUX=enforcing, SELINUXTYPE=targeted」を前提として解説します。

このモードでは、AVルール(アクセス許可ルール)はコンテキストの中でも3つ目の属性である "タイプ/ドメイン" のみを取り扱います。(User や Role や Sensitivity range は無視します)

FAQ

Q. SELinux ってデフォルト設定でもいいの?

A. デフォルトでも十分効果があります。

アクセス制御に使われるAccess Vector(AV)ルールは「httpdプロセスは具体的にどのようなディレクトリにアクセスしてよいか、どのようなTCPポートを使ってよいか」といった一般的な情報を元に作られています。

なので、プロセス(サービス)に関して特別な設定をしなければデフォルトでうまく動作するはずです。

ですが例えばhttpdによる公開ディレクトリを/var/www/htmlから変更したり、待受ポートをtcp:80,443 から変更するのであれば、それに対応したSELinux設定が必要となります。

昔よりもトラブルになることは少なくなりましたが、今でもバグフィックスとして許可ルール(ポリシールール)がアップデートで追加されています。

スポンサーリンク

Q. ポリシールールってどれくらいあるの?自分でも作らなきゃいけないの?

A. 20万以上あります。基本は自分で作らなくてもよいです

主要なポリシールールは大きく2種類あります。

1つはType Enforcementルールで、前述のAVルール(アクセス制御ルール)や後述の Type Transitionルール等を含むルールのことです。20万以上のルールがあります。

Type Enforcementルールは以下コマンドで確認できます。

sesearch --all

『コマンドが見つかりません』と表示された場合は『yum -y install setools-console』でパッケージをインストールします。AVルールの許可ルールだけ見たい場合は sesearch --allow を打ちます。

もう1つはLabelingルールです。例えば『このディレクトリ配下に新規作成されるfile/dirはこのコンテキストを割り当てる』といったファイルやディレクトリへのコンテキスト割当ルールを行うファイルLabelingルールがあります。このLabelingルールは6000ルール程度です。例えば

/tmp(/.*)?  all files  fileB_u:fileB_r:fileB_t:s0

というファイルLabelingルールがあった場合、/tmp自体、およびその配下の全ての種類のファイル(シンボリックリンクやディレクトリ、ブロックデバイスファイル等)が新規に作成された場合にこのコンテキストが割り当たります。新規ではなくとも、restorecon -RFv コマンドをすることによりこのルール通りにコンテキストが修復されます。

ファイルLabelingルールは sesearch コマンドでは表示されませんが以下のsemanageコマンドで確認できます。

semanage fcontext -l

『コマンドが見つかりません』と表示された場合は『yum -y install policycoreutils-python』でパッケージをインストールします。Labelingルールには他にも TCP/UDPポート番号とコンテキストを紐付けるネットワークLabelingルールもあり、これは以下のコマンドで確認できます。

semanage port -l

Labelingルールのイメージ図(紫の箇所)を以下に示します。

Q. SELinuxの実体はどこにあるの?

A. 設定は /etc/selinux および /var/lib/selinux 配下, ファイルコンテキストはファイルシステムの拡張属性, 動作中のバイナリは /sys/fs/selinux

定義やポリシー等のルールや設定は /etc/selinux および /var/lib/selinux 配下に存在しています。基本設定は /etc/selinux/config、中心となるポリシー(Reference Policy)は /etc/selinux/targeted/policy/policy.## になります。

また、ファイルやディレクトリのコンテキストはファイルシステムのメタ(i-node)領域にある拡張属性のsecurity.selinuxという属性に存在しています。拡張属性は getfattr コマンドで見ることができ、setfattr コマンドで設定することもできます。

[root@localhost ~] yum -y install attr
[root@localhost ~] touch test1.txt
[root@localhost ~] getfattr -m security.selinux -d test1.txt
# file: test1.txt
security.selinux="unconfined_u:object_r:admin_home_t:s0"
[root@localhost ~] setfattr -n security.selinux -v system_u:object_r:httpd_sys_content_t:s0 test1.txt
[root@localhost ~] getfattr -m security.selinux -d test1.txt
# file: test1.txt
security.selinux="system_u:object_r:httpd_sys_content_t:s0"

ただし、通常は "getfattr" コマンドではなく "ls -Z" を使い、"setfattr" コマンドではなく chcon を使います。

[root@localhost ~]# ls -Z test1.txt
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 test1.txt
[root@localhost ~]# chcon -t httpd_sys_rw_content_t test1.txt
[root@localhost ~]# ls -Z test1.txt
-rw-r--r--. root root system_u:object_r:httpd_sys_rw_content_t:s0 test1.txt

また、設定をファイルコンテキスト定義ルール通りに戻す場合は "restorecon -RFv" を使います。(R はディレクトリを再帰実行、Fは強制リセット、vは詳細出力)

[root@localhost ~]# restorecon -RFv test1.txt
restorecon reset /root/test1.txt context system_u:object_r:httpd_sys_rw_content_t:s0->system_u:object_r:admin_home_t:s0

Q. パーミッションの種類ってどれくらいあるの?

A. たくさん

パーミッション一覧は seinfo コマンドで見れます。

[root@localhost ~] yum -y install setools-console
[root@localhost ~] seinfo -c -x

例えばファイルへの操作とネットワークに関する操作では、許可する内容は全然違います。ファイルには読み込みという操作がありますがネットワークにはありません。つまり操作対象によって具体的な許可する操作内容が異なるのです。

操作対象のことをオブジェクトクラス、許可する操作内容をパーミッションと言います。

オブジェクトクラス数は100程度あり、パーミッション数は1000を超えます。

詳細はこちら。http://www.selinuxproject.org/page/ObjectClassesPerms

Q. SELinuxでは結局具体的にどんな効果が期待できるの?

A. 次の章以降で詳しく見ていきます。

スポンサーリンク

httpdでの例

httpd というプロセスは通常、httpd_t というドメインで起動しています。プロセスのコンテキストはpsの -Zオプションで確認できます。

[root@localhost ~] yum -y install httpd
[root@localhost ~] systemctl start httpd
[root@localhost ~]# ps auxZ | grep httpd_t
system_u:system_r:httpd_t:s0    root      1526  0.0  0.1 224024  5024 ?        Ss   23:51   0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0    apache    1527  0.0  0.0 224024  2956 ?        S    23:51   0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0    apache    1528  0.0  0.0 224024  2956 ?        S    23:51   0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0    apache    1529  0.0  0.0 224024  2956 ?        S    23:51   0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0    apache    1530  0.0  0.0 224024  2956 ?        S    23:51   0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0    apache    1531  0.0  0.0 224024  2956 ?        S    23:51   0:00 /usr/sbin/httpd -DFOREGROUND

一方、一般的なWebサーバのコンテンツの置き場所である /var/www/html にはどのようなルールが適用されるのでしょうか。

[root@localhost ~]# semanage fcontext -l | grep /var/www/html
/var/www/html(/.*)?/uploads(/.*)?                  all files          system_u:object_r:httpd_sys_rw_content_t:s0
/var/www/html(/.*)?/wp-content(/.*)?               all files          system_u:object_r:httpd_sys_rw_content_t:s0
/var/www/html(/.*)?/wp_backups(/.*)?               all files          system_u:object_r:httpd_sys_rw_content_t:s0
/var/www/html(/.*)?/sites/default/files(/.*)?      all files          system_u:object_r:httpd_sys_rw_content_t:s0
/var/www/html(/.*)?/sites/default/settings\.php    regular file       system_u:object_r:httpd_sys_rw_content_t:s0
/var/www/html/[^/]*/cgi-bin(/.*)?                  all files          system_u:object_r:httpd_sys_script_exec_t:s0
/var/www/html/munin(/.*)?                          all files          system_u:object_r:munin_content_t:s0
/var/www/html/cgi/munin.*                          all files          system_u:object_r:munin_script_exec_t:s0
/var/www/html/munin/cgi(/.*)?                      all files          system_u:object_r:munin_script_exec_t:s0
/var/www/html/owncloud/data(/.*)?                  all files          system_u:object_r:httpd_sys_rw_content_t:s0
/var/www/html/nextcloud/data(/.*)?                 all files          system_u:object_r:httpd_sys_rw_content_t:s0
/var/www/html/configuration\.php                   all files          system_u:object_r:httpd_sys_rw_content_t:s0

/var/www/htmlの1つ下のディレクトリにマッチするルールが表示されました。もう1つ上のディレクトリに関するルールを見てみると

[root@localhost ~]# semanage fcontext -l | grep /var/www | more
/var/www(/.*)?                                     all files          system_u:object_r:httpd_sys_content_t:s0
~~~省略~~~

このように表示されます。つまり、/var/www配下のコンテンツには "httpd_sys_content_t" というタイプがラベルされます(/.*)?は 正規表現で、ここでの意味は /var/www もしくは /var/www/配下の全てのfile/dirを指します。(.*は任意の文字列で、/も含めることができますので何階層下のファイルであってもこの表現に含まれます。)

ではhttpd_t というドメインから httpd_sys_content_t というタイプにはどのような操作を許可されているのでしょうか。

[root@localhost ~]# sesearch --allow | grep "allow httpd_t httpd_sys_content_t "
   allow httpd_t httpd_sys_content_t : dir { ioctl read getattr lock search open } ;
   allow httpd_t httpd_sys_content_t : lnk_file { read getattr } ;
   allow httpd_t httpd_sys_content_t : file { ioctl read getattr lock open } ;
   allow httpd_t httpd_sys_content_t : dir { ioctl read write getattr lock add_name remove_name search open } ;

4つの許可ルール(AVルール)が表示されました。1番目のルールは4番目のルールに内包されています。許可ルールは色々なモジュールからインストールされるため、このように重複することは頻繁に起こります。(だからルールの行数が増えるわけですが)

lnk_file はシンボリックリンクファイルのことです。readは汎用的なファイルアクセス許可、getattrは属性情報へのアクセス許可を意味します。シンボリックリンクはリンク先情報がメタ(属性)情報にありますのでgetattrが必要です。

file は一般的なファイルのことです。read や getattr は lnk_file と同様です。ioctl はIOドライバ(ここではDisk IOドライバ)が提供する機能へのアクセス許可を意味します。lockは排他、openはファイルディスクリプタを紐付けてファイル入出力(読み書き)が出来る状態にすることです。

dir はディレクトリのことです。fileのアクセス権に加え、ディレクトリへの汎用的な書き込みを意味するwrite、ディレクトリ配下へのファイル作成するadd_name、ファイル削除するremove_nameが許可されています。

これらがhttpdに与えられた、/var/www/html配下のファイルへの操作です(ただしそれよりも下位ディレクトリで、別の定義ルールに該当する場合は、別のコンテキストが割り当たるため、操作許可内容は異なります。)

スポンサーリンク

プロセスのドメインを決めるType Transition ルール

ところで、httpdのドメインが"httpd_t"である、というのはどのように決まっているのでしょうか。

実はこのドメインを決める要素がTypeEnforcement(TE)ルールにある Type Transition ルールです。httpdの実行ファイルは/usr/sbin/httpdですが、このファイルのタイプは httpd_exec_t です。

[root@localhost ~]# ls -Z /usr/sbin/httpd
-rwxr-xr-x. root root system_u:object_r:httpd_exec_t:s0 /usr/sbin/httpd

一方、Type Transition ルールには以下のようなルールがあります。

[root@localhost ~]# sesearch --type | grep httpd_exec_t
~~~
type_transition init_t httpd_exec_t : process httpd_t;
~~~

これは『init_t というドメインにいるプロセスが httpd_exec_t のタイプの実行ファイルを起動するとそのプロセスは httpd_t のドメインに遷移する』という意味です。systemd のプロセスのドメインが init_t であるため、起動時およびsystemctlコマンドでのhttpdサービス開始時は(systemdによる操作で起動するため)このルールに合致する訳です。

[root@localhost ~]# ps auxZ | more
LABEL                           USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
system_u:system_r:init_t:s0     root          1  0.0  0.1 193512  6612 ?        Ss   11:30   0:04 /usr/lib/systemd/systemd --switched-root --system --deserialize 22

このType Transition ルールに該当しないケースでは、起動を行ったプロセスのドメインを引き継ぎます。例えばrootユーザがログインやsu - によりbashにいるときのタイプは "unconfined_t" です。これは「制限なし」という意味で、SELinuxとしてはかなり自由に操作ができるタイプで、ログイン定義ルールによって予め決まっています。(後述しますが、権限を落とすように設定変更することも可能です。)

[root@localhost ~]# ps -Z
LABEL                              PID TTY          TIME CMD
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 4152 pts/0 00:00:00 bash

この状態で (systemctl ではなく) 直接httpdを実行すると、httpdのコンテキストも "unconfined_t" になります。

[root@localhost ~]# ps auxZ | grep httpd
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 root 4315 0.0  0.0 224020 3488 ? Ss 22:27   0:00 /usr/sbin/httpd
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 apache 4316 0.0  0.0 224020 2956 ? S 22:27   0:00 /usr/sbin/httpd
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 apache 4317 0.0  0.0 224020 2956 ? S 22:27   0:00 /usr/sbin/httpd
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 apache 4318 0.0  0.0 224020 2956 ? S 22:27   0:00 /usr/sbin/httpd
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 apache 4319 0.0  0.0 224020 2956 ? S 22:27   0:00 /usr/sbin/httpd
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 apache 4320 0.0  0.0 224020 2956 ? S 22:27   0:00 /usr/sbin/httpd

スポンサーリンク

このあたりにSELinuxの目指したものが透けて見えます。つまり、一度 "httpd_t" というドメインで起動したhttpdプロセスは、例えこのプロセスが乗っ取られ、root権限に昇格され、シェルを起動されようが、子プロセスを fork しようが、(Type Transitionルールに無い限り、)ドメインは "httpd_t" から抜け出せないので、rootにも関わらず、出来ることは "httpd_t" に与えられたAVルール内に限られるのです。(ただし、"unconfined_t" のプロセスを乗っ取られたらSELinuxを有効にした意味が無くなりますので、絶対にログインユーザのシェルから直接起動してはいけません。)

SELinuxってプロセスが乗っ取られてrootに昇格されても大丈夫っていうけど、プロセスが乗っ取られたらSELinuxを無効にされないの?』という疑問に対する答えがこれです。

SELinuxの思想 ~DACとMAC~

既に答えの根拠は出していますが、SELinux は攻撃そのものを防ぐことを目指しているわけではありません。攻撃された結果、プロセスが乗っ取られたりroot権限を奪取された場合においても、そのプロセスをMAC(Mandatory Access Controll)による強制アクセス制御下に置き、行動を制限させることを目的としています。

このSELinuxによるMAC(強制アクセス制御)は、今までのファイルシステムのDAC(任意アクセス制御)とは軸が異なります。

ファイルシステムのDACは各ユーザによりアクセス権を変更できるためこのように呼ばれますが、それ以外にrootへの権限付与を行いすぎた、という反省があります。つまり、rootになればそのシステムを自由に操り法律を決める神様になれるのですが、プログラムの脆弱性によりリモートからrootへ昇格するクラッキング行為が幾度と行われてきました。

SELinuxによるアクセス制御は単にrootであるだけでは変更できません。システムの神様になるには、root権限かつ "unconfined_t" のドメインである必要があるのです。

なのでポイントとして、攻撃者がパスワードクラックしてsshによるログインをされた場合は攻撃を制限できません。効果があるのはあくまでhttpd等で動かしているプロセスをバッファオーバーフロー等で乗っ取られた時の話です。

ログインユーザの操作をSELinuxで制限する

前述の通り、デフォルトではシェルから起動すると "unconfined_t" という無制限のドメインで動作させてしまう危険性があります。これは一般ユーザであってもそうなります。

これを制限するには、ログイン時のドメインを "unconfined_t" から変更する必要があります。これを実現するためには、『ローカルアカウントユーザ』を『SELinuxユーザ(コンテキストの一番最初の属性)』と紐付けることが必要です。

Linuxログイン時のデフォルトSELinuixユーザ(__default__)は "unconfind_u" というユーザです。

[root@localhost ~]# semanage login -l
ログイン名                SELinux ユーザー         MLS/MCS 範囲           サービス
__default__          unconfined_u         s0-s0:c0.c1023       *
root                 unconfined_u         s0-s0:c0.c1023       *
system_u             system_u             s0-s0:c0.c1023       *

__default__ となっているのがデフォルト値で、全ユーザが "unconfind_u" に紐付けられています。そしてこのSELinuxユーザのロールは "unconfined_r" に紐付けられています。

[root@localhost ~]# semanage user -l
                ラベリング      MLS/       MLS/
SELinux ユーザー    プレフィックス    MCS レベル    MCS 範囲                         SELinux ロール
guest_u         user       s0         s0                             guest_r
root            user       s0         s0-s0:c0.c1023                 staff_r sysadm_r system_r unconfined_r
staff_u         user       s0         s0-s0:c0.c1023                 staff_r sysadm_r 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
unconfined_u    user       s0         s0-s0:c0.c1023                 system_r unconfined_r
user_u          user       s0         s0                             user_r
xguest_u        user       s0         s0                             xguest_r

そしてログイン時のドメインはロールの _r を _t に変更したものになります。

では実際に testuser を unconfined_u ではないユーザに紐づけて見ましょう。

[root@localhost ~]# semanage login -a -s staff_u testuser
[root@localhost ~]# semanage login -l

ログイン名                SELinux ユーザー         MLS/MCS 範囲           サービス

__default__          unconfined_u         s0-s0:c0.c1023       *
root                 unconfined_u         s0-s0:c0.c1023       *
system_u             system_u             s0-s0:c0.c1023       *
testuser             staff_u              s0-s0:c0.c1023       *

この状態で su - testuser をしてもドメインは変わりません。なぜならもともと unconfined_t で動作しているシェルだからです。

[root@localhost ~]# su - testuser
最終ログイン: 2018/07/01 (日) 21:58:14 JST日時 pts/0
[testuser@localhost ~]$ id -Z
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

そこで新たなSSHセッションで接続し、testuser でログインします。すると無事、ドメインがstaff_tに変わりました。

[testuser@localhost ~]$ id -Z
staff_u:staff_r:staff_t:s0-s0:c0.c1023
スポンサーリンク
スポンサーリンク
スポンサーリンク

シェアする

  • このエントリーをはてなブックマークに追加

フォローする

スポンサーリンク
スポンサーリンク