Developer's Blog

理由がわかれば怖くない!SELinux とのつきあい方

こんにちは。BoltzEngine 担当の伊藤です。

SELinux といえば Linux に強制アクセス制御(MAC)という機能を追加するモジュールで、Linux をより安全に運用することができる機能です。

Linux をインストールするとディストリビューションによっては最初から ON になっていて、一部のソフトウェアがこれが原因で動作しないとか、設定ファイルを外部からアップロードして上書きしたら読み込めなくなってプロセスが起動しなくなった…というようなつまづきをされる方が多いですし、実際自分自身もよくひっかかりました。SELinux で Google 検索すると、サジェストの一番頭が「無効」というワードが続いてしまいますし、検索して出てくる情報も無効にする方法の数が圧倒的に多い状態です。

しかし昨今様々な攻撃がある中、攻撃からの防御を考えて有効にして動かしたいですよね。今回は実際の SELinux のさわり方を確認しながら、どのように付きあっていくかを見ていきたいと思います。

※以下、Red Hat Enterprise Linux 7 (RHEL7) 系列の OS について説明していきます。また、一部のコマンドは出力が長いため、省略されているものがあります。

SELinux の動作モード

SELinux には以下の 3 つの動作モードがあるというのはご存じの方は多いと思います。

  • Enforcing: SELinux 有効。ルール外の動作があれば止める。
  • Permissive: SELinux 有効。ただしルール外の動作はログに記録するのみ。
  • Disable: SELinux 無効。

SELinux を使う場合は、Permissive で構築・テストをしてルール外の動作に対して 1 つずつ例外設定を入れていき、運用開始するときに Enforcing に変えるという方法がよく言われています。

では、実際に今使っている Linux の SELinux はどういう状態なのかというのは getenforce コマンドで知ることができます。「何かおかしい!設定も全部合ってるはずなのに!」というときはまず実行するコマンドですね。

$ getenforce
Enforcing

これが Enforcing だった場合、ひとまず Permissive に変えて様子を見てみることになります。一時的に SELinux の設定を変えるには setenforce コマンドを使います。

# setenforce Permissive
$ getenforce
Permissive

Permissive の状態で問題なく動作するようであれば、SELinux の規定外の動作が起こっているということになりますので、ログを見て対応していくことになります。

もちろん、Permissive の状態から Enforcing にすることもできます。

# setenforce Enforcing
$ getenforce
Enforcing

再起動しても有効になるようにデフォルト設定を変更するには、/etc/selinux/config を書き換えます。

$ cat /etc/selinux/config

# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=enforcing
# SELINUXTYPE= can take one of three two values:
#     targeted - Targeted processes are protected,
#     minimum - Modification of targeted policy. Only selected processes are protected. 
#     mls - Multi Level Security protection.
SELINUXTYPE=targeted

SELinux コンテキスト

SELinux が有効の状態では、ファイルは通常 Linux で扱われるパーミッション (777 とか 644 などで表されるアレです) のほかに、SELinux コンテキストを持ちます。また、プロセスも同様に SELinux コンテキストを持ちます。SELinux コンテキストは単にラベルとも呼ばれます。

SELinux コンテキストは ls コマンドや ps コマンドに -Z オプションをつけることで確認できます。 (※以下のコマンドは出力が長いので一部省略しています)

$ ls -Z
drwxr-xr-x. root root system_u:object_r:var_t:s0       adm
drwxr-xr-x. root root system_u:object_r:var_t:s0       cache
drwxr-xr-x. root root system_u:object_r:kdump_crash_t:s0 crash
drwxr-xr-x. root root system_u:object_r:system_db_t:s0 db
drwxr-xr-x. root root system_u:object_r:var_t:s0       empty
drwxr-xr-x. root root system_u:object_r:games_data_t:s0 games
drwxr-xr-x. root root system_u:object_r:var_t:s0       gopher
drwxr-xr-x. root root system_u:object_r:var_t:s0       kerberos
drwxr-xr-x. root root system_u:object_r:var_lib_t:s0   lib
drwxr-xr-x. root root system_u:object_r:var_t:s0       local
$ ps -efZ
LABEL                           UID        PID  PPID  C STIME TTY          TIME CMD
system_u:system_r:chronyd_t:s0  chrony     514     1  0  9月16 ?      00:00:00 /usr/sbin/chronyd
system_u:system_r:syslogd_t:s0  root       521     1  0  9月16 ?      00:00:00 /usr/sbin/rsyslogd -n
system_u:system_r:systemd_logind_t:s0 root 522     1  0  9月16 ?      00:00:02 /usr/lib/systemd/systemd-logind
system_u:system_r:gssproxy_t:s0 root       534     1  0  9月16 ?      00:00:00 /usr/sbin/gssproxy -D
system_u:system_r:crond_t:s0-s0:c0.c1023 root 568  1  0  9月16 ?      00:00:00 /usr/sbin/crond -n
system_u:system_r:sshd_t:s0-s0:c0.c1023 root 773   1  0  9月16 ?      00:00:00 /usr/sbin/sshd -D
system_u:system_r:httpd_t:s0    root       778     1  0  9月16 ?      00:00:11 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:mysqld_safe_t:s0 mysql   872     1  0  9月16 ?      00:00:00 /bin/sh /usr/bin/mysqld_safe --basedir=/usr

