web-dev-qa-db-ja.com

シェルスクリプトで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エントリを追加して、ALLが特定のスクリプトを実行したいユーザーとして実行できるようにし、NOPASSWDを使用してパスワードプロンプトを回避することです。その主な欠点は、これを行うたびにsudoersエントリが必要になることと、呼び出し側がSudo some-scriptだけでなくsome-scriptを呼び出す必要があることです。

195
Michael Mrozek

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

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


Setuidシバン

Shebang(#!)の一般的な実装方法に固有の競合状態があります。

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

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

この実装を保護する1つの方法は、インタプリタがスクリプトファイルを開くまでカーネルがスクリプトファイルをロックすることです(これにより、ファイルのリンク解除や上書きだけでなく、パス内のディレクトリ名の変更も防ぐ必要があります)。しかし、UNIXシステムは必須のロックを回避する傾向があり、シンボリックリンクは、正しいロック機能を特に困難かつ侵襲的にします。私は誰もこのようにそれをしないと思います。

いくつかのUNIXシステム(主にOpenBSD、NetBSD、Mac OS X、すべてカーネル設定を有効にする必要がある)は、追加機能を使用してsecure setuid Shebangを実装します。パス/dev/fd/Nは、すでに開いているファイルを参照しますファイル記述子上[〜#〜] n [〜#〜](したがって、/dev/fd/Nを開くことは、dup(N)とほぼ同等です)。多くのUNIXシステム(Linuxを含む)には/dev/fdがありますが、setuidスクリプトはありません。

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

Sven Mascheckのシバンページsetuid support を含め、ユニバース全体のシバンに関する多くの情報があります。


Setuidインタープリター

OSがsetuid Shebangをサポートしているため、またはネイティブバイナリラッパー(Sudoなど)を使用しているため、プログラムをrootとして実行できたと仮定します。セキュリティホールを開けましたか? たぶん。ここでの問題は、解釈されたプログラムとコンパイルされたプログラムに関するではありません。問題はランタイムシステムが特権付きで実行された場合に安全に動作するかどうかです。

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

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

  • ほとんどのランタイム環境(ネイティブ、バイトコード、インタープリターのいずれでも)は同様の機能を備えています。 setuid実行可能ファイルで特別な注意を払う人はほとんどいませんが、ネイティブコードを実行するものは、動的なリンク(注意が必要)よりも優れた機能を実行しません。

  • Perlは注目すべき例外です。 setuidスクリプトを明示的にサポート安全な方法で。実際、OSがスクリプトのsetuidビットを無視した場合でも、スクリプトはsetuidを実行できます。これは、Perlにsetuidルートヘルパーが付属しているためです。このヘルパーは、必要なチェックを実行し、必要なスクリプトを実行し、必要な特権でインタプリタを呼び出します。これは perlsec マニュアルで説明されています。以前は、setuid Perlスクリプトには#!/usr/bin/suidperl -wTではなく#!/usr/bin/Perl -wTが必要でしたが、最近のほとんどのシステムでは、#!/usr/bin/Perl -wTで十分です。

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

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


TL、DR:

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

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

この問題を解決する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  

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

57
Hemant

このボートにあるいくつかのスクリプトにプレフィックスを付けます。

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

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

24
rcrowley

Sudo some_scriptを呼び出さないようにするには、次のようにします。

  #!/ust/bin/env sh

  Sudo /usr/local/scripts/your_script

SETUIDプログラムは、root権限で実行され、ユーザーがそれらを大幅に制御できるため、細心の注意を払って設計する必要があります。彼らはすべてを健全性チェックする必要があります。次の理由により、スクリプトを使用してそれを行うことはできません。

  • シェルは、ユーザーと頻繁に対話する大きなソフトウェアです。特にすべてのコードがそのようなモードで実行することを意図していないため、すべてを正常性チェックすることはほぼ不可能です。
  • スクリプトは、ほとんどが迅速で汚れたソリューションであり、通常、setuidを許可するような注意を払って準備されていません。彼らは多くの潜在的に危険な機能を持っています。
  • 彼らは他のプログラムに大きく依存しています。シェルがチェックされただけでは不十分です。 sedawkなどもチェックする必要があります

Sudoはいくつかの健全性チェックを提供しますが、それだけでは不十分です。自分のコードのすべての行をチェックしてください。

最後に、機能の使用を検討してください。これにより、通常root権限を必要とする特別な権限を、ユーザーとして実行しているプロセスに与えることができます。ただし、たとえば、pingはネットワークを操作する必要がありますが、ファイルにアクセスする必要はありません。ただし、継承されているかどうかはわかりません。

12

super [-r reqpath]コマンド[args]

スーパーでは、指定されたユーザーがスクリプト(または他のコマンド)をrootであるかのように実行できます。または、コマンドを実行する前に、uid、gid、補足グループをコマンドごとに設定できます。これは、スクリプトをsetuid rootにする安全な代替手段となることを目的としています。 Superは、一般ユーザーが他のユーザーが実行するコマンドを提供することもできます。これらは、コマンドを提供するユーザーのuid、gid、およびグループで実行されます。

スーパーは「super.tab」ファイルを調べて、ユーザーが要求されたコマンドの実行を許可されているかどうかを確認します。許可が与えられた場合、superはpgm [args]を実行します。ここで、pgmはこのコマンドに関連付けられているプログラムです。 (ルートはデフォルトで実行を許可されていますが、ルールがルートを除外する場合でも拒否される可能性があります。通常のユーザーはデフォルトで実行を許可されていません。)

Commandがスーパープログラムへのシンボリックリンク(またはハードリンク)の場合、%command argsと入力することは、%super command argsと入力することと同じです(コマンドはsuperであってはなりません。リンク。)

http://www.ucolick.org/~will/RUE/super/README

http://manpages.ubuntu.com/manpages/utopic/en/man1/super.1.html

5
Nizam Mohamed

Sudoのエイリアス+スクリプトの名前を作成できます。もちろん、エイリアスもセットアップする必要があるため、セットアップはさらに大変ですが、Sudoを入力する必要がなくなります。

しかし、恐ろしいセキュリティリスクを気にしない場合は、シェルスクリプトのインタプリタとしてsetuidシェルを使用してください。それがうまくいくかどうかはわかりませんが、おそらくそれはうまくいくと思います。

ただし、実際にこれを行うことはお勧めしません。教育目的で言及しているだけです;-)

4
wzzrd

何らかの理由でSudoを使用できない場合は、Cでシンラッパースクリプトを記述できます。

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

そして、コンパイルしたら、_chmod 4511 wrapper_script_でsetuidとして設定します。

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

これは環境を完全に破棄することに注意してください。脆弱性を開かずにいくつかの環境変数を使用したい場合は、Sudoを使用するだけで十分です。

もちろん、スクリプト自体がrootだけが書き込み可能であることを確認する必要があります。

2
Chris