Ectouch2.0 分析代码审计流程 (四) 任意文件删除/读取(ssrf)

 

0x1 前言

任意文件删除的作用其实很大,危害也很大,比如可以重装系统,然后写入配置getshell等,任意文件读取可以导致ssrf,泄漏config文件等等。这里我还是从Ectouch2.0 分析下我是如何挖掘到任意文件删除的,以及简单谈谈我在挖掘过程遭遇的失败和困难,最终得到了一些审计的绕过思路和一些利用的tips。

 

0x2 审计思路

>1.直接搜索关键词,`file_get_contents` `unlink`  `rename`  `readfile`一些函数
>
>2.看下有木有过滤../,后缀限不限定等等
>
>3.然后就没了、没了(欢迎师傅拍砖,给骚思路)

 

0x3 挖掘鸡肋任意文件读取/ssrf之旅(潜伏漏洞)

首先这个标题名字有点忽悠,这个具体来说是ssrf漏洞,但是产生在了文件读取函数上,因为两者关系密切

故结合来分析下:

按步骤来:

image-20190119233447189

image-20190119233458982

这里说下我是怎么找的,很简单看下后缀,看下文件名是不是有变量,然后简单看下上下文,就是搜索结果显示的那种

很容易就看出来是不是了。

比如这种限定了html后缀就没啥意义了。

image-20190119234145606

继续向下看看,发现一个有趣的点

image-20190120000259200

跟进这个文件:

function get_url_image($url)
{
    $ext = strtolower(end(explode('.', $url)));
    if($ext != "gif" && $ext != "jpg" && $ext != "png" && $ext != "bmp" && $ext != "jpeg")
    {
        return $url;
    }

    $name = date('Ymd');
    for ($i = 0; $i < 6; $i++)
    {
        $name .= chr(mt_rand(97, 122));
    }
    $name .= '.' . $ext;
    $target = ROOT_PATH . DATA_DIR . '/attached/afficheimg/' . $name;

    $tmp_file = DATA_DIR . '/attached/afficheimg/' . $name;
    $filename = ROOT_PATH . $tmp_file;

    $img = file_get_contents($url);

    $fp = @fopen($filename, "a");
    fwrite($fp, $img);
    fclose($fp);

    return $tmp_file;
}

这里说明下这个版本,没有搞topic专题,需要自己加上去,才能进入那个具体类的方法(所以说这挺鸡肋的)

(我感觉未来会完善这个的点,要不然就删文件了)

要修改一些配置,才能成功调用。

    protected function checkLogin() {
        $access = array(
            'crowd' => '*', 
            'wechat' => '*', 
            'extend' => '*', 
            'upload' => '*',
            'topic' => '*',//这个是我自己加的,默认没有,也就是默认这个版本漏洞不存在
            'authorization' => '*', 
            'navigator' => '*',
            'upgrade' => '*',
            'index' => array('license', 'uploader')
        );

下面分析下如何利用漏洞流程:

elseif ($_REQUEST['act'] == 'insert' || $_REQUEST['act'] == 'update')
{
..........................//这些代码到了第四讲没必要去分析了,直接漏洞点
            else if (!empty($_REQUEST['url']))
            {
                /* 来自互联网图片 不可以是服务器地址 */
                if(strstr($_REQUEST['url'], 'http') && !strstr($_REQUEST['url'], $_SERVER['SERVER_NAME']))
                {
                    /* 取互联网图片至本地 */
                    $topic_img = get_url_image($_REQUEST['url']);//这里进入那个漏洞函数
                }
                if(strstr($_REQUEST['url'], 'http') && !strstr($_REQUEST['url'], $_SERVER['SERVER_NAME'])) //这个是判断url是不是存在http和url不能出现$_SERVER['SERVER_NAME'],默认本机内网ip

很简单绕过啦 localhost,跟进漏洞函数

function get_url_image($url)
{
    $ext = strtolower(end(explode('.', $url)));//这个写的感觉有点佛 1.php?1.png
    if($ext != "gif" && $ext != "jpg" && $ext != "png" && $ext != "bmp" && $ext != "jpeg")
    {
        return $url; //这里限定了后缀
    }

    $name = date('Ymd');
    for ($i = 0; $i < 6; $i++)
    {
        $name .= chr(mt_rand(97, 122));
    }
    $name .= '.' . $ext;
    $target = ROOT_PATH . DATA_DIR . '/attached/afficheimg/' . $name;

    $tmp_file = DATA_DIR . '/attached/afficheimg/' . $name;
    $filename = ROOT_PATH . $tmp_file;
    骚操作
    $img = file_get_contents($url);//漏洞点

    $fp = @fopen($filename, "a");
    fwrite($fp, $img);//把获取的内容写在了文件上
    fclose($fp);
    return $tmp_file;
}

后面就是输出图片路径了

(0)实际操作演示

http://127.0.0.1:8888/ecshop/upload2/upload/mobile/admin/topic.php

去添加个专题,burp抓包

image-20190120110717756

post后去访问:

http://127.0.0.1:8888/ecshop/upload2/upload/mobile/admin/topic.php?act=edit&topic_id=3

image-20190120105014407

(1)

image-20190120105054582

(2)

image-20190120105109456

实战利用写个脚本跑跑就好了。

0x3.1 分析下漏洞意义

​ 这里我说任意文件读取,file_get_contents的确支持file协议:

image-20190120105557295

image-20190120105617281

很明显就可以感觉到问题了吧,file协议很明显处理文件名和http协议不同的,在file协议里面

?1.jpg不是参数而是当做了文件名,所以这种想读取文件内容是没办法的,看到p神的小密圈也有人问过

file_get_contents的ssrf的问题,(其实我也没深入研究,只是简单谈谈我的猜想)

回到SSRF的定义上:

SSRF(Server-Side Request Forgery, 服务端请求伪造)利用漏洞可以发起网络请求来攻击内网服务。
利用SSRF能实现以下效果:
1) 扫描内网(主机信息收集,Web应用指纹识别)
2) 根据所识别应用发送构造的Payload进行攻击
3) Denial of service

