連載
» 2017年06月29日 05時00分 UPDATE

OSS脆弱性ウォッチ:「sudoコマンドの脆弱性はSELinuxが有効になっていたため」が誤解であるワケ

連載「OSS脆弱性ウォッチ」では、さまざまなオープンソースソフトウェアの脆弱性に関する情報を定期的に取り上げ、解説していく。2017年5月30日、Linuxのsudoコマンドに全特権取得の脆弱性が報告された。連載初回は、こちらの詳しい説明と情報をまとめる。

[面和毅,OSSセキュリティ技術の会]

 「OSSセキュリティ技術の会」の面和毅です。本連載「OSS脆弱性ウォッチ」では、さまざまなオープンソースソフトウェア(OSS)の脆弱(ぜいじゃく)性に関する情報を定期的に取り上げ、解説していきます。

 2017年5月30日、Linuxのsudoコマンドに全特権取得の脆弱性(CVE-2017-1000367/CVE-2017-1000368)が報告されました。連載初回は、こちらの詳しい説明と情報をまとめます。

脆弱性発見の経緯

 2017年5月30日、Linuxのsudoコマンドに全特権取得の脆弱性(CVE-2017-1000367)が報告され、修正が出されました。しかし、この修正が不十分(任意のコマンド実行が可能だった)のため、再度6月6日にCVE-2017-1000368として脆弱性情報の公開と修正版のリリースが行われました。

 実際には、この脆弱性は「Qualys Security Advisory」が2017年5月17日に報告した「Stack Clash」の脆弱性レポートの中にあったものです(他にもCronの脆弱性など、さまざまな脆弱性の情報が含まれています。これらは個々にCVEも付与されていますので、別の機会に説明します)。

 この「Stack Clash」に関しては関連するパッケージが多いことから2017年6月20日まで公開されず、各ディストリビューションや主要OSSに報告されていたものになります。

 ただし、このsudoの脆弱性は、「Stack Clash」とは独立しており、SELinuxが有効になっているシステム上で発生することから、先に2017年5月30日に公開されたものです。この記事の中では、このSELinux+sudoによる脆弱性(CVE-2017-1000367/CVE-2017-1000368)だけに焦点を当てて紹介します。

今回発見された脆弱性の概要

 下記の説明は、「OSSセキュリティ技術の会」のブログにも英語で公開しています

 この脆弱性は、sudoのttynameの取り扱いの不備に起因しています。

 Linuxでは、プログラムを実行した際に、そのプログラムの情報を「/proc/[pid]/stat」から下記のように見ることができます。

[pid] ([cmd]) [State] [ppid] [pgrp] [session] [tty_nr] [tpgid] ....

 sudoのプログラムでは、sudoを実行したユーザーの情報(user_info: uid, cwdなど)を取得する際に「get_user_info()」を呼び出しています。この「get_user_info()」内では、プログラムを実行している端末の情報を取得するために、前述の「/proc/[pid]/stat」の左から7番目のフィールドに格納されている、プロセスが呼び出されているttyの番号を取得します。

 このttyの番号からプログラムを実行しているtty(ファイル名)を求めるために、「get_process_ttyname()」経由で呼び出される「sudo_ttyname_dev()」で代表的なtty(「search_devs[]」で定義されている「/dev/console」など)を検索し、そこで見つからなかった場合、「sudo_ttyname_scan()」を呼び出して、ttyデバイスがどこにあるかを見つけるために、「/dev」以下を広くスキャンします(「breadth-first走査」と呼ばれています)。

 このtty番号(7番目のフィールド)とデバイスファイルの関連は下記のようになります。

cat /proc/2778/stat
2778 (mplayer) S 2366 2778 2366 34816 2778 1077936128 10433 .....
例(mplayerを「/dev/pts/0(tty)」で、「pid=2778」で実行しているとき)

 この「34816」を2進数にすると下記です。

0000 0000 0000 0000 1000 1000 0000 0000

 31-20と7-0ビットを合わせたものがマイナー番号、19-8ビットがttyのメジャー番号。つまり、メジャー番号は「000010001000 = 136」、マイナー番号は0です。

 確認のため、「ls -l /dev/pts/0」とすると、下記のようになり、「/dev/pts/0」が「(major, minor) = (136,0)」で表されていることが確認されます。

