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

时间:2013-06-28 17:12:01

标签: bash shell

我想检查一个程序是否安装在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中的内容。

2 个答案:

答案 0 :(得分:13)

问题的根源

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

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

请注意,chownchmod之前。通常4模式之前的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上工作。 :)

答案 1 :(得分:3)

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

可以更安全地测试的是:

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

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