1.UIDが#で指定されている場合は、/etc/passwdを確認してUIDからユーザー名に変換します。例えば/etc/passwdが下記のようになっている場合があるとします。
jsosug:x:1000:1000:,,,:/home/jsosug:/bin/bash
この場合、下記のようにすると、まず/etc/passwdを参照して「#1000->jsosug」と変換してから処理を行います。
test@localhost:~$ sudo -u#1000 /bin/cat /home/jsosug/hidden
以降は【Case1】【Case2】のときと全く同じになり、下記のように表示されます。
test@localhost:~$ sudo -u#1000 /bin/cat /home/jsosug/hidden This is hidden file. test@localhost:~$
2.一方で、UIDが/etc/passwdに載っていない場合には、そのままUIDをユーザー名として処理していきます。例えば「UID=10000」が/etc/passwdに登録されていないときに下記を実行すると、「#10000」をそのままユーザー名として処理を行います。
test@localhost:~$ sudo -u#10000 /bin/cat /home/jsosug/hidden
そのため、【Case1】【Case2】のときと同様の処理になります。【Case2】の4.のようにsudoersがALLで許可されていれば、下記のようにuidをdetails->uid(今回の場合は引き渡されたUIDである#10000)、details->euid(今回の場合は引き渡されたUIDである#10000)を使ってsetresuid()を呼び出します。
203 #if defined(HAVE_SETRESUID) 204 if (setresuid(details->uid, details->euid, details->euid) != 0) { 205 sudo_warn(U_("unable to change to runas uid (%u, %u)"), 206 (unsigned int)details->uid, (unsigned int)details->euid); 207 goto done; 208 }
3.ここで、UIDを10000として「/bin/cat /home/jsosug/hidden」を実行します。当然、/home/jsosugはUID=10000に許可を与えていないので、下記のようになり、エラーとなります。
test@localhost:~$ sudo -u#1000 /bin/cat /home/jsosug/hidden /bin/cat: /home/jsosug/hidden: 許可がありません test@localhost:~$
今回の脆弱性では、sudoersにALLが設定されており、かつUIDとして#-1を与えたときの挙動から問題が発生しています。
1.UIDに#-1(または実質的に同じなである「4294967295」)を与えると、【Case3】のような挙動になります。また、ALLが設定されているため、一足飛びに下記が呼び出されます。
203 #if defined(HAVE_SETRESUID) 204 if (setresuid(details->uid, details->euid, details->euid) != 0) { 205 sudo_warn(U_("unable to change to runas uid (%u, %u)"), 206 (unsigned int)details->uid, (unsigned int)details->euid); 207 goto done; 208 }
ここで、setresuidの動作が問題となります。Linux Manページにある通り、uid、euidに-1を与えることは特別なことを意味しており、値が変更されません。
-rwsr-xr-x 1 root root 586560 10月 30 10:49 /bin/sudo
上記でSUIDビットが立っていてrootで動作しているため、結果としてsudoコマンドの実行は、UID="#-1"を与えるとrootで動作してしまうことになり、下記のようにアクセスできてしまいます。
test@localhost:~$ sudo -u#-1 /bin/cat /home/jsosug/hidden This is hidden file. test@localhost:~$
試しに、「/tmp」ディレクトリに「/bin/touch」コマンドで「hogehoge」というファイルを作成すると、下記のようにrootアカウントで作成できていることが分かります。
test@localhost:~$ sudo -u#-1 /bin/touch /tmp/hogehoge test@localhost:~$ ls -l /tmp 合計 28 -rw-r--r-- 1 root test 0 10月 31 20:21 hogehoge drwx------ 3 root root 4096 10月 31 15:40 systemd-private-35f05b06f7dfcada1f10b15c20ac7-systemd-timesyncd.service-SDjQ
2.ここで、sudoersにALLが設定されていない場合にはどうなるでしょうか? ALLが設定されていないため、【Case1】の動作で#-1というユーザーで動作させたことになります。そのため、【Case1】の2.の箇所の処理を通り、/etc/sudoersに#-1という文字列が設定されていないため、最初まで/etc/sudoersを確認した後にDENY(0)が返されます。
結果として下記のようになり、rootアカウントを取得することはできません。
test@localhost:~$ sudo -u jsosug /bin/cat /home/jsosug/hidden 残念ですが、ユーザー test は'/bin/cat /home/jsosug/hidden' を #-1 として localhost.localdomain 上で実行することは許可されていません。 test@localhost:~$
以上のことから、今回の脆弱性は下記の状態でのみ、発生することが分かります。
今回の脆弱性の修正版では、UIDに#-1(または4294967295)を与えないようにするため、-1(または4294967295)を「EINVAL」とし、値で与えられた場合には「Error」として処理することにしてあります。これにより、setresuid()に-1を与えないようになっています。
sudoersの設定には、大まかに下記の2通りがあります。当然、2.の方がセキュリティは高くなります。
今回の脆弱性では、/etc/sudoersにALLを設定した場合が対象になっています。そのため、2.を選択していた場合には影響を受けませんでした。そういう意味では、もともとセキュリティを高く設定していたユーザーは賢い選択をしていたといえるでしょう。
しかしながら、実環境下でsudoersを一つ一つ設定していくのは運用上かなりの負担となります(筆者もそういう環境下で運用を行った経験がありますが、ユーザーだけではなく新しいアプリを追加するなどの場合にも一苦労でした)。そういう意味では、バランスを取って運用しやすいsudoersの設定(つまり1.を選択)も相応のメリットがあると思います。
いずれにせよ、今回の場合は1.と2.で結果が違ったのは「たまたま」であるため、運用では「小まめにパッケージの脆弱性が出たら更新を繰り返す」という基本的なことが大事になると思います。
略歴:OSSのセキュリティ専門家として20年近くの経験があり、主にOS系のセキュリティに関しての執筆や講演を行う。大手ベンダーや外資系、ユーザー企業などでさまざまな立場を経験。2015年からサイオステクノロジーのOSS/セキュリティエバンジェリストとして活躍し、同社でSIOSセキュリティブログを連載中。
CISSP:#366942
近著:『Linuxセキュリティ標準教科書』(LPI-Japan)」
Copyright © ITmedia, Inc. All Rights Reserved.