jsossug@localhost:~$ ls -l /dev/pts/0
crw--w---- 1 jsossug tty 136, 0  6月 19 15:21 /dev/pts/0

 この7番目のフィールドからttyの情報を取得するわけですが、上記の例で分かるように、「/proc/[pid]/stat」ファイルがスペースで区切られているため、「get_process_ttyname()」ではスペース区切りで「何番目か」を確認するロジックになっています。このため、2つ目の「cmd」(実行コマンド。オプションは含まない)が半角スペースを含んでいる場合には、「get_process_ttyname()」でtty番号を拾う際に誤ったフィールドを拾ってきてしまいます。

 攻撃者はこれを利用して、スペースを6つ入れた「cmd」(実行コマンド。オプションは含まない)を使うことで、tty番号を任意の値に変えることが可能です。

 さらに、sudoではtty番号からデバイスを検索する順番として、「sudo_ttyname_dev()」で代表的なtty(「search_devs[]」で定義されている「/dev/console」「/dev/pts/」など)を検索した後に、「/dev」以下を全て検索しにいきます。そこで、「/dev/shm」以下のような、「全てのユーザーが書き込み権限を持っている」デバイスまでtty番号の検索に行ってしまうために、「/dev/shm」を経由して好きなファイルへのシンボリックリンクをデバイスと勘違いさせることができます。

攻撃の方法

 前提条件として、攻撃者(jsossug)がsudoで限られた特権を譲渡されている状態である必要があります(この前提条件があるため、「ローカルかつ一部の特権が譲渡されているユーザー」が攻撃者の前提になるため、攻撃自体のハードルは比較的高いものになります)。

 例えば、冒頭の「Qualys Security Advisory」の報告では、「/etc/sudoers」としての下記のような例が挙げられています。

jsossug	ALL=(ALL) 	/usr/bin/sum

1.「/dev/shm/_tmp」ディレクトリを作成します。

jsossug@cent7enc:/dev/shm$ mkdir _tmp

2.「/dev/shm/_tmp/tty」として、存在していない「pts"/dev/pts/57"」へのシンボリックリンクを作成します。

jsossug@cent7enc:/dev/shm$ ln -s /dev/pts/57 /dev/shm/_tmp/tty

 確認します。

jsossug@cent7enc:/dev/shm$ ls -l /dev/shm/_tmp
lrwxrwxrwx. 1 jsossug jsossug 11  Jun 22 09:02 tty -> /dev/pts/57

3.前節で説明したデバイス番号の規則により、「/dev/pts/57」のデバイス番号は「34873」になるはずです。そのため、「/dev/shm/_tmp/ 34873」として「/usr/bin/sudo」へのシンボリックリンクを作成します。

[jsossug@cent7enc _tmp]$ ln -s /usr/bin/sudo "/dev/shm/_tmp/     34873 "
                                                            スペースが6つ

 確認します。

[jsossug@cent7enc _tmp]$ ls -l
lrwxrwxrwx. 1 jsossug jsossug 13  Jun 22 09:07      34873  -> /usr/bin/sudo

4.「/dev/shm/_tmp」以下を「inotify」で「IN_OPEN」が出るまでモニタリングしておき、「/dev/shm/_tmp」以下にアクセスがあった際に競合状態を起こして、シンボリックリンクの「/dev/shm/_tmp/_tty」を、書き換えたいファイル(「/etc/passwd」など)へのシンボリックリンクに置き換えます。

5.sudoがttyを「/etc/passwd」として処理を続行します。

SELinuxが関係してくる理由

 この一連の動きまでは、SELinuxが全く関与していません。では、ここで「なぜSELinuxが有効の場合に書き換えが可能なのか」について話します。

 実は、このttyの置き換えだけだと、処理の途中でプログラムを実行した実行元のtty番号が入れ替わっただけなので、処理自体には影響がありません。しかし、このttyの値を使う方法として、「sudoのSELinuxオプションである『-r ロール識別子』を用いる」方法があります。

1.SELinuxが有効になったシステム(EnforcingでもPermissiveでも同じ)で、「-r ロール識別子」オプションを用いると、sudoのソースの「selinux_setup()」が「exec_setup()」を呼び出します。

