シェルスクリプトでsetuidを許可する

シェルスクリプトでsetuidを許可する

許可setuidビットは、実行者ではなく所有者の実効ユーザー ID を使用してプログラムを実行するように Linux に指示します。

> cat setuid-test.c

#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv) {
    printf("%d", geteuid());
    return 0;
}

> gcc -o setuid-test setuid-test.c
> ./setuid-test

1000

> sudo chown nobody ./setuid-test; sudo chmod +s ./setuid-test
> ./setuid-test

65534

ただし、これは実行可能ファイルにのみ適用され、シェル スクリプトは setuid ビットを無視します。

> cat setuid-test2

#!/bin/bash
id -u

> ./setuid-test2

1000

> sudo chown nobody ./setuid-test2; sudo chmod +s ./setuid-test2
> ./setuid-test2

1000

ウィキペディア言う:

セキュリティ上の欠陥が発生する可能性が高まるため、多くのオペレーティング システムでは、実行可能なシェル スクリプトに適用された setuid 属性を無視します。

これらのリスクを受け入れるつもりだと仮定すると、シェル スクリプトの setuid ビットを実行可能ファイルと同じように扱うように Linux に指示する方法はありますか?

そうでない場合、この問題の一般的な回避策はありますか?現在の解決策は、パスワードプロンプトを回避するために、特定のスクリプトを実行したいユーザーとして実行できるようにsudoersするエントリを追加することです。その主な欠点は、これを行うたびにエントリが必要になることと、呼び出し側が単にALLNOPASSWDsudoerssudo some-scriptsome-script

答え1

#!Linuxは、すべての解釈実行ファイル(つまり、行で始まる実行ファイル)のsetuid¹ビットを無視します。comp.unix.questions よくある質問setuid シェル スクリプトのセキュリティ上の問題について説明します。これらの問題には、シェバン関連とシェル関連の 2 種類があります。詳細については、後述します。

セキュリティを気にせずsetuidスクリプトを許可したい場合は、Linuxではカーネルにパッチを当てる必要があります。3.xカ​​ーネルでは、次の呼び出しを追加する必要があると思います。install_exec_credsの中にload_script関数は を呼び出す前に実行しますopen_execが、テストは行っていません。


セトゥイド・シェバン

#!シェバン ( ) の典型的な実装方法には、競合状態が内在しています。

  1. カーネルは実行可能ファイルを開き、それが で始まることを検出します#!
  2. カーネルは実行可能ファイルを閉じ、代わりにインタープリターを開きます。
  3. カーネルはスクリプトへのパスを引数リスト( としてargv[1])に挿入し、インタープリタを実行します。

この実装でsetuidスクリプトが許可されている場合、攻撃者は既存のsetuidスクリプトへのシンボリックリンクを作成し、それを実行し、カーネルがステップ1を実行した後、インタープリタが最初の引数を開く前にリンクを変更するように設定することで、任意のスクリプトを呼び出すことができます。このため、ほとんどのUnixはsetuidビットを無視します何か異常を感知したとき。

この実装を安全にする 1 つの方法は、インタープリタがスクリプト ファイルを開くまでカーネルがスクリプト ファイルをロックすることです (ファイルのリンク解除や上書きだけでなく、パス内のディレクトリ名の変更も防止する必要があることに注意してください)。ただし、UNIX システムは強制ロックを避ける傾向があり、シンボリック リンクは正しいロック機能を特に困難かつ侵入的なものにします。誰もこの方法を採用していないと思います。

いくつかのUNIXシステム(主にOpenBSD、NetBSD、Mac OS X、これらはすべてカーネル設定を有効にする必要がある)では実装されています。安全な setuid シバン追加機能の使用: パスはファイル記述子で既に開かれているファイルを参照します/dev/fd/Nいいえ(したがって、opening はとほぼ同等です)。多くの Unix システム (Linux を含む) にはsetuid スクリプトがありますが、そうではありません。/dev/fd/Ndup(N)/dev/fd

  1. カーネルは実行可能ファイルを開き、それが で始まることを検出します#!。実行可能ファイルのファイル記述子が 3 であるとします。
  2. カーネルはインタープリターを開きます。
  3. カーネルは/dev/fd/3引数リスト ( としてargv[1]) を挿入し、インタープリタを実行します。

Sven Mascheck の shebang ページユニバース全体のシェバンに関する多くの情報があります。setuid サポート


Setuid 通訳者

