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请注意,虚拟用户无论如何都可以自由地查看其中的内容。

4

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

不幸的是,使用 shebang 的东西不能有 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 上工作。:)

于 2013-06-28T17:12:01.387 回答
3

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

可以更安全地测试的是:

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

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

于 2013-06-28T22:33:20.200 回答