まず、setuid()
スーパーユーザー以外でも間違いなく使用できます。技術的には、Linuxで必要なのは、任意のユーザーに切り替えるCAP_SETUID
(および/またはCAP_SETGID
)機能だけです。次に、実際のID(プロセスを実行したユーザー)、有効なID(setuid / setgidバイナリの所有者setuid()
)、および保存されたsetgid()
IDの間でプロセスIDを変更できます。
ただし、そのいずれも実際にはあなたの状況に関連していません。
比較的単純ですが、非常に堅牢なソリューションがあります。スレッドを作成する前に、サービスデーモンによってフォークおよび実行されるsetuidルートヘルパーを用意し、Unixドメインソケットペアを使用してヘルパーとサービスの間で通信します。サービスは両方を渡します。その資格情報と、ユーザーバイナリが実行されるときにヘルパーにエンドポイントファイル記述子をパイプします。ヘルパーはすべてを安全にチェックし、すべてが正常であれば、指定されたパイプエンドポイントを標準入力、標準出力、および標準エラーに接続して、目的のユーザーヘルパーをフォークして実行します。
サービスができるだけ早くヘルパーを開始するための手順は次のとおりです。
サービスとヘルパー間の特権通信に使用されるUnixドメインソケットペアを作成します。
フォーク。
子では、ソケットペアの一方の端だけを残して、余分なファイル記述子をすべて閉じます。標準の入力、出力、およびエラーをにリダイレクトします/dev/null
。
親で、ソケットペアの子端を閉じます。
子で、特権ヘルパーバイナリを実行します。
親は単純なメッセージを送信します。データがまったくない可能性がありますが、その資格情報を含む補助メッセージが含まれています。
ヘルパープログラムは、サービスからの最初のメッセージを待ちます。受信すると、資格情報を確認します。クレデンシャルがマスターを通過しない場合、すぐに終了します。
補助メッセージのクレデンシャルは、発信元のプロセス'、、、およびを定義UID
しGID
ますPID
。プロセスはこれらを入力する必要がありますが、カーネルはそれらが真であることを確認します。ヘルパーはもちろん、それが期待どおりであることUID
を確認しGID
ます(サービスが実行されているはずのアカウントに対応します)が、トリックは、/proc/PID/exe
シンボリックリンクが指すファイルの統計を取得することです。これは、資格情報を送信したプロセスの真の実行可能ファイルです。インストールされているシステムサービスデーモン(システムバイナリディレクトリのroot:rootが所有)と同じであることを確認する必要があります。
この時点までのセキュリティを破る可能性のある非常に単純な攻撃があります。悪意のあるユーザーが独自のプログラムを作成し、ヘルパーバイナリを正しくフォークして実行し、最初のメッセージを真のクレデンシャルで送信する場合がありますが、ヘルパーが実際にクレデンシャルを参照しているものを確認する前に、自身を正しいシステムバイナリに置き換えます。 !!
その攻撃は、さらに3つのステップで簡単に打ち負かされます。
ヘルパープログラムは、(暗号的に安全な)疑似乱数、たとえば1024ビットを生成し、それを親に送り返します。
親は番号を送り返しますが、補助メッセージにその資格情報を再度追加します。
ヘルパープログラムは、、、およびが変更されていないこと、およびUID
正しいサービスデーモンバイナリを指していることを確認します。(完全なチェックを繰り返すだけです。)GID
PID
/proc/PID/exe
ステップ8で、ヘルパーは、ソケットのもう一方の端が実行すべきバイナリを実行していることをすでに確認しています。返送する必要のあるランダムなCookieを送信するということは、相手側が事前にメッセージをソケットに「詰め込む」ことができないことを意味します。もちろん、これは攻撃者が疑似乱数を事前に推測できないことを前提としています。注意したい場合は、から適切なCookieを読み取ることができますが、/dev/random
それは限られたリソースであることを忘れないでください(カーネルで利用できる十分なランダム性がない場合はブロックされる可能性があります)。個人的には、から1024ビット(128バイト)と言って/dev/urandom
、それを使用します。
この時点で、ヘルパーはソケットペアのもう一方の端がサービスデーモンであることを確認しました。ヘルパーは、サービスデーモンを信頼できる限り、制御メッセージを信頼できます。(これがサービスデーモンがユーザープロセスを生成する唯一のメカニズムであると想定しています。そうでない場合は、以降のすべてのメッセージで資格情報を再渡して、ヘルパーで毎回再確認する必要があります。)
サービスデーモンがユーザーバイナリを実行する場合は常に、
必要なパイプを作成します(1つはユーザーバイナリに標準入力を供給するためのもので、もう1つはユーザーバイナリから標準出力を取得するためのものです)。
を含むヘルパーにメッセージを送信します
- バイナリを実行するためのID。ユーザー(およびグループ)名、またはUIDとGIDのいずれか
- バイナリへのパス
- バイナリに指定されたコマンドラインパラメータ
- データパイプのユーザーバイナリエンドポイントのファイル記述子を含む補助メッセージ
ヘルパーがそのようなメッセージを受け取るときはいつでも、それは分岐します。子では、標準の入力と出力を補助メッセージのファイル記述子に置き換え、IDをsetresgid()
およびsetresuid()
/またはinitgroups()
で変更し、作業ディレクトリを適切な場所に変更して、ユーザーバイナリを実行します。親ヘルパープロセスは、補助メッセージ内のファイル記述子を閉じて、次のメッセージを待ちます。
ソケットからの入力がなくなるときにヘルパーが終了すると、サービスが終了するとヘルパーは自動的に終了します。
十分な関心があれば、サンプルコードを提供できます。正しく理解するための詳細がたくさんあるので、コードを書くのは少し面倒です。ただし、正しく記述されていれば、ApacheSuEXECなどよりも安全です。