setuidのプログラムからシェルを起動する

結論
#include <stdlib.h>
#include <unistd.h>

int main()
{
    execl("/bin/sh", "/bin/sh", "-p", NULL);
}

もしくは

#include <stdlib.h>
#include <unistd.h>

int main()
{
    setreuid(geteuid(), -1);
    system("/bin/sh");
}
詳細

Linuxでは、

[kusano@hoge suid]$ sudo chown kusano2: a.out
[kusano@hoge suid]$ sudo chmod u+s a.out
[kusano@hoge suid]$ ll
total 32
-rwsrwxr-x 1 kusano2 kusano2 7073 Aug  3 15:22 a.out

とsetuidビットを立てると、ユーザーkusanoがa.outを実行した場合に、a.outはkusano2の権限で動く。ちなみにスクリプトには効かない。
では、a.outの中で/bin/shを呼び出すとkusano2の権限になるかというとならない。

//  test1.cpp
#include <stdlib.h>
#include <unistd.h>

int main()
{
    system("/bin/sh");
}
//  test2.cpp
#include <stdlib.h>
#include <unistd.h>

int main()
{
    execl("/bin/sh", "/bin/sh", NULL);
}
[kusano@hoge suid]$ g++ test1.cpp; sudo chown kusano2: a.out; sudo chmod u+s a.out; ./a.out
sh-4.1$ id
uid=500(kusano) gid=500(kusano) groups=500(kusano),10(wheel)
sh-4.1$ exit
exit
[kusano@hoge suid]$ g++ test2.cpp; sudo chown kusano2: a.out; sudo chmod u+s a.out; ./a.out
sh-4.1$ id
uid=500(kusano) gid=500(kusano) groups=500(kusano),10(wheel)
sh-4.1$ exit
exit

ユーザーIDには実ユーザーIDと実効ユーザーIDがあり、setuidビットを立てたプログラムは、実効ユーザーIDはファイルの所有者になるが、実ユーザーIDはプログラムを実行したユーザーとなる。/bin/shは起動時に実効ユーザーIDと実ユーザーIDが一致しているか調べ、一致していなかったら実効ユーザーIDを実ユーザーIDに変えてしまう。-pオプションを付けることでこの動作を抑制できる。このようにして起動したシェルは実ユーザーIDはkusanoだが、実効ユーザーがkusano2なのでkuasno2の権限でファイル操作などができる。ただし、実ユーザーIDはkusanoなので、さらにシェルを起動すると実効ユーザーIDもkusanoになる。

//  test3.cpp
#include <stdlib.h>
#include <unistd.h>

int main()
{
    execl("/bin/sh", "/bin/sh", "-p", NULL);
}
[kusano@hoge suid]$ g++ test3.cpp; sudo chown kusano2: a.out; sudo chmod u+s a.out; ./a.out
sh-4.1$ id
uid=500(kusano) gid=500(kusano) euid=506(kusano2) groups=507(kusano2),10(wheel),500(kusano)
sh-4.1$ touch hoge
sh-4.1$ ls -l hoge
-rw-rw-r-- 1 kusano2 kusano 0 Aug  3 15:54 hoge
sh-4.1$ sh
sh-4.1$ id
uid=500(kusano) gid=500(kusano) groups=500(kusano),10(wheel)

system関数ではうまくいかない。

//  test4.cpp
#include <stdlib.h>
#include <unistd.h>

int main()
{
    system("/bin/sh -p");
}
[kusano@hoge suid]$ g++ test4.cpp; sudo chown kusano2: a.out; sudo chmod u+s a.out; ./a.out
sh-4.1$ id
uid=500(kusano) gid=500(kusano) groups=500(kusano),10(wheel)
sh-4.1$ exit
exit

system関数では/bin/sh -cに引数の文字列を渡してコマンドを実行しているから。

//  test5.cpp
#include <stdlib.h>
#include <unistd.h>

int main()
{
    system("/usr/bin/id");
}
//  test6.cpp
#include <stdlib.h>
#include <unistd.h>

int main()
{
    execl("/usr/bin/id", "/usr/bin/id", NULL);
}
[kusano@hoge suid]$ g++ test5.cpp; sudo chown kusano2: a.out; sudo chmod u+s a.out; ./a.out
uid=500(kusano) gid=500(kusano) groups=500(kusano),10(wheel)
[kusano@hoge suid]$ g++ test6.cpp; sudo chown kusano2: a.out; sudo chmod u+s a.out; ./a.out
uid=500(kusano) gid=500(kusano) euid=506(kusano2) groups=507(kusano2),10(wheel),500(kusano)

systemでもkusano2で動かすためには、実ユーザーIDを実効ユーザーIDにすれば良い。ただし、下記のようにsetuid(geteuid())ではダメ。

//  test7.cpp
#include <stdlib.h>
#include <unistd.h>

int main()
{
    setuid(geteuid());
    system("/bin/sh");
}
[kusano@hoge suid]$ g++ test7.cpp; sudo chown kusano2: a.out; sudo chmod u+s a.out; ./a.out
sh-4.1$ id
uid=500(kusano) gid=500(kusano) groups=500(kusano),10(wheel)

setuidのマニュアルに書いてあるように、setuidは0(root)が指定された場合には実ユーザーIDも変更するが、それ以外のユーザーでは実効ユーザーしか変更しないため。setreuidならば実ユーザーIDを変更できる。この場合は、起動したシェルの中でさらシェルを立ち上げてもユーザーIDは変化しない。

//  test8.cpp
#include <stdlib.h>
#include <unistd.h>

int main()
{
    setreuid(geteuid(), -1);
    system("/bin/sh");
}
[kusano@hoge suid]$ g++ test8.cpp; sudo chown kusano2: a.out; sudo chmod u+s a.out; ./a.out
sh-4.1$ id
uid=506(kusano2) gid=500(kusano) groups=507(kusano2),10(wheel),500(kusano)
sh-4.1$ sh
sh-4.1$ id
uid=506(kusano2) gid=500(kusano) groups=507(kusano2),10(wheel),500(kusano)