审计了一个比较小众的cms,官网提供了两个版本,基础版和ugc投稿版版,后者相对前者提供了用户注册投稿等功能,以下漏洞针对两个版本都存在
SSYCMS内容管理系统采用热门框架,方便开发者二次开发系统,前台采用Bootstrap4.x,后台采用vue2.x、iview3.x。系统使用Thinkphp5.x框架,架构模式为PHP+MYSQL
只需要注意手动创建数据库,其他没什么
首先登陆后台
在内容->全部内容处添加内容即可,需要注意标题和标签字段可以直接触发,描述和关键词字段需要闭合,内容字段则不能触发
结果如图所示
同样可以触发的还有功能->单个页面处,这里也可以自定义页面的路由,同样标题直接触发,关键词和描述需要闭合
如图所示
同样友情链接处也存在触发点,这里名称网址描述三个字段都可以直接触发
以第一处为例
通过路由找到调用方法
POST /index.php?s=/article/ApiAdminArticle/itemAdd HTTP/1.1
public function itemAdd()
{
$data = $this->request->post();
if (!isset($data['title']) || !$data['title']) {
return jsonError('请输入标题');
}
$itemInfo = db($this->item)->where('title',$data['title'])->find();
if ($itemInfo) {
return jsonError('标题已存在');
}
$fieldList = input('post.fieldList');
$fieldList = json_decode($fieldList,true);
$res = model($this->itemModelNameSpace)->itemAdd($data,$fieldList);
if ($res) {
return jsonSuccess('操作成功');
} else {
return jsonError('操作失败');
}
}
通过post方法获取数据,跟踪
public function post($name = '', $default = null, $filter = '')
{
if (empty($this->post)) {
$content = $this->input;
if (empty($_POST) && false !== strpos($this->contentType(), 'application/json')) {
$this->post = (array) json_decode($content, true);
} else {
$this->post = $_POST;
}
}
if (is_array($name)) {
$this->param = [];
$this->mergeParam = false;
return $this->post = array_merge($this->post, $name);
}
return $this->input($this->post, $name, $default, $filter);
}
继续跟踪input
public function input($data = [], $name = '', $default = null, $filter = '')
{
......
// 解析过滤器
$filter = $this->getFilter($filter, $default);
if (is_array($data)) {
array_walk_recursive($data, [$this, 'filterValue'], $filter);
reset($data);
} else {
$this->filterValue($data, $name, $filter);
}
if (isset($type) && $data !== $default) {
// 强制类型转换
$this->typeCast($data, $type);
}
return $data;
}
因为过滤器参数为空,所以直接返回数据
回到itemEAdd方法,除去判断之外,调用模块中的itemEdit方法,其中$this->itemModelNameSpace
值为app\article\model\Articles
,继续跟踪
鉴于代码比较长,只贴出关键部分
......
$uuid = uuid();
$imgUrl = $paramData['img_url'] ? $paramData['img_url'] : getImgUrlByContent($paramData['content']);
$paramData['description'] = $paramData['description'] ? $paramData['description'] : mb_substr(str_replace('"','',deleteHtml($paramData['content'])),0,88,'utf-8');
$paramData['keywords'] = $paramData['keywords'] ? $paramData['keywords'] : $paramData['tags'];
$indexId = db($this->item)->insertGetId(array (
'uuid' => $uuid,
'cid' => $paramData['cid'],
'uid' => $paramData['uid'],
'views' => $paramData['views'] ? $paramData['views'] : 0,
'is_recommend' => $paramData['is_recommend'] ? $paramData['is_recommend'] : 0,
'title' => $paramData['title'],
'img_url' => $imgUrl,
'publish_time' => $paramData['publish_time'] ? $paramData['publish_time'] : time(),
'description' => $paramData['description'],
'keywords' => $paramData['keywords'],
));
$content = $paramData['content'];
db($this->itemContent)->insert(array(
'id' => $uuid,
'content' => htmlspecialchars($content),
));
......
可以看到标题等参数没有经过过滤直接放到了数据库中,而内容则经过了过滤放到了另一个表中,所以不能触发
从数据库中能直观的看到这一点
UGC版还提供了用户投稿功能,但并没有出现XSS漏洞,原因也很简单
public function itemAdd()
{
$title = Htmlp::htmlp(input('post.title'));
$content = Htmlp::htmlp(input('post.content'));
$cid = Htmlp::htmlp(input('post.cid'));
$tags = Htmlp::htmlp(input('post.tags'));
$img_url = Htmlp::htmlp(input('post.img_url'));
$description = Htmlp::htmlp(input('post.description'));
$keywords = Htmlp::htmlp(input('post.keywords'));
$cid = $cid ? $cid : 0;
......
}
跟进htmlp方法
class Htmlp
{
static public function htmlp($dirty_html)
{
$config = HTMLPurifier_Config::createDefault();
$purifier = new HTMLPurifier($config);
return $purifier->purify($dirty_html);
}
}
调用时利用HTMLPurifier进行了过滤,至于为什么管理员发布的文章没有过滤。。。可能是觉得管理员不会搞破坏?
漏洞危害也不大。。。聊胜于无