OS が setuid shebang をサポートしているか、ネイティブ バイナリ ラッパー (など) を使用しているため、プログラムを root として実行できたと仮定しますsudo。セキュリティ ホールが開いてしまったのでしょうか?多分ここでの問題はない解釈型プログラムとコンパイル型プログラムの違い。問題は、ランタイムシステム権限で実行すると安全に動作します。

  • 動的にリンクされたネイティブバイナリ実行ファイルは、ダイナミックローダー(例/lib/ld.so) は、プログラムに必要な動的ライブラリをロードします。多くの unice では、環境 ( はLD_LIBRARY_PATH環境変数の一般的な名前です) を通じて動的ライブラリの検索パスを設定でき、さらに実行されたすべてのバイナリ ( ) に追加のライブラリをロードすることもできます。プログラムの呼び出し側は、特別に細工された を (他の戦術の中でも)にLD_PRELOAD配置することで、そのプログラムのコンテキストで任意のコードを実行できます。すべての正常なシステムは、setuid 実行可能ファイル内の変数を無視します。libc.so$LD_LIBRARY_PATHLD_*

  • 貝殻sh、csh およびその派生言語では、環境変数は自動的にシェル パラメータになります。、、などのパラメータを通じてPATHIFSスクリプトの呼び出し元は、シェル スクリプトのコンテキストで任意のコードを実行する機会を数多く持っています。一部のシェルは、スクリプトが特権で呼び出されたことを検出すると、これらの変数を適切なデフォルトに設定しますが、信頼できる特定の実装があるかどうかはわかりません。

  • ほとんどのランタイム環境(ネイティブ、バイトコード、またはインタープリタのいずれであっても) 同様の機能があります。setuid 実行ファイルで特別な予防措置を講じているものはほとんどありませんが、ネイティブ コードを実行するものは、多くの場合、動的リンク (予防措置を講じます) 以上の特別な処理は行いません。

  • パール注目すべき例外です。setuid スクリプトを明示的にサポートします安全な方法で。実際、スクリプトは、OSがスクリプトのsetuidビットを無視した場合でもsetuidで実行できます。これは、perlがsetuidルートヘルパーを搭載しており、必要なチェックを実行し、必要な権限で必要なスクリプトのインタープリタを再起動するためです。これは、パールセック#!/usr/bin/suidperl -wTマニュアル。以前は の代わりにsetuid perl スクリプトが必要でしたが#!/usr/bin/perl -wT、最近のほとんどのシステムでは で#!/usr/bin/perl -wT十分です。

ネイティブバイナリを使用することに注意してくださいラッパー自体はこれらの問題を防ぐことはできません実際、それは状況を変える可能性がある悪いランタイム環境が特権で呼び出されたことを検知できず、ランタイム構成をバイパスできなくなる可能性があるためです。

ネイティブバイナリラッパーは、ラッパーが環境を浄化するスクリプトは、(現在のディレクトリなどについて)あまり多くの仮定をしないように注意する必要がありますが、これは問題ありません。環境をサニタイズするように設定されている場合は、これに sudo を使用できます。変数をブラックリスト化するとエラーが発生しやすいため、常にホワイトリスト化してください。sudo を使用する場合は、env_resetオプションがオンになっていること、がsetenvオフになっていること、env_fileおよびenv_keep無害な変数のみが含まれていることを確認してください。


要約:

  • Setuid Shebang は安全ではありませんが、通常は無視されます。
  • プログラムを特権付きで実行する (sudo または setuid 経由) 場合は、ネイティブ コードまたは perl を記述するか、環境をサニタイズするラッパー (オプション付きの sudo などenv_reset) を使用してプログラムを起動します。

¹この説明は、「setuid」を「setgid」に置き換えた場合も同様に適用されます。これらは両方ともスクリプト上のLinuxカーネルによって無視されます

答え2

この問題を解決する 1 つの方法は、setuid ビットを使用できるプログラムからシェル スクリプトを呼び出すことです。
これは sudo のようなものです。たとえば、C プログラムでこれを実現する方法は次のとおりです。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    setuid( 0 );   // you can set it at run time also
    system( "/home/pubuntu/setuid-test2.sh" );
    return 0;
 }

これを setuid-test2.c として保存します。
コンパイルして
、このプログラム バイナリに対して setuid を実行します。

su - nobody   
[enter password]  
chown nobody:nobody a.out  
chmod 4755 a.out  

これで、スクリプトを実行できるようになり、スクリプトが nobody 権限で実行されていることがわかります。
ただし、ここでも、スクリプト パスをハードコードするか、上記の exe にコマンド ライン引数として渡す必要があります。

答え3

同様の状況にあるいくつかのスクリプトに、次のようにプレフィックスを付けます。

#!/bin/sh
[ "root" != "$USER" ] && exec sudo $0 "$@"

これは を使用せsetuidず、単に現在のファイルを で実行するだけであることに注意してくださいsudo

答え4

何らかの理由でsudo利用できない場合は、C で薄いラッパー スクリプトを作成できます。

#include <unistd.h>
int main() {
    setuid(0);
    execle("/bin/bash","bash","/full/path/to/script",(char*) NULL,(char*) NULL);
}

コンパイルしたら、 のように設定しsetuidますchmod 4511 wrapper_script

これは別の投稿された回答と似ていますが、スクリプトをクリーンな環境で実行し、/bin/bashによって呼び出されるシェルの代わりにを明示的に使用するためsystem()、潜在的なセキュリティ ホールがいくつか閉じられます。

これは環境を完全に破棄することに注意してください。脆弱性を生じさせずに環境変数を使用したい場合は、 を使用するだけで済みますsudo

当然ですが、スクリプト自体は root のみが書き込み可能であることを確認する必要があります。

関連情報