bool
exec_setup(struct command_details *details, const char *ptyname, int ptyfd)
{
--snip-- 
#ifdef HAVE_SELINUX
    if (ISSET(details->flags, CD_RBAC_ENABLED)) {
        if (selinux_setup(details->selinux_role, details->selinux_type,
            ptyname ? ptyname : user_details.tty, ptyfd) == -1)

 上記一番下の行で、「selinux_setup()」が呼び出されています。

2.この「selinux_setup()」は下記のように、「relabel_tty()」を呼び出しています。

int
selinux_setup(const char *role, const char *type, const char *ttyn,
    int ptyfd)
{
--snip--
    if (relabel_tty(ttyn, ptyfd) < 0) {

3.この「relabel_tty()」では、下記のように「open(ttyn, O_RDWR|O_NONBLOCK);」を呼び出しています。

--snip--
        /* Re-open tty to get new label and reset std{in,out,err} */
        close(se_state.ttyfd);
        se_state.ttyfd = open(ttyn, O_RDWR|O_NONBLOCK);
--snip--

 この処理の際、前節の手順で攻撃をしている場合にはttynには「/etc/passwd」というファイル名が入ることになります。

 さらに「relabel_tty()」で下記のように「dup2()」を呼び出して、子プロセス用にファイルディスクリプタをコピーしているので、sudoの子プロセスが前述のttyn(「/etc/passwd」)を標準入力、標準出力、標準エラー出力先として動作することになります。

--snip--
                for (fd = STDIN_FILENO; fd <= STDERR_FILENO; fd++) {
                    if (isatty(fd) && dup2(se_state.ttyfd, fd) == -1) {
--snip--

 これらにより、sudoで実行するコマンド(「Qualys Security Advisory」の例では「/usr/bin/sum」)を使って、標準出力に欲しい出力が出るようにうまくチューニングすることで、任意のファイルを上書きすることが可能になります。

 これを利用して、例えば「/etc/sudoers」ファイルを上書きして、「/etc/sudoers」で全ての特権を得られるようにできれば(下記のように)、jsossugがrootとして完全に動作することが可能になります。

jsossug	ALL=(ALL) 	ALL

修正内容

 CVE-2017-1000367では、実行コマンドの「cmd」部分にスペースが含まれている可能性を考えた修正になっています。

 また、ttyデバイスを検索する際に「/dev/pts」などのttyデバイスを検索するように指定されており、「/dev/」以下のサブディレクトリ以下を掘り下げてスキャンはしないようになりました。

 これにより、「/dev/shm」以下もスキャンされなくなります。しかし、CVE-2017-1000367では、「cmd」部分に改行文字が入っていることが考慮されていなかったため、新たにCVE-2017-1000368として、その部分の脆弱性の公開と修正が行われています。

SELinuxを無効にしているから大丈夫?

 前述のように、今回SELinuxが使われているのは、標準出力にファイルを回すために使われているだけであり、そもそもの脆弱性はsudoの「cmd」部分の取り扱いと「/dev/tty」のスキャン方法にありました。そのため、SELinuxはあくまでも「補助的に」脆弱性を利用する際に使われている形になります。

 今回の件で、「SELinuxが有効になっていたために脆弱性があった」という誤解が流布されていますが、惑わされず、セキュリティが必要なシステムでは引き続きSELinuxを有効にして利用することをお勧めします。

 また、「SELinuxを無効にしているから大丈夫」という方も、SELinuxのモードが「Permissive」モードのときには引き続き今回の脆弱性を悪用される懸念があるため、注意が必要です。

 最後に。パッケージに脆弱性が発見された場合には、なるべく早くパッケージのバージョンを上げる体制が望まれます。

筆者紹介

面和毅

略歴:OSSのセキュリティ専門家として20年近くの経験があり、主にOS系のセキュリティに関しての執筆や講演を行う。大手ベンダーや外資系、ユーザー企業などでさまざまな立場を経験。2015年からサイオステクノロジーのOSS/セキュリティエバンジェリストとして活躍し、同社でSIOSセキュリティブログを連載中。

CISSP:#366942

近著:『Linuxセキュリティ標準教科書』(LPI-Japan)」


Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。