前言
记录一下最近碰到的几处cms路径穿越的问题(均在后台),均已提交CNVD。
正文
1.某cms的任意文件读取和删除
这个cms目前还是处于更新的状态,来到更多功能-模板管理-新建文件:发现会报错:
根据链接:
往回看下源码:
function addFile() {
$root = app()->getRootPath() . 'public' . DIRECTORY_SEPARATOR;
if (!$this->request->isPost()) {
$dir = rtrim(I('get.path'), '/');
if (strpos($dir, 'template') === false) {
exit('error|目录错误!');
}
$path = $root . str_replace('/', DIRECTORY_SEPARATOR, $dir);
$info = [
'name' => 'newfile.html',
'content' => '',
'mode' => 'htmlmixed',
'isNew' => true,
'path' => $dir,
];
if (is_file($path)) {
$parts = pathinfo($path);
$dir = str_replace('/' . $parts['basename'], '', $dir);
$info['name'] = $parts['basename'];
$info['content'] = file_get_contents($path);
$info['isNew'] = false;
if ($parts['extension'] == 'js') {
$info['mode'] = 'text/javascript';
} elseif ($parts['extension'] == 'css') {
$info['mode'] = 'text/css';
} elseif ($parts['extension'] == 'json') {
$info['mode'] = 'application/json';
}
} elseif (is_dir($path)) {
strpos($path, '.') !== false and exit('error|目录错误!');
} else {
exit('error|目录错误!');
}
$this->assign('dir', $dir);
$this->assign('info', $info);
return $this->fetch();
}
$dir = rtrim(I('post.path'), '/');
$name = I('post.name');
$content = I('post.content');
if (strpos($dir, 'template') === false) $this->error('目录错误');
$path = $root . str_replace('/', DIRECTORY_SEPARATOR, $dir);
if (is_file($path)) {
$parts = pathinfo($path);
$dir = str_replace('/' . $parts['basename'], '', $dir);
$new = str_replace($parts['basename'], $name, $path);
$parts = pathinfo($new);
if (!in_array($parts['extension'], ['js', 'html', 'css', 'txt', 'json'])) {
$this->error('只允许操作文件类型如下:html|js|css|txt|json');
}
file_put_contents($path, $content);
rename($path, $new) or $this->error('操作失败,请检查文件目录权限!');
} elseif (is_dir($path)) {
$path = $path . DIRECTORY_SEPARATOR . $name;
$parts = pathinfo($path);
if (!in_array($parts['extension'], ['js', 'html', 'css', 'txt', 'json'])) {
$this->error('只允许操作文件类型如下:html|js|css|txt|json');
}
$rs = file_put_contents($path, $content);
$rs === false and $this->error('操作失败,请检查文件目录权限!');
} else {
$this->error('目录错误!');
}
$this->success('操作成功!', U('Template/fileList') . '?path=' . urlencode($dir));
}
}
get请求进入第一个if后,通过
$dir = rtrim(I('get.path'), '/');
获取path参数,经过一个判断
if (strpos($dir, 'template') === false) {
exit('error|目录错误!');
}
然后直接拼接到
$path = $root . str_replace('/', DIRECTORY_SEPARATOR, $dir);
然后通过下面代码返回结果:
$info['content'] = file_get_contents($path);//读取文件
//调用并显示
$this->assign('info', $info);
return $this->fetch();
利用(为了绕过判断,请求路径用template/..实行):
原始包
修改包
响应
通篇来看该cms的文件操作通过数据库实现,因此一定程度上避免了该类问题的发生。
后台清除缓存:
源码:
function clearCache() {
if (!$this->request->isPost()) {
return $this->fetch();
}
if (!function_exists('unlink')) {
$this->error('php.ini未开启unlink函数,请联系空间商处理!');
}
$clear = I('post.clearCache', []);
clearCache($clear);
$this->success('操作成功');
}
//跟进clearCache
function clearCache($clears = []) {
if ($clears === true || $clears === 'all') {
$clears = ['cache', 'log', 'temp'];
}
$apps = C('config.apps');//清除的应用
$runtime = app()->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR;
foreach ($clears as $item) {
if ($item == 'cache') {
delFile($runtime . $item, true);
continue;
}
if ($item == 'log') {
delFile($runtime . $item, true);
}
foreach ($apps as $app) {
$path = $runtime . $app . DIRECTORY_SEPARATOR . $item;
delFile($path, true);
}
}
/*清除旧升级备份包,保留最后一个备份文件*/
$backupArr = glob($runtime . 'data/backup/v*_www');
for ($i = 0; $i < count($backupArr) - 1; $i++) {
delFile($backupArr[$i], true);
}
delFile($runtime . 'schema');
delFile($runtime . 'log');//调试时生成的日志文件
return true;
}
原理跟上面类似,也是直接拼接:
2.某cms任意文件下载
该cms也是处于更新状态,系统管理-数据还原-备份下载
请求包
通过路由找到源码:
// 下载
public function downfile(string $name)
{
$file_name = $name; //得到文件名
header("Content-type:text/html;charset=utf-8");
$file_name = iconv("utf-8", "gb2312", $file_name); // 转换编码
$file_sub_path = $this->config['path']; //确保文件在这个路径下面,换成你文件所在的路径
$file_path = $file_sub_path . $file_name;
# 将反斜杠 替换成正斜杠
$file_path = str_replace('\\', '/', $file_path);
if (!file_exists($file_path)) {
$this->error($file_path);exit; //如果提示这个错误,很可能你的路径不对,可以打印$file_sub_path查看
}
$fp = fopen($file_path, "r"); // 以可读的方式打开这个文件
# 如果出现图片无法打开,可以在这个位置添加函数
ob_clean(); # 清空擦掉,输出缓冲区。
$file_size = filesize($file_path);
//下载文件需要用到的头
Header("Content-type: application/octet-stream");
Header("Accept-Ranges: bytes");
Header("Accept-Length:" . $file_size);
Header("Content-Disposition: attachment; filename = " . $file_name);
$buffer = 1024000;
$file_count = 0;
while (!feof($fp) && $file_count < $file_size) {
$file_con = fread($fp, $buffer);
$file_count += $buffer;
echo $file_con;
}
fclose($fp); //关闭这个打开的文件
}
这个$file_name直接拼接,没有经过过滤,利用
3.某cms两处任意文件删除
该cms较老,且处于停更状态,一次偶然机会碰到的。
一、后台附件管理-图片管理可删除图片,
得到删除包:
ids和path可控,找到对应方法:admincms\contraller\upfile.contraller.php
传入的ids 用 “,” 进行分割,然后直接赋值没有过滤,跟进$this->pagebls['path']
发现也是直接赋值,没有过滤,修改ids 为 ,../s,txt, 的形式
二、admincms\contraller\template.contraller.php
找到模板管理,选中一个文件点击删除:
得到删除包
发现id[]是可控的,对应到源码:
发现$idd可控,跟进$this->pagebls['path']
发现$this->pagebls['path']也可控,修改id为id[]=../s.txt
结尾
这东西就是图一乐啊,继续搬砖了。