header image

枝折

非ルートユーザーで権限のないファイルを実行する方法を調べた

CREATED: 2024 / 02 / 04 Sun

UPDATED: 2024 / 02 / 04 Sun

コンテナの非ルートユーザーが権限問題について調べた。

非ルートユーザーで権限のないファイルを実行する方法

docker でルートユーザーを使わずに実行する方法を調べていましたが、Linux におけるその辺の仕組みを調べてみました。 Linux ではルートユーザー以外のユーザーが権限のないファイルを実行するには、以下の方法があります。

  • setuid や setgid を実行ファイルにセットする
  • capabilities で必要な権限を与える

setuid と setgid

setuid と setgid は、権限のないユーザーにもファイルを実行させることができる機能です。 setuid は所有者の権限として、setgid は所有グループの権限として、一般ユーザーがファイルを実行することを許可します。

例えば、ping コマンドの権限を確認すると、root ユーザー及び root グループによって所有されており、パーミッションとしては所有者が全権限、所有グループとその他ユーザーには読込と実行のみが許可されています。

# mac book から lima を利用して ubuntu 22.04 環境を構築した
# ubuntu のテンプレートは以下から拝借した
# https://github.com/lima-vm/lima/blob/master/examples/ubuntu-lts.yaml
$ which ping
/usr/bin/ping
$ ls -la /usr/bin/ping
-rwxr-xr-x 1 root root 72344 Feb  5  2022 /usr/bin/ping

この実行ファイルをコピーしてみると、所有者と所有グループが変化します。

$ cp /usr/bin/ping ./myping
$ ls -la ./
-rwxr-xr-x 1 user user  72344 Feb  1 12:00 myping

この状態で myping を実行すると、実は実行できてしまうのですが、これは net.ipv4.ping_group_range に現在のユーザーのグループ ID も含まれているためです。 以下のコマンドを実行すると、0~2147483647 のグループ ID のユーザーは難なく ping を実行することができてしまいます(これは ubuntu 20.04 以降の挙動であるため、それ以前のバージョンであれば ping を実行することはできません)。

$ id # 現在のユーザーの UID GID グループを確認
uid=501(user) gid=1000(user) groups=1000(user)
$ sudo sysctl net.ipv4.ping_group_range
net.ipv4.ping_group_range = 0	2147483647

なので、これを一旦無効化します(飽くまで setuid と setgid について話したいので)。 1 0 を指定すると、今度こそ myping を実行することができなくなったかと思います。

$ sudo sysctl -w net.ipv4.ping_group_range="1 0"
net.ipv4.ping_group_range = 1 0
$ ./myping 8.8.8.8
# nothing...

で、これを実行させるために必要なのが setuid です。 まず、setuid は所有者権限での実行を一般ユーザーに委譲する、対象の実行ファイルの所有者を root に変えます。 所有権が変わった状態で chmod +s(setuid) してあげると、一般ユーザーでもファイルを実行できるようになります。

$ sudo chown root ./myping
$ sudo chmod +s ./myping
$ ls -la
-rwsr-sr-x 1 root user  72344 Feb  1 12:00 myping
$ ./myping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
...

とこのように無事実行されました。

setuid は所有者としての実行を一般ユーザーに与えるものでしたが、setgid は所有グループのメンバーとしての実行を一般ユーザーに与えます。 コマンドまでは割愛しますが、上記でやったような流れで setgid を設定することができます。

capabilities

setuid や setgid は便利ではありますが、権限昇格を狙った攻撃の被害を受ける可能性があります。 一時的な特権を利用して、悪意のあるプログラムを実行される危険性を孕んでいるわけです。

capabilities を利用することで、setuid や setgid とは別の方法で実行ファイルに権限を与えることで、非ルートユーザーによるファイル実行を許可することができます。

capabilities はプロセスやファイルに割り当てられます。 一般ユーザーの capabilities には何も割り当てられていませんが、ルートユーザーには ep が割り当てられています。 これは全ての capability を指しています。

$ ps
PID TTY          TIME CMD
3519 pts/0    00:00:00 bash
3989 pts/0    00:00:00 ps
$ getpcaps 3519
3519: = # 一般ユーザーの capabilities
$ sudo bash
root@lima-ubuntu-lts:/home/user.linux# ps
PID TTY          TIME CMD
4008 pts/2    00:00:00 sudo
4009 pts/2    00:00:00 bash
4017 pts/2    00:00:00 ps
root@lima-ubuntu-lts:/home/user.linux# getpcaps 4009
4009: =ep # ルートユーザーの capabilities

再度 myping の所有権を一般ユーザーに戻し、setuid も外しておきます。

$ sudo chmod -s ./myping
$ sudo chown user ./myping
$ ls -la
-rwxr-xr-x 1 user user  72344 Feb  1 12:00 myping

で、これを実行できるように capability を myping に付与します。 そもそも ping を実行するには RAW ソケットを開くための権限が必要なのですが、これが一般ユーザーにはないためパーミッションエラーとなってしまっているのが現状です。 なので RAW ソケットを開くための capability を myping に付与してあげます。 その capability の名前は、CAP_NET_RAW capability です。 ちなみに capability を付与するには CAP_SETFCAP capability が必要ですが、ルート権限はすでにそれを持っているので、sudo で付与してあげます。

$ sudo setcap 'cap_net_raw+p' ./myping
$ ./myping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
...

これで myping が実行されました。

を仕舞い

ちなみに、man capabilities で capabilities の一覧を確認することができます。

次は docker でルートを設定せずに cron を実行する方法を考えてみます。 仕舞いです。