コンテキストは : で区切られた 4 つの項目からなり、それぞれユーザー、ロール、タイプ、機密ラベルとなっています。

selinux_label

まずはタイプに注目して見ていくと良いでしょう。

SELinux が原因で動かないのはなぜか

SELinux で動作が拒否された場合、/var/log/audit/audit.log にログが出力されます。

例えば、BoltzMessenger で SELinux ポリシーパッケージ未導入の場合、通知送信時に以下のようなログが出力されます。

type=AVC msg=audit(1461488763.342:856):
avc:  denied  { name_connect }
for  pid=6104 comm="httpd" dest=13070
scontext=system_u:system_r:httpd_t:s0
tcontext=system_u:object_r:unreserved_port_t:s0
tclass=tcp_socket

scontext が source context で操作元のプロセスのコンテキスト、tcontext が target context で操作先のオブジェクトのコンテキスト、ブレースの中が実行しようとした動作です。このログは httpd_t から unreserved_port_t に対して name_connect という操作をしようとしたら拒否された(denied)、ということになります。

SELinux が原因で思ったように動かないというのは、おおまかに以下のパターンに集約できます。

ルール外の動作をしようとした

ルールにないアクセスをしようとした場合に起こります。例えば、httpd_t という、HTTP サービスを提供するタイプのプロセスから var_lib_t というタイプのファイルを read した場合などはルールに定義がないため、拒否されます。

実際のルールは sesearch コマンドで見ることができます。(sesearch コマンドが見つからない場合は setools-console パッケージをインストールします)

$ sesearch --allow -t httpd_t  /etc/selinux/targeted/policy/policy.29
Found 758 semantic av rules:
   allow dirsrvadmin_unconfined_script_t httpd_t : process sigchld ; 
   allow dirsrvadmin_unconfined_script_t httpd_t : fd use ; 
   allow dirsrvadmin_unconfined_script_t httpd_t : fifo_file { ioctl read write getattr lock append } ; 
   allow dspam_script_t httpd_t : unix_stream_socket { ioctl read write getattr accept } ; 
   allow awstats_script_t httpd_t : unix_stream_socket { ioctl read write getattr accept } ; 
   allow cvs_script_t httpd_t : unix_stream_socket { ioctl read write getattr accept } ; 
   allow mojomojo_script_t httpd_t : unix_stream_socket { ioctl read write getattr accept } ; 
   allow bugzilla_script_t httpd_t : unix_stream_socket { ioctl read write getattr accept } ; 
   allow sssd_t nsswitch_domain : key { view read write search link setattr create } ; 
   allow sosreport_t domain : process getattr ; 
   allow systemd_logind_t domain : process { sigkill signull signal } ; 

httpd からデータベースへの接続など、一般的なものについては sebool というフラグ制御があり、getsebool / setsebool で設定値を変更するだけでもルールを追加できる場合があります。

# getsebool -a | grep httpd
httpd_anon_write --> off
httpd_builtin_scripting --> on
httpd_can_check_spam --> off
httpd_can_connect_ftp --> off
httpd_can_connect_ldap --> off
httpd_can_connect_mythtv --> off
httpd_can_connect_zabbix --> off
httpd_can_network_connect --> off
httpd_can_network_connect_cobbler --> off
httpd_can_network_connect_db --> off
httpd_can_network_memcache --> off

# setsebool httpd_can_connect_ftp on

それでも無理な場合のルールの定義はこの後、ポリシーファイルの作成のところで見ていきます。

ファイルを移動してパスとコンテキストが一致しなくなった

