在Bash中检查命令是否存在(包括超级用户)

10

我想检查UNIX系统上是否安装了某个程序。

我可以使用命令如下:

  • command -v
  • hash
  • type
  • which

其中所有这些命令都已经在此答案中提到过

然而,如果我想要测试,作为普通用户,我或者任何超级用户是否可以运行给定的命令,则以上所有命令都无法工作。

以下是我指的示例:

dummy:~$ command -v poweroff; echo $?
1
dummy:~$ su
root:~# command -v poweroff; echo $?
/sbin/poweroff
0

正如您所看到的,普通用户没有发现poweroff命令的存在。请注意,虚拟用户可以自由地查看/ sbin 中的内容。

2个回答

14

问题来源

你尝试的命令无法工作的原因是它们只在$PATH变量中查找可执行文件。首先,让我们测试一下我们的假设。

dummy:~$ mkdir test
dummy:~$ cd test
dummy:~/test$ echo '#!/bin/sh' >test.sh
dummy:~/test$ chmod +x test.sh
dummy:~/test$ cd
dummy:~$ command -v test.sh
dummy:~$ PATH+=:/home/dummy/test/
dummy:~$ command -v test.sh
/home/dummy/test/test.sh

这证实了我之前的陈述。
现在,让我们来看看不同用户的$PATH是什么样子:

dummy:~$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
dummy:~$ su
root:~# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

因此,为了检查特定用户(在您的问题中,即root)是否可以使用给定的命令,您需要知道他的$PATH环境变量。

解决方案

Debian上这种环境变量的值通常可以在/etc/profile/etc/environment/文件中找到。没有简单的方法可以从文件中获取这些值。

最基本的解决方案是将已知目录临时添加到您的$PATH变量中,然后使用command -v

dummy~$ OLDPATH=$PATH
dummy~$ PATH=$OLDPATH:/sbin:/usr/sbin/:/usr/local/sbin/
dummy~$ command -v poweroff
/sbin/poweroff
dummy~$ PATH=$OLDPATH

这种解决方案有一个问题:如果你想要可移植性,你不知道应该连接哪些文件夹。在大多数情况下,这种方法应该足够了。

另一种解决方案

相反,你可以编写一个使用 setuid 位脚本 程序来代替。Setuid 位是 Linux 操作系统的一个隐蔽功能,允许程序以其所有者权限执行。因此,你可以编写一个程序,像超级用户那样执行一些命令,只是普通用户也可以运行它。这样,你就可以像 root 一样查看 command -v poweroff 的输出。

不幸的是,使用 shebang 的东西不能具有 setuid 位,所以你不能为此创建一个 shell 脚本,你需要一个 C 程序。这是一个可以完成工作的示例程序:

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

int main(int argc, char** argv)
{
    if (argc <= 1)
    {
        fprintf(stderr, "No arguments.\n");
        return 1;
    }

    //validate the argv
    char* prog = argv[1];
    int i;
    for (i = 0; i < strlen(prog); i ++)
    {
        if (prog[i] < 'a' || prog[i] > 'z')
        {
            fprintf(stderr, "%s contains invalid characters (%c), exiting.", prog, prog[i]);
            return 1;
        }
    }

    //here's the `which` command. We start it in new interactive shell,
    //since this program inherits environment variables from its
    //parent shell. We need to start *new* shell that will initialize
    //and overwrite existing PATH environment variable.
    char* command = (char*) malloc(strlen(prog) + 30);
    if (!command)
    {
        fprintf(stderr, "No memory!\n");
        return 1;
    }
    sprintf(command, "bash -cli 'command -v %s'", prog);

    int exists = 0;
    //first we try to execute the command as a dummy user.
    exists |= system(command) == 0;
    if (!exists)
    {
        //then we try to execute the command as a root user.
        setuid(0);
        exists |= system(command) == 0;
    }
    return exists ? 0 : 1;
}

安全提示: 上述版本只进行了非常简单的参数验证(只允许通过与^[a-z]*$匹配的字符串)。真实的程序应该包括更好的验证。

测试

假设我们将文件保存在test.c中。我们编译它并添加setuid位:

root:~# gcc ./test.c -o ./test
root:~# chown root:root ./test
root:~# chmod 4755 ./test
请注意,chown应该在chmod之前执行。通常的755模式中的4是设置UID位。
现在我们可以将程序作为普通用户进行测试。
dummy:~$ ./test ls; echo $?
alias ls='ls -vhF1 --color=auto --group-directories-first'
0
dummy:~$ ./test blah; echo $?
1
dummy:~$ ./test poweroff; echo $?
/sbin/poweroff
0

最棒的是——它足够便携,在cygwin上使用也没有问题。 :)


你的回答真的表达得非常好! - Chaos
你应该在 http://unix.stackexchange.com 上发布问题和答案。 - iruvar
1
根据http://meta.stackexchange.com/questions/64068/,我不应该这样做。我发布到这里是因为这里有23k个标记为`bash`的问题,而unix.stackexchange.com只有2k个。 - rr-
你缺少了 stdio.h、malloc.h 和 string.h 的头文件。此外,一个带有 set-root-ID 权限的进程的有效 UID 是 root,并且 setuid(geteuid()) 只是复制了 root 的权限。使用“blah”的测试并不能揭示程序一直以 root 身份运行的事实。 - Alex North-Keys
2
啊,通过使用包含分号的argv[1],可以运行任何所需的命令 - 第二部分将以完全的根访问权限运行("whoami ; whoami")。你的示例命令为本地用户打开了整个系统的后门。暴露shell给恶意用户进行命令注入是非常难以解决的,除非移除对shell本身的调用。 - Alex North-Keys
2
这个例子的名称真的不应该与系统命令同名。 - Alex North-Keys

3
真正的答案是,如果你的意思是:“任何有sudo访问权限的用户是否可以在搜索路径中看到此命令?”,那么你无法满足“任何超级用户”方面的要求。原因是,你必须运行每个用户的启动脚本,才能找出他的搜索路径最终是什么 - 许多用户会包括自己的~/bin或~/pod/abi/x86_64-ubu-1204/bin等等,而且还有其他的(例如/afs//bin) - 而且许多启动脚本都有副作用,可能会使整个过程变得非常混乱,包括生成日志、启动各种守护程序等等。如果其中一个用户的启动脚本尝试运行你的新命令本身,那么你真的会遇到麻烦,因为它们将递归并对你自己的系统造成拒绝服务攻击。

您可以更安全地测试以下内容:

  • 任何人是否可以通过完整路径名运行命令?(跳过启动脚本的疯狂)
  • 当前用户是否可以运行简单的(不完整路径的)命令?
  • 当前用户是否可以使用sudo运行简单的命令?(这假设了root的启动脚本)
  • 是否有任何运行默认环境的用户可以运行该命令?(使用已知设置的虚拟用户)。

在具有数千个用户的大型系统上,“任何人”选项并不实用。成熟的站点也不希望在sudo启用的可信管理员之外,运行具有root权限的命令来运行任意用户的脚本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接