web-dev-qa-db-ja.com

setuidビットの動作に一貫性がないのはなぜですか?

私はコードを書きました:

// a.c
#include <stdlib.h>
int main () {
  system("/bin/sh");
  return 0;
}

コマンドでコンパイル:

gcc a.c -o a.out

その上にsetuidビットを追加しました:

Sudo chown root.root a.out
Sudo chmod 4755 a.out

Ubuntu 14.04で一般ユーザーとして実行すると、root権限を取得しました。

しかし、Ubuntu 16.04では、現在のユーザーのシェルがまだあります。

なぜ違うのですか?

8
user10883182

変更されたのは、_/bin/sh_がbashになるか、またはdashのままになり、bashの動作を模倣する追加のフラグ_-p_を取得したことです。

(man page で説明されているように、bashはsetuid特権を削除しないように_-p_フラグを必要とします:

実効ユーザー(グループ)IDが実際のユーザー(グループ)IDと等しくない状態でシェルが起動され、-pオプションが指定されていない場合、起動ファイルは読み込まれず、シェル関数は環境から継承されません。SHELLOPTS 、BASHOPTS、CDPATH、およびGLOBIGNORE変数は、環境に表示されても無視され、実効ユーザーIDは実際のユーザーIDに設定されます。呼び出し時に-pオプションが指定されている場合、起動時の動作は同じですが、有効なユーザーIDはリセットされません。

以前は、dashはこれを気にせず、(それを防ぐために何もしないことによって)setuidの実行を許可していました。しかし buntu 16.04のdashのマンページ には、bashと同様に、説明されている追加のオプションがあります。

-p priv
有効なuidがuidと一致しない場合は、それをリセットしようとしないでください。 これはデフォルトでは設定されておらず、system(3)またはpopen(3)を介したsetuidルートプログラムによる誤った使用を回避するのに役立ちます。

このオプションは pstream には存在しませんでした(提案されたパッチに反応しなかった可能性があります)*)Debian 9でもありませんが、2018年以降にパッチを入手したDebianバスターに存在します。

注:StéphaneChazelasによって説明されているように、system()は_"/bin/sh -p"_で指定されたものを実行するため、setuidはすでに削除されているため、system()で_/bin/sh_を呼び出すのは遅すぎます。 derobert の答えは、これを処理する方法をsystem()の前のコードで説明しています。

* 履歴についての詳細 here および there

9
A.B

おそらくシェルは、何らかの理由で、起動の一部として有効なユーザーIDを実際のユーザーIDに戻しています。これを確認するには、次を追加します。

_/* needs _GNU_SOURCE; non-Linux users see setregid/setreuid instead */
uid_t euid = geteuid(), egid = getegid();
setresgid(egid, egid, egid);
setresuid(euid, euid, euid);
_

system()の前。 (実際には、Linuxの場合でも、おそらく実際のものを設定するだけで済みます。保存されたものはそのままにしておいても構いません。これは、デバッグするための力です。set-idの理由によっては、もちろん、実際のIDもどこかに保存します。)

[また、これがsetidがどのように機能するかを学ぶだけの演習ではない場合、特にシェルを呼び出すときに、心配するべき多くのセキュリティ問題があります。たとえば、シェルの動作に影響を与える多くの環境変数があります。可能であれば、Sudoのような既存のアプローチを優先してください。]

8
derobert