我试图了解如何setuid
运作。
所以我制作了一个虚拟程序,它只打印当前用户:
#include<bits/stdc++.h>
using namespace std;
int main(){
cout << system("id -a") << "\n";
cout << system("whoami") << "\n";
}
my-binary
我在用户下编译并创建了可执行文件anmol
:
-rwxrwxr-x 1 anmol anmol 9972 Feb 1 16:54 my-binary
然后,我setuid
使用以下命令设置选项chmod +s
:
-rwsrwsr-x 1 anmol anmol 9972 Feb 1 16:54 my-binary
如果我正常执行它,我会得到以下输出:
uid=1000(anmol) gid=1000(anmol) groups=1000(anmol),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),122(sambashare)
anmol
现在,如果我使用 更改为另一个用户su user2
,然后执行它,我会得到:
uid=1001(user2) gid=1001(user2) groups=1001(user2)
user2
当我使用 执行它时sudo ./my-binary
,我得到:
uid=1001(root) gid=1001(root) groups=1001(root)
root
据我了解,无论我如何运行它,我不应该每次都得到第一个输出吗?
我在这里检查了其他类似的问题,有些建议我检查文件系统是否使用nosuid
选项安装,所以我执行mount | /dev/sda1
并得到了输出:
/dev/sda1 on / type ext4 (rw,relatime,errors=remount-ro,data=ordered)
这意味着该选项未启用。
关于为什么我没有得到预期输出的任何提示?
答案1
库system(3)
函数通过将其传递给 来运行其命令参数/bin/sh -c
,/bin/sh
Linux 上的 ( 、bash
或dash
)mksh
将放弃任何 setuid 或 setgid 权限,除非使用该-p
选项调用。
bash 手册页说:
如果 shell 启动时有效用户(组)id 不等于真实用户(组)id,并且选项
-p
为不提供[...]有效用户ID设置为真实用户ID。
对于dash
(/bin/sh
来自 Debian/Ubuntu),这是有点新:Debian 9 Stretch (2017) 中还没有这种情况,它只是 Debian 特定的改变,仍然不在上游来源截至 2020 年 2 月 4 日。bash
从 2.X 版本开始(首次包含在 RedHat 7.X,2000 年)就已经有了这个。
这是情况仍然不是这样与其他 shell(ksh93
、zsh
等)或来自/bin/sh
其他系统(OpenBSD、FreeBSD、Solaris;但不是 NetBSD)改变了像 bash 一样工作)。
如果他们拥有它,他们的特权模式就可以工作不同地与 bash 相比:它已经变成了在默认情况下,当 shell 以 setuid 模式运行时,您必须将其打开离开withset +p
以使 shell 删除 setuid 权限。
如果您想直接从 setuid 二进制文件使用system()
、popen()
运行 shell 或可执行 shell 脚本,那么您应该放弃任何“人格分裂”并通过以下方式完全切换到您的真实或有效凭据setres[ug]id
:
% cat a.cc
#include <unistd.h>
#include <stdlib.h>
int main(){
uid_t euid = geteuid();
setresuid(euid, euid, euid);
system("id");
}
% c++ a.cc
% chmod u+s a.out
% ./a.out
uid=1002(fabe)
% su -c ./a.out
uid=1002(fabe)
如果您只想检查您的二进制文件是否确实切换了其有效凭据,请直接执行此操作,而不是通过以下方式调用外部程序system()
:
#include <iostream>
#include <unistd.h>
#include <pwd.h>
using namespace std;
int main(){
uid_t euid = geteuid(); struct passwd *pw = getpwuid(euid);
cout << "euid=" << euid;
if(pw) cout << ", " << pw->pw_name;
cout << endl;
}