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

rr-*_*rr- 8 bash shell

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

我可以使用如下命令:

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

...... 在这个答案中已经提到所有这些.

但是,如果我想作为普通用户测试我或任何超级用户是否可以运行给定命令,它们都不起作用.

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

dummy:~$ command -v poweroff; echo $?
1
dummy:~$ su
root:~# command -v poweroff; echo $?
/sbin/poweroff
0
Run Code Online (Sandbox Code Playgroud)

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

rr-*_*rr- 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
Run Code Online (Sandbox Code Playgroud)

这证实了我上面的陈述.
现在,让我们来看看$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
Run Code Online (Sandbox Code Playgroud)

因此,为了检查给定命令是否对给定用户可用(在您的问题中,即: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
Run Code Online (Sandbox Code Playgroud)

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

替代方案

你可以做的是编写一个使用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;
}
Run Code Online (Sandbox Code Playgroud)

安全说明:上面的版本有非常简单的参数验证(它只允许字符串匹配^[a-z]*$).真正的程序应该包括更好的验证.

测试

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

root:~# gcc ./test.c -o ./test
root:~# chown root:root ./test
root:~# chmod 4755 ./test
Run Code Online (Sandbox Code Playgroud)

需要注意的是chown之前 chmod.在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
Run Code Online (Sandbox Code Playgroud)

最重要的是 - 它足够便携,可以毫无问题地在cygwin上工作.:)

  • 啊,可以使用包含分号的argv [1]运行任何所需的命令 - 第二部分将以完全root访问权限运行("whoami; whoami").您的示例命令将整个系统后门转接给本地用户.如果不删除shell本身的调用,则很难解决由恶意用户对shell进行命令注入的问题. (2认同)
  • 该示例实际上不应该与系统命令相同. (2认同)