

【CVE-2017-5638 / S2-045】SELinuxポリシーのバグフィックスでTomcatアプリのRCEは防げるようになったのか
プラットフォーム技術部の佐藤です。
だいぶ前の話になりますが、CentOS7/RHEL7のSELinuxポリシーのバグフィックスで、tomcatプロセスを制限のあるプロセス(confined)として動作させられるようになりました。
以前は制限のないプロセス(unconfined)の状態で、実質SELinuxのアクセス制御を受けない状態でした。Firewallのルールに例えると、制限なし(unconfined)は最後のルールがallow all、制限あり(confined)は最後のルールがdeny allな状態です。
ただ、実はこのバグフィックスだけではtomcatサービスが起動できない状態になってしまうので、そのまま適用するためには次のバグフィックスまで待つ必要がありました。
- RHBA-2018:0763 – Bug Fix Advisory
- Bug 1470735 – [regression] tomcat fails to start via tomcat-jsvc service startup due to selinux denials
これらのバグフィックスを受け、晴れて制限ありプロセスとして動作するようになったtomcatで、Web Application Frameworkの脆弱性であるリモートコード実行(RCE)が防げるようになったのか試してみようと思います。
環境
今回の検証環境は以下になります。
- CentOS Linux release 7.5.1804
- tomcat-7.0.76-6.el7.noarch
- selinux-policy-3.13.1-192.el7_5.3.noarch
- selinux-policy-targeted-3.13.1-192.el7_5.3.noarch
Web Application FrameworkはStruts 2.5.10を使用します。脆弱性は以下になります。
Web ApplicationはStruts2に同梱されているsturts2-showcase.warを使用します。
実験開始
まずは環境構築
最初にtomcatパッケージをインストールします。unzipはあとでstruts2のサンプルアプリケーション展開に使います。
[root@centos7 ~]# yum install tomcat unzip -y
strutsのサンプルアプリケーションを入手しデプロイします。
[root@centos7 ~]# curl -O -R https://archive.apache.org/dist/struts/2.5.10/struts-2.5.10-apps.zip
[root@centos7 ~]# unzip struts-2.5.10-apps.zip
[root@centos7 ~]# cp struts-2.5.10/apps/struts2-showcase.war /var/lib/tomcat/webapps/
tomcatを起動します。
[root@centos7 ~]# systemctl start tomcat
[root@centos7 ~]# systemctl is-active tomcat
active
SELinux無効状態でRCE動作確認
SELinux無効化状態でRCEできるか確認してみます。
[root@centos7 ~]# setenforce 0
[root@centos7 ~]# sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: permissive
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Max kernel policy version: 31
ではExploit Codeを実行してみます。
[root@centos7 ~]# ./struts2_S2-045.py http://localhost:8080/struts2-showcase/showcase.action 'head -1 /etc/passwd'
[*] CVE: 2017-5638 - Apache Struts2 S2-045
[*] cmd: head -1 /etc/passwd
root:x:0:0:root:/root:/bin/bash
はい。意図したとおりコード実行できました。
SELinux有効状態でRCE防げるか確認
では、いよいよSELinuxを有効にしてRCEを防げるか試してみます。
[root@centos7 ~]# setenforce 1
[root@centos7 ~]# sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Max kernel policy version: 31
Exploit Codeを実行してみます。
[root@centos7 ~]# ./struts2_S2-045.py http://localhost:8080/struts2-showcase/showcase.action 'head -1 /etc/passwd'
[*] CVE: 2017-5638 - Apache Struts2 S2-045
[*] cmd: head -1 /etc/passwd
root:x:0:0:root:/root:/bin/bash
おっと。残念ながら変わらず実行できてしまいました。せっかく制限つきプロセス(confined)になったのにどうしてでしょうか?
制限つきプロセス(confined)とはいっても許可されているものは実行できる状態なので、今回実行したheadコマンドは実行許可されており、/etc/passwdに対する読み込みも許可されているということなのでしょう。
setoolsを使ってtomcatのポリシーを調べてみます。
[root@centos7 ~]# yum install setools-console -y
先ほどのheadコマンドを確認してみます。
[root@centos7 ~]# ls -lZ /usr/bin/head
-rwxr-xr-x. root root system_u:object_r:bin_t:s0 /usr/bin/head
タイプはbin_tが付与されています。
tomcat_tに実行権限(execute)が付与されているタイプを調べるとこれだけありました。やはりbin_tは実行が許可されています。
[root@centos7 ~]# sesearch -A -s tomcat_t -c file -p execute
Found 13 semantic av rules:
allow tomcat_t tomcat_exec_t : file { ioctl read getattr lock execute execute_no_trans entrypoint open } ;
allow domain textrel_shlib_t : file { ioctl read getattr execute execmod open } ;
allow tomcat_domain shell_exec_t : file { ioctl read getattr lock execute execute_no_trans open } ;
allow domain lib_t : file { ioctl read getattr lock execute open } ;
allow tomcat_domain ldconfig_exec_t : file { ioctl read getattr lock execute execute_no_trans open } ;
allow tomcat_t pki_common_t : file { ioctl read write create getattr setattr lock append unlink link rename execute execute_no_trans open } ;
allow domain ld_so_t : file { ioctl read getattr execute open } ;
allow tomcat_domain base_ro_file_type : file { ioctl read getattr lock execute execute_no_trans open } ;
allow tomcat_domain tomcat_exec_t : file { ioctl read getattr lock execute execute_no_trans open } ;
allow tomcat_domain bin_t : file { ioctl read getattr lock execute execute_no_trans open } ;
allow domain abrt_helper_exec_t : file { read getattr execute open } ;
allow tomcat_domain rpm_exec_t : file { ioctl read getattr lock execute execute_no_trans open } ;
allow domain prelink_exec_t : file { ioctl read getattr lock execute execute_no_trans open } ;
※ sesearchの–directオプションで確認できる直接許可されているタイプはtomcat_exec_tとpki_common_tのみです。
上記タイプに属さない実行ファイルを探してみます。
[root@centos7 ~]# find /usr/bin -executable ! -context *:tomcat_exec_t:* ! -context *:textrel_shlib_t:* ! -context *:shell_exec_t:* ! -context *:lib_t:* ! -context *:ldconfig_exec_t:* ! -context *:pki_common_t:* ! -context *:pld_so_t:* ! -context *:base_ro_file_type:* ! -context *:tomcat_exec_t:* ! -context *:bin_t:* ! -context *:abrt_helper_exec_t:* ! -context *:rpm_exec_t:* ! -context *:prelink_exec_t:* | sort
/usr/bin/chage
/usr/bin/checkpolicy
/usr/bin/chfn
/usr/bin/chronyc
/usr/bin/chsh
/usr/bin/crontab
/usr/bin/dbus-daemon
/usr/bin/dmesg
/usr/bin/gpasswd
(略)
[root@centos7 ~]# ls -lZ /usr/bin/dmesg
-rwxr-xr-x. root root system_u:object_r:dmesg_exec_t:s0 /usr/bin/dmesg
dmesgが丁度よさそうです。ちなみにtomcatユーザーにスイッチした対話シェルはunconfinedになりますのでdmesgも実行できます。
[root@centos7 ~]# su - tomcat -s /bin/bash
Last login: Wed May 31 21:18:25 JST 2018 on pts/0
-bash-4.2$ ps -Z
LABEL PID TTY TIME CMD
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 9901 pts/0 00:00:00 bash
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 9916 pts/0 00:00:00 ps
-bash-4.2$ dmesg | head
[ 0.000000] Initializing cgroup subsys cpuset
[ 0.000000] Initializing cgroup subsys cpu
[ 0.000000] Initializing cgroup subsys cpuacct
[ 0.000000] Linux version 3.10.0-862.3.2.el7.x86_64 (builder@kbuilder.dev.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-28) (GCC) ) #1 SMP Mon May 21 23:36:36 UTC 2018
[ 0.000000] Command line: BOOT_IMAGE=/vmlinuz-3.10.0-862.3.2.el7.x86_64 root=UUID=c0915705-b51c-44bd-8d04-2cabf7f74c12 ro vconsole.keymap=jp106 crashkernel=auto vconsole.font=latarcyrheb-sun16 rhgb quiet LANG=en_US.UTF-8
[ 0.000000] e820: BIOS-provided physical RAM map:
[ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009ffff] usable
[ 0.000000] BIOS-e820: [mem 0x00000000000c0000-0x00000000000fffff] reserved
[ 0.000000] BIOS-e820: [mem 0x0000000000100000-0x000000003eee8fff] usable
[ 0.000000] BIOS-e820: [mem 0x000000003eee9000-0x000000003eef1fff] ACPI data
でもtomcat_domainで動くtomcatサービスにはdmesgを実行する権限がないのでRCEは防げるはず!
[root@centos7 ~]# ./struts2_S2-045.py http://localhost:8080/struts2-showcase/showcase.action '/usr/bin/dmesg'
[*] CVE: 2017-5638 - Apache Struts2 S2-045
[*] cmd: /usr/bin/dmesg
/bin/bash: /usr/bin/dmesg: Permission denied
はい。こちらはきちんと実行防止できました。audit.logではSELinuxで拒否された様子が確認できます。
type=AVC msg=audit(1527683087.236:643): avc: denied { getattr } for pid=9950 comm="bash" path="/usr/bin/dmesg" dev="sda4" ino=67319813 scontext=system_u:system_r:tomcat_t:s0 tcontext=system_u:object_r:dmesg_exec_t:s0 tclass=file
考察
今回の実験では、標準のSElinuxポリシーだけでtomcatで動作するWeb ApplicationのRCEを完全に防ぐには不十分ということがわかりました。 ただ、tomcatの権限はある程度制限されるようになりましたので、それだけでも被害を軽減する効果はあると思います。 例えば、ファイルシステムのパーミッション以上に読み書き可能なディレクトリが制限されていたり(/tmpには書き出せません)、実行可能ファイルもdmesgやpingはダメでしたし。
実は海外には、自分でポリシーを作成してtomcatをより厳しい制限ありプロセスとして動作させている方もいらっしゃます。CentOS 6での記事になりますが。
- SELinux – How to confine a Tomcat application on RHEL6 / CentOS6 – part 1 – Clever Net Systems
- SELinux – How to confine a Tomcat application on RHEL6 / CentOS6 – part 2 – Clever Net Systems
機会がありましたら、次回は更に厳しいSELinuxポリシーを自作してみたいと思います。