其实任意文件读取和ssrf关系本来就很密切,区别在于比如限制协议,限制域名啥的,

file_get_content如果能访问内网资源,那么就是ssrf了,防御就是限制内网ip

file_get_content如果能控制协议或者直接文件名,那么就是任意文件读取了

不过如果文件名做了限制什么的,任意文件读取基本就不存在了,参考我上面所说的。

然后就是file_get_contents支持什么协议,

网上一些文章(感觉真的有点low,这些东西还是得去看底层代码(太菜无果),或者去看官方手册找,我没有找到):

大部分 PHP 并不会开启 fopen 的 gopher wrapper
file_get_contents 的 gopher 协议不能 URLencode
file_get_contents 关于 Gopher 的 302 跳转有 bug,导致利用失败
curl/libcurl 7.43 上 gopher 协议存在 bug(%00 截断),经测试 7.49 可用
curl_exec() //默认不跟踪跳转,
file_get_contents() // file_get_contents支持php://input协议

经过测试理论应该支持

https://secure.php.net/manual/zh/wrappers.php

官方的php支持的协议,但是一些gopher,dict我测试是不行的,网上的文章真的很杂很乱,看的心情有点浮躁

(php代码审计进阶之路,深入底层,一个新方向也逐渐复现在脑海里了)

回到漏洞上:

这个漏洞的确可以探测内网的web服务,严格来说的确是个ssrf漏洞,但是ssrf至少我觉得要深入理解的话,还需要做很多工作,这篇主要是分享下挖掘思路,就不展开怎么去研究了。

下面是我在研究中的疑惑,这里简单说下(欢迎有师傅跟我探讨下何为ssrf,ssrf在php的深入理解和利用):

比如这个漏洞有没有绕过后缀限制达到任意读取文件的可能?

image-20190120115823006

image-20190120115831070

关于ssrf的利用,推荐篇文章:

腾讯某处SSRF漏洞(非常好的利用点)附利用脚本

关于ssrf的挖掘,推荐phpoop大佬的一个文章:

混低保-MetInfo系统中隐藏的一处旧插件导致的ssrf

 

0x4 WechatController 任意文件删除

​ 思路同上,搜索关键词:unlink

image-20190120120626400