例えば、/var/www/html に配置するファイルを sftp でアップロードした後に mv で動かした場合、本来 /var/www/html にあるファイルが持つべきコンテキストと一致しなくなります。このファイルを http でアクセスすると Forbidden になってしまいます。

ls -Z で確認すると、user_home_t という、ホームディレクトリにあるファイルのタイプになっていることがわかります。

$ ls -Z /var/www/html
-rw-rw-r--. centos centos unconfined_u:object_r:user_home_t:s0 test.html

パスに応じて設定されているべき SELinux コンテキストの定義があるので、restorecon コマンドでこれに合わせることができます。ls -Z で確認すると httpd_sys_content_t というタイプに修正されたことがわかります。

# restorecon -R /var/www/html
$ ls -Z /var/www/html
-rw-rw-r--. centos centos unconfined_u:object_r:httpd_sys_content_t:s0 test.html

また、chcon コマンドでタイプを任意に変更することもできます。以下は /var/tmp/sample-cache を httpd から読み書きができるタイプである httpd_sys_rw_content_t に変更する場合です。

# chcon -R -t httpd_sys_rw_content_t /var/tmp/sample-cache

本来のサービス提供用ポートじゃないポートで動作させるようにした

最近では、攻撃されることを考えて SSH などの管理用ポートを別のポートに変更することが増えていると思います。この場合も、SELinux に本来定義されているタイプのポートではないため、拒否されることになります。

semanage コマンドで変更後のポートを対象のサービスのポートに設定することで、正しく扱えるようになります。

追加
# semanage port -a -t ssh_port_t -p tcp 2022

削除
# semanage port -d -t ssh_port_t -p tcp 2022

ポリシーファイルの作成

SELinux に新しいルールを追加するにはポリシーモジュールを作成する必要があります。selinux-policy-devel selinux-policy-doc パッケージを導入すると、sepolicy コマンドを使ってポリシーのテンプレートを作ることができるようになります。

$ touch boltzmessenger
$ sepolicy generate --application -n boltzmessenger_gateway boltzmessenger 
読み込んだプラグイン:fastestmirror
Created the following files:
/home/centos/ito/boltzmessenger_gateway.te # 強制ファイルの記入
/home/centos/ito/boltzmessenger_gateway.if # インターフェイスファイル
/home/centos/ito/boltzmessenger_gateway.fc # ファイルコンテキストファイル
/home/centos/ito/boltzmessenger_gateway_selinux.spec # スペックファイル
/home/centos/ito/boltzmessenger_gateway.sh # セットアップスクリプト

このテンプレートは記述したポリシーを RPM パッケージにビルドして追加・削除が簡単にできるようにしてくれている優れものです。

メインとなるファイルは .te ファイルです。例えば、独自のポートを定義して、httpd_t からそのポートに対する接続を許可する場合、以下のような内容になります。

policy_module(boltzmessenger_gateway 1.0)
require {
    type httpd_t;
    class tcp_socket name_connect;
    attribute port_type;
    attribute defined_port_type;
    attribute packet_type;
    attribute port_type;
    attribute exec_type;
    attribute server_packet_type;
    attribute client_packet_type;
    attribute unreserved_port_type;
    attribute corenet_unconfined_type;
}
type boltzmessenger_gateway_t;
domain_type(boltzmessenger_gateway_t)
type boltzmessenger_gateway_port_t, port_type;
type boltzmessenger_gateway_exec_t, exec_type;
allow httpd_t boltzmessenger_gateway_port_t:tcp_socket name_connect;

あとは、同時に生成されたスクリプトを実行すれば、ルールが生成されます。(生成後、インストールテストが走るので、root 権限が必要になります)

# ./boltzmessenger_gateway.sh

またこのスクリプトには、–update 引数をつけると、sepolicy generate コマンドの -n オプションでつけた名前と一致する名前のコマンドで発生した audit.log の拒否ログから一括でそれを許可する内容に .te ファイルを書き換える機能もあります。最初はこれを参考にして作っていくと良いでしょう。

さいごに

いかがでしたでしょうか。伊藤もきちんと SELinux と向き合ったのは最近でしたが、わかってしまえばうまく付きあっていけそうな気がしました。

攻撃の種類は本当に多様になってきていますので、SELinux を Disable で運用されている方はサーバーや情報を守る手段として SELinux をもう一度検討してみてはいかがでしょうか。

フェンリルのオフィシャル Twitter アカウントでは、フェンリルプロダクトの最新情報などをつぶやいています。よろしければフォローしてください!

Copyright © 2019 Fenrir Inc. All rights reserved.