前回からだいぶ間隔が空いてしまいました。前回の最後に案内したudzuraさんのCRIUに関する記事はもう少し時間がかかるようですので、
今回から数回は、
setuid
一般的にはUNIX系のOSでは、
一般ユーザは、pingコマンドは特権が必要で、setuidされており、
$ ls -l $(which ping)
-rwsr-xr-x 1 root root 64424 Jun 28 20:05 /bin/ping
↑(このsがsetuidされている印)
ケーパビリティ
先に紹介したpingコマンドは、setuidされており、
逆に言うとRawソケットを使うのに必要な権限のみが必要なのに、pingコマンドに渡していることになります。これはセキュリティ上の観点から望ましいことではありません。
Linuxにはこのような場合に使える、
例えば、CAP_というケーパビリティがあればRawソケットを使用できます。Linuxカーネルでは、/usr/で定義されています。
$ grep "#define CAP_" /usr/include/linux/capability.h
#define CAP_CHOWN 0
#define CAP_DAC_OVERRIDE 1
#define CAP_DAC_READ_SEARCH 2
#define CAP_FOWNER 3
#define CAP_FSETID 4
#define CAP_KILL 5
:(略)ケーパビリティの実装は結構古くて、capabilities(7)に載っています。
プロセスのケーパビリティ
プロセス
- Permitted
- EffectiveとInheritableで持つことを許されるケーパビリティセット
- Inheritable
execve(2)した際に継承できるケーパビリティセット- Effective
- 実際にカーネルがスレッドの実行権限を判定するのに使うケーパビリティセット
- Ambient
- 特権のない
(setuid/ setgidされていない) プログラムを execve(2)した際に子プロセスに継承されるケーパビリティセット(Linux 4. 3以降で使用可能)
さらに、
実際にカーネルがプロセスが持つ権限をチェックする時は、
そして、
capset(2):システムコールでケーパビリティを設定するexecve(2):システムコールの前後でケーパビリティが変化するprctl(2):システムコールでAmbientケーパビリティやバウンディングセットを設定する
execve(2)システムコールはプログラムを実行するためのシステムコールです。このシステムコールとケーパビリティの関係については次回で説明します。
実行中のプロセスが持つケーパビリティを確認する
では実際にプロセスでケーパビリティがどのように設定されているかを見てみましょう。
プロセス/proc/<PID>/statusファイルで確認できます。例えばPIDが1であるinitのPermittedCapPrm)、CapEff)、CapBnd)
$ grep Cap /proc/1/status CapInh: 0000000000000000 CapPrm: 0000003fffffffff CapEff: 0000003fffffffff CapBnd: 0000003fffffffff CapAmb: 0000000000000000
特定のケーパビリティだけが設定されている、ntpdは次のようになっています。
$ grep Cap /proc/$(pgrep ntpd)/status CapInh: 0000000000000000 CapPrm: 0000000002000400 CapEff: 0000000002000400 CapBnd: 0000003fffffffff CapAmb: 0000000000000000
上記の例のように status ファイルでケーパビリティを確認できます。しかし、
もう少しわかりやすくプロセスのケーパビリティセットを確認したい場合はlibcapに含まれるgetpcapsコマンドが使えますlibcap2-binパッケージに含まれています)。
$ getpcaps $(pgrep ntpd) Capabilities for `21935': = cap_net_bind_service,cap_sys_time+ep
ポート番号1024番未満を使うには特権が必要です。しかし、ntpdは一般ユーザであるntpユーザ権限で動作しており、ntpdで使うポートである123番ポートは使えないはずです。しかし、cap_を持っているので123番ポートを使えます。
また、cap_を持っていますので、
つまりntpdは必要最低限のケーパビリティのみを持った状態で実行されているということです。不要な特権を持たずにデーモンが実行されていますので、
プロセスのケーパビリティを操作する
実際にケーパビリティによって必要な特権のみを持って動く例を見ましたので、
特権を持っていないプロセスがいきなり特権を取得できませんので、
ケーパビリティを持っていなければ、libcapに含まれるcapshコマンドで簡単に試せます。ここで指定するケーパビリティは、capability.で定義されているケーパビリティを指定します
# id
uid=0(root) gid=0(root) groups=0(root)
# capsh --drop="cap_net_raw" -- -c "ping -c 1 127.0.0.1"
ping: socket: Operation not permitted
(cap_net_rawを削除して実行したのでrootユーザなのにpingが実行できない)
cap_ケーパビリティを削除したときにケーパビリティがどのようになっているのかを確認してみましょう。まずはrootで普通に実行しているbashが持つケーパビリティです。
# grep Cap /proc/$$/status CapInh: 0000000000000000 CapPrm: 0000003fffffffff CapEff: 0000003fffffffff CapBnd: 0000003fffffffff CapAmb: 0000000000000000
サポートされているケーパビリティすべてが有効になっているのがわかります。ここでcap_を削除すると次のようになります。
# capsh --drop="cap_net_raw" --
(cap_net_rawを削除してbashを実行)
# grep Cap /proc/$$/status
CapInh: 0000000000000000
CapPrm: 0000003fffffdfff
CapEff: 0000003fffffdfff
CapBnd: 0000003fffffdfff
CapAmb: 0000000000000000
/usr/を見ると、CAP_は13ですので、
ファイルケーパビリティ
プロセスは、
セキュリティ的には不要な特権が減っていくわけですから意味があるわけですが、pingコマンドに設定されていたsetuidのようなケースです。
そこでsetuidのように、
ファイルケーパビリティも、
次の例は CentOS 8にインストールされているpingコマンドです。
$ ls -l /bin/ping -rwxr-xr-x. 1 root root 69160 May 11 23:22 /bin/ping (setuidされていない) $ id -u 1000 (一般ユーザで実行) $ ping -c 1 127.0.0.1 PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.021 ms --- 127.0.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.021/0.021/0.021/0.000 ms (pingが実行できた)
このようにsetuidされていませんがpingコマンドは実行できています。これはファイルケーパビリティが設定されているからです。
ファイルケーパビリティを確認するにはlibcapに含まれるgetcapコマンドを使います。
$ getcap /bin/ping /bin/ping = cap_net_admin,cap_net_raw+p
これは、/bin/にはファイルケーパビリティのPermittedケーパビリティとして、cap_とcap_ケーパビリティが設定されているという意味です
ファイルケーパビリティは安全のためにコピーすると設定が外れます
$ cp /bin/ping . $ getcap ./ping (ファイルケーパビリティが何も設定されていない) $ ./ping 127.0.0.1 ping: socket: Operation not permitted (権限がないので実行できない)
このようにファイルケーパビリティが設定されていないので、pingコマンドは一般ユーザーでは実行できません。
ここでコピーしたpingコマンドを一般ユーザ権限で実行できるようにファイルケーパビリティでcap_を付与してみましょう。同じくCentOS 8で実行しています。
$ sudo setcap cap_net_raw=p ./ping (permittedをオン) $ getcap ./ping ./ping = cap_net_raw+p (cap_net_rawが設定された) $ ./ping -c 1 127.0.0.1 PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.023 ms --- 127.0.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.023/0.023/0.023/0.000 ms (実行できた)
ファイルケーパビリティを設定するには特権が必要ですので、setcapコマンドだけはroot権限で実行しています。
まとめ
今回は、
また、
実は今回の記事は、
その前提知識として、
次回まででケーパビリティ全体について説明を終わらせ、
