![当从 C 程序的 AWK 脚本调用 cat 时,SETUID 无效](https://linux22.com/image/211308/%E5%BD%93%E4%BB%8E%20C%20%E7%A8%8B%E5%BA%8F%E7%9A%84%20AWK%20%E8%84%9A%E6%9C%AC%E8%B0%83%E7%94%A8%20cat%20%E6%97%B6%EF%BC%8CSETUID%20%E6%97%A0%E6%95%88.png)
考虑下面的简化示例来说明我的问题。
服务器的操作系统是Debian 11 armhf。
我们在目录中/botm/test/
:
drwx------ 2 b b 4096 Sep 27 19:27 hide
-rwsr-xr-x 1 b b 8260 Sep 27 19:54 test1
-rwsr-xr-x 1 b b 8264 Sep 27 19:58 test2
-rw-r--r-- 1 b b 56 Sep 27 19:52 test1.awk
-rw-r--r-- 1 b b 172 Sep 27 19:54 test1.c
-rw-r--r-- 1 b b 186 Sep 27 19:58 test2.c
可以看出,只有所有者b
才能访问该目录/botm/test/hide
。
我们现在是用户,test
我们无法访问该文件/botm/test/hide/test.txt
:
$ cat hide/test.txt
cat: hide/test.txt: Permission denied
好的。程序test1
已test2
设置 SETUID 位。
因此,即使我们是用户,test
我们也可以像用户一样运行这些程序b
。
第一的,test2.c
:
/* test2.c */
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int r;
r = execl("/usr/bin/cat","/usr/bin/cat","/botm/test/hide/test.txt", (char *)0);
printf("%d\n",r);
return r;
}
$ ./test2
hidden content
$
正如预期的那样,它有效。
现在,test1.c
,test1.awk
:
/* test1.c */
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int r;
r = execl("/usr/bin/mawk","/usr/bin/mawk","-f","/botm/test/test1.awk", (char *)0);
printf("%d\n",r);
return r;
}
# test1.awk
BEGIN {
system("cat /botm/test/hide/test.txt");
};
$ ./test1
cat: /botm/test/hide/test.txt: Permission denied
$
令人惊讶的是,这不起作用。
我的期望:
如果我运行具有 SETUID 的程序test1
,那么它应该像用户一样运行b
。
因此,如果程序调用,/usr/bin/mawk
那么它也应该以 user 身份运行b
。
因此,如果 awk 脚本随后调用cat
,那么cat
也应该以用户身份运行b
。
因此cat
应该能够访问/botm/test/hide/test.txt
仅对用户可用的内容b
。
但这并没有发生。
现在有一些真实的背景。
上面只是一个简化的例子。我有一个包含多个 C 和 AWK 程序的项目。从 C 程序调用 AWK 程序来处理一些文本并生成一些输出。这有时包括调用cat
或其他系统工具。其中一些程序是作为用户从 Apache 服务器运行的,www-data
该用户无权(也不应该有权)访问这些程序创建的某些临时文件。这就是使用 SETUID 的原因。而且这种工作流程在这个项目中被大量使用(从2014年开始)。
我现在正在将其从我的旧服务器(这有效)移动到新服务器(这不起作用)。将项目更改为不依赖此机制将是一个非常大的重新设计,我现在不想这样做。
当 C 程序(带有 SETUID)调用 AWK 脚本(该脚本调用系统工具(cat
, ...)时,为什么不保留 SETUID?
该怎么做才能再次保存它?
在系统的早期版本上它可以工作。
编辑添加:
我验证了 AWK 程序仍然可以访问隐藏文件,只有 don 调用的文件system()
不能访问。
所以我发现这种行为的原因是当使用system()
(或getline
使用管道)然后mawk
调用sh
执行命令时。
AWK 没有exec
或类似的东西。
sh
除非用 调用,否则将删除 SETUID -p
。
这是新行为。这部分内容man sh
不存在于我的旧服务器上:
-p priviliged Do not attempt to reset effective uid if it does not match uid. This is not set by default to help avoid incorrect usage by
setuid root programs via system(3) or popen(3).
因此,为了让我的程序再次运行,我必须至少实现以下目标之一:
- make
/usr/bin/sh
默认情况下不删除 SETUID。 - make
mawk
或其他兼容的 AWK 解释器-p
在使用/usr/bin/sh
for时使用system()
- 找到一种不同的方式来运行 AWK 程序
- 重新设计所有程序以不依赖于此。例如,用 C、Perl 或 Python 执行它们,它们确实具有不依赖于
sh
.
重新设计整个项目(以及其他一些也使用此工作流程的项目)将花费我目前没有的精力和时间,因此我确实需要以某种方式恢复完整的 SETUID 功能。
实际上,可以在 AWK 程序中轻松地重新创建的功能,cat
而且在我的项目中还可以调用其他工具system()
:[ -f
、、、、、、、、、。在大多数情况下,在没有 SETUID 的情况下调用它们是不可接受的。cat
cp
mkdir
mv
sleep
wget
答案1
在这个类似问题的答案中:
https://unix.stackexchange.com/a/565254/543092
有一个建议可以使用setresuid()
。
由于在我的项目中,AWK 脚本总是从 C 程序调用,因此这是我可用的解决方案。
我做了这样的事情:
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
int main(int argc, char **argv)
{
uid_t euid;
int r;
euid = geteuid();
r = setreuid(euid, euid);
if (r != 0)
return (r = errno);
r = execl("/usr/bin/mawk","/usr/bin/mawk","-f","/botm/test/test1.awk", (char *)0);
printf("%d\n",r);
}
这样“伪装”就完成了,并且sh
不会知道之前有SETUID。一切正常。
这是一个比以某种方式使sh
始终表现得好像存在更好的解决方案-p
。如果我知道我在做什么,它将保留我的 SETUID。
我使用setreuid()
而不是setresuid()
那里建议的,因为:
setreuid()
足够setresuid()
是一个 GNU 扩展。