跟进去看看

    public function article_edit()
    {
        if (IS_POST) {
            $id = I('post.id');
            $data = I('post.data');
            $data['content'] = I('post.content');
            $pic_path = I('post.file_path');//输入
            // 封面处理
            if ($_FILES['pic']['name']) {
                $result = $this->ectouchUpload('pic', 'wechat');
                if ($result['error'] > 0) {
                    $this->message($result['message'], NULL, 'error');
                }
                $data['file'] = substr($result['message']['pic']['savepath'], 2) . $result['message']['pic']['savename'];
                $data['file_name'] = $result['message']['pic']['name'];
                $data['size'] = $result['message']['pic']['size'];
            } else {
                $data['file'] = $pic_path;
            }
            $rs = Check::rule(array(
                Check::must($data['title']),
                L('title') . L('empty')
            ), array(
                Check::must($data['file']),
                L('please_upload')
            ), array(
                Check::must($data['content']),
                L('content') . L('empty')
            ), array(
                Check::url($data['link']),
                L('link_err')
            ));
            if ($rs !== true) {
                $this->message($rs, NULL, 'error');
            }

            $data['wechat_id'] = $this->wechat_id;
            $data['type'] = 'news';

            if (! empty($id)) {
                // 删除图片
                if ($pic_path != $data['file']) {
                    @unlink(ROOT_PATH . $pic_path); //漏洞点
                }
                $data['edit_time'] = gmtime();
                $this->model->table('wechat_media')
                    ->data($data)
                    ->where('id = ' . $id)
                    ->update();
            } else {
                $data['add_time'] = gmtime();
                $this->model->table('wechat_media')
                    ->data($data)
                    ->insert();
            }
            $this->message(L('edit') . L('success'), url('article'));
        }
        $id = I('get.id');
        if (! empty($id)) {
            $article = $this->model->table('wechat_media')
                ->where('id = ' . $id)
                ->find();
            $this->assign('article', $article);
        }
        $this->display();
    }

根据变量的转移流程去简化代码:

   public function article_edit()
    {
        if (IS_POST) {
            $id = I('post.id');
            $data = I('post.data');
            $data['content'] = I('post.content');
            $pic_path = I('post.file_path');//这里可控输入
            if ($_FILES['pic']['name']) {//只要存在上传表单就行了
                $result = $this->ectouchUpload('pic', 'wechat');
                if ($result['error'] > 0) {
                    $this->message($result['message'], NULL, 'error');
                }
                $data['file'] = substr($result['message']['pic']['savepath'], 2) . $result['message']['pic']['savename'];
                $data['file_name'] = $result['message']['pic']['name'];
                $data['size'] = $result['message']['pic']['size'];
            } else {
                $data['file'] = $pic_path;
            }
...............................//中间过程肯定能满足的
            if (! empty($id)) { //这个可控
                // 删除图片
                if ($pic_path != $data['file']) { //
                    @unlink(ROOT_PATH . $pic_path); //漏洞点
                }
....................................
        $this->display();
    }

直接在后台去编辑文章,burp抓包看数据流程:

http://127.0.0.1:8888/ecshop/upload2/upload/mobile/index.php?m=admin&c=wechat&a=article_edit&id=1

image-20190120123058667

选定一个图片,这样$_FILES['pic']['name']就为test.png

image-20190120123444554

这里@unlink(ROOT_PATH . $pic_path); 拼接了文件夹的目录,可以利用多个../来进行绕过

image-20190120123539345

应该还会有其他的点,这里我就不再进行去找了,我个人觉得这种漏洞找很多点出来价值不大。

 

0x6 插入语

向lemon师傅请教了一波,后面我会针对我的困惑,再静心去研究,另写篇文章详尽的说明,希望各位师傅能多多指点。

 

0x5 总结

​ 不知道为什么,挖掘这两种类型漏洞的时候,我感觉很没有意思,这种漏洞拿出来写文章吧,感觉很low,感觉不知道怎么去讲,本来觉得这两种漏洞应该是很水的,但是出乎我意料的是,在审计任意文件读取的时候,激起了我想研究一番ssrf的想法。

这次审计是我觉得浪费时间比较久,感觉写起来怪怪的一次,让我感受到了php实战代码审计和ctf题目其实还是有挺大的区别的,以前我做ctf的审计题,都是一些知识点,但是实战代码审计,你会遇到很多重复,很多迷糊的东西然后感觉很枯燥,丧失了很多热情,比如这个任意文件删除吧,搜索关键词,然后去找就好了,漏洞成因很简单,拿来写文章真的感觉没什么意思,绕过方式也没啥新奇的点。

 

0x7 感想

​ 这次是我<<Ectouch2.0 分析代码审计流程>>系列写的最差的一篇,有点凑漏洞类型的嫌疑,但是我觉得把我自身在学习php代码审计流程遭遇的挫折也能表现给你们看,我感觉也是一种好事吧,因为我以前总是觉得,那些大神很厉害,轻轻松松就挖掘到了漏洞,其实我觉得吧,那些大神肯定也在学习过程中总结积累了很多经验,然后把自己内化的内容精炼的写成文章。最好,我希望这个php代码审计系列能坚持下去,是一个不断发展完善和自我提高的过程。

(完)