【问题标题】:Check if a command exists in Bash (including superusers)检查 Bash 中是否存在命令(包括超级用户)
【发布时间】:2013-07-01 02:13:00
【问题描述】:

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

我可以使用如下命令:

  • command -v,
  • hash,
  • type,
  • which...

...所有这些都已被提及in this answer

但是,如果我想作为普通用户进行测试,无论我还是任何超级用户都可以运行给定的命令,它们都不起作用。

这是我的意思的一个例子:

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

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

【问题讨论】:

    标签: bash shell


    【解决方案1】:

    问题的根源

    您尝试的命令不起作用的原因是它们仅在$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 bit脚本 程序。 Setuid 位是 Linux 操作系统的一个有点隐藏的功能,它允许程序以其所有者权限执行。所以你写了一个程序,它像超级用户一样执行一些命令,除了它可以由普通用户运行。这样你就可以像 root 一样看到command -v poweroff 的输出。

    Unfortunately, stuff that uses shebang can't have setuid bit,所以你不能为此创建一个 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 位于 之前 chmod4 在通常的 755 模式之前是 setuid 位。
    现在我们可以以普通用户的身份测试程序了。

    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 上工作。 :)

    【讨论】:

    • 你的答案真的很好!
    • 您也应该在unix.stackexchange.com 上发布问题和答案
    • 根据meta.stackexchange.com/questions/64068 我不应该这样做。我在这里发布它是因为这里有 23k 个问题标记为bash,而 unix.stackexchange.com 只有 2k 个。
    • 啊,可以使用包含分号的 argv[1] 运行任何所需的命令 - 第二部分将以完全 root 访问权限运行 ("whoami ; whoami")。您的示例命令将整个系统后门给本地用户。如果不删除 shell 本身的调用,将 shell 暴露给敌对用户的命令注入是非常困难的。
    • 这个例子真的不应该和系统命令一样命名。
    【解决方案2】:

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

    可以更安全地测试的是:

    • 任何人都可以运行由完整路径名给出的命令吗? (跳过启动脚本疯狂)
    • 当前用户能否运行简单(未完全路径)命令?
    • 当前用户可以用 sudo 运行简单的命令吗? (这对 root 的启动脚本做了一些假设)
    • 任何运行默认环境的用户都可以运行该命令吗? (使用具有已知设置的虚拟用户)。

    在拥有数千名用户的大型系统上,“任何人”选项并不实用。成熟的网站也不希望有 root 权限的命令运行任意用户的脚本,即使是那些启用了 sudo 的、通常值得信赖的管理员也是如此。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-01
      • 2015-02-23
      • 2011-02-12
      • 1970-01-01
      • 1970-01-01
      • 2016-07-22
      • 2015-04-07
      相关资源
      最近更新 更多