root以外のeffective uidの放棄をしてみた
effective uid = root の完全放棄は setuid(newUid) とすれば real uid = effective uid = saved uid = newUid となるので二度と effective uid = root にすることはできなくなる。ところで root ではないが重要なアカウント(仮に 501)があったとして、その権限の放棄はどうすればよいか?と疑問に思ったので試してみた。
#include <sys/types.h> #include <unistd.h> #include <iostream> using namespace std; void showCurrentUid() { cout << "# real user id = " << getuid() << endl; cout << "# effective user id = " << geteuid() << endl; } void setUid(uid_t uid) { int rc = setuid(uid); cout << "setuid(" << uid << ") = " << rc << endl; } int main(int argc, char** argv) { uid_t oldEffectiveUid = geteuid(); showCurrentUid(); setUid(getuid()); // real uid に権限を変更 showCurrentUid(); setUid(oldEffectiveUid); // 元の effective uid に復帰 showCurrentUid(); return 0; }
こんなプログラムを用意して、chown 0 a.out; chmod u+s a.out; でアカウント 500 で起動すると、
# real user id = 500 # effective user id = 0 ← setuid ビットが立っているため 500 ではなく 0 で起動 setuid(500) = 0 ← effective = 500 に変更 (同時に real = saved = 500 となる) # real user id = 500 # effective user id = 500 setuid(0) = -1 ← effective = 0 に戻そうとしても失敗(権限が放棄できた) # real user id = 500 # effective user id = 500
ところが、chown 501 a.out; chmod u+s a.out; でアカウント 500 で起動すると、
# real user id = 500 # effective user id = 501 ← setuid ビットが立っているため 500 ではなく 501 で起動 (見えないが saved = 501) setuid(500) = 0 ← effective = 501 に変更 (saved は書き換わらない) # real user id = 500 # effective user id = 500 setuid(501) = 0 ← effective = 501 に戻せてしまう(saved = 501 のため) # real user id = 500 # effective user id = 501
saved uid も書き換わらないと元の権限に戻ることができてしまうのだな。setuid() では effective uid = root の時のみ saved uid が書き換わるのでどうしようもない。そこで setreuid() の man を読むと、
If the real user ID is set or the effective user ID is set to a value not equal to the previous real user ID, the saved user ID will be set to the new effective user ID.
こちらは
- real uid をセットする
- real uid と異なる effective uid をセットする
ことで saved uid も書き換わるみたい。で、実際にプログラムを書き換えて、
void setRealEffectiveUid(uid_t realUid, uid_t effectiveUid) { int rc = setreuid(realUid, effectiveUid); cout << "setreuid(" << realUid << ", " << effectiveUid << ") = " << rc << endl; } int main(int argc, char** argv) { uid_t oldEffectiveUid = geteuid(); showCurrentUid(); setRealEffectiveUid(getuid(), getuid()); showCurrentUid(); setUid(oldEffectiveUid); showCurrentUid(); return 0; }
これで、chown 501 a.out; chmod u+s a.out; でアカウント 500 で起動すると、
# real user id = 500 # effective user id = 501 ← setuid ビットが立っているため 500 ではなく 501 で起動 (見えないが saved = 501) setreuid(500, 500) = 0 ← effective = 500, real = 500 に変更 (※real を同じ値で上書きしたので saved = 500 になる) # real user id = 500 # effective user id = 500 setuid(501) = -1 ← effective = 501 に戻そうとしても失敗(権限が放棄できた) # real user id = 500 # effective user id = 500
※のところがちょっと面白い。effective uid だけ 501→500 に変更するため setreuid(-1, 500) としても、real = 500 のため 2 の条件に満たずに saved uid は書き換わらない。元々 real = 500 だけど、setreuid(500, -1) とすると、1 の条件により saved uid が書き換わった。