0x00 前言
在之前几篇的通读MVC的文章中,访问的控制器大部分是由外部参数来决定的,那么本篇文章所通读的MVC框架与之前的一系列MVC框架不太一样,它的路由是由程序本身的路由表来决定的。
我们本次通读的CMS为ShangFanCMS,算得上比较冷门的CMS。
源码下载地址:https://www.jb51.net/codes/673736.html
0x01 MVC的了解
老样子,整个故事从index.php开始说起。
从图中我们看到,其他CMS都是用常量来定义常量让整个程序拿来运用,而它却是定义全局变量。这里我们直接在底部var_dump即可。
提取到程序主要变量之后,我们直接把目光放到第14行中。
可以看到。/framework/Application.php文件中只是定义了一个类而已,我们看到index.php文件中的第15行直接调用了Application类下的run静态方法。我们看一下run静态方法是怎么玩的。
我们可以看到,run方法的前三行是分别用来定义时区,服务器返回的header头以及屏蔽错误用的。
那么第Application.php文件的第28行包含进来一个function.php,该文件定义了许多公共的函数,这些函数可能在程序的应用上所用到。
而在Application.php文件的第29 – 34行中,使用spl_autoload_register函数来动态的包含类文件,这个函数我们可以暂时先放到这里不管,当我们审计到不认识的类文件时,再来看一下也无妨。
现在我们把目光放在Application.php文件的第35行:return static::runContext();
self与static的区别笔者在一篇文章有了解过,贴出链接:https://blog.csdn.net/qmhball/article/details/77369457
在Application.php文件的第44 – 46行中,我们看到调用了appPath()函数,想必是在我们包含function.php文件中的。
可以看到,appPath与rootPath函数无非就是将$GLOBALS[‘app’][‘app_path’]与$GLOBALS[‘app’][‘root_path’]给封装了起来。如图:
我们转回头来继续看Application.php文件中的逻辑。
将 ./shangfan/function/mall.php、./shangfan/function/member.php、./shangfan/function/merchant.php文件统统包含进来,我们分别看一下这三个文件是怎么玩的。
都是分别定义了一些方法而已。那么我们继续往下通读。
register_shutdown_function函数是在整个程序运行结束后调用的,可以看到回调函数中return了一个Exception类下的fastCgiCatchError方法,看到第7行use framework\Exception,然后调用spl_autoload_register函数来进行包含,现在我们了解spl_autoload_register比较清晰。
从spl_autoload_register函数中的回调函数中我们看到,只是包含 ./命名空间/类名.php 文件。这里我们打开./framework/Exception.php文件看一下fastCgiCatchError方法是怎么玩的。
fastCgiCatchError方法的$_error是用来得到程序最终运行的错误信息,我们可以看到第34行中调用了configSystem方法,我们看一下这个方法是怎么玩的。
configSystem只是封装了\framework\config命名空间下的Config类下的make方法,并且我们注意到这个第三个参数的true。
当调用不存在的类文件时,会进入到spl_autoload_register方法,spl_autoload_register方法之前我们有了解过,这里我们直接包含 /framework/config/Config.php 文件看下make静态方法是怎么玩的。
代码的逻辑在图中详细的写出了,这里只是获取到./config.php文件debug的值。
这里可以为true或false,但两者还是有区别的。
可以看到如果debug为true,则进入到View类的output函数,如果debug为false,则进入到File类的writeStorageLog函数,我们通过类的命名方式可以猜测到,View是程序出Bug时的回显,而File则是写入错误日志,当然这只是猜测,我们还要进一步通读。
在第38行调用了stringFormat方法,将错误信息依次传入进去了,这里我们看一下stringFormat方法是怎么玩的。
可以看到stringFormat方法只是将错误信息都整理成字符串。这里我们目光直接转移到39行的return File::writeStorageLog($message);
跟进File类看一下writeStorageLog方法是怎么玩的。(./framework/file/File.php文件)
通过这里我们可以了解到,如果config.php文件的debug定义为false,则会开启错误日志记录,会记录到 ./storage/log/当前日期.log 中。
这里笔者进行测试。
好,debug机制我们了解完毕后我们回到./framework/Application.php文件中继续读取。
在第37行中调用了RouteDriver::driverReady(),我们跟进./framework/route/Driver.php文件看一下driverReady方法是怎么玩的。
将图中左侧中/shangfan/route/base/h5、/shangfan/route/base/web目录中的所有文件统统包含进来。
我们看一下是怎么玩的。
简单打开两个文件后,我们看到有那么多行代码在我们面前,都是调用了Route::get、Route::post以及Route::any,这里我们打开一下./framework/route/Route.php文件是怎么玩的。
从图中我们可以看到,get、post、any都调用了restful方法,区别只是第三个参数的传递不同,在restful方法中,我们可以看到,原来上一张那么多代码是用来生成路由表的。从第63行的@符的分隔,再加上第70行的method下标我们可以猜测出默认调用的方法是什么。
我们回到./framework/Application.php文件。
从第38行看到,这里并不是使用$_SERVER[REQUEST_URI]来接收,而是使用filter_input(INPUT_SERVER, REQUEST_URI),这两种接收方式理论上都是可以的。随后调用到Route类中的search方法,我们看一下search方法都做了一些什么。
用来处理REQUEST_URI来使网站支持静态化、参数接收、请求类型检测等功能。
我们回到Application.php文件。
什么?要删除路由表?这里我们要在unset之前提取出路由表来,方便我们后期的审计!
将路由表扒出来后继续通读。
这里callBeforeMiddleware方法与callAfterMiddleware方法在开发中其实都是new到了\framework\Middleware类,我们看一下/framework/Middleware.php文件中Middleware类的context方法。
这里直接return一个true,所以进入不到上张图的80-82 / 91-93行中。
可以看到47行的操作是用来处理Controller的。随后使用call_user_func_array方法来回调控制器下的某个方法。
这里还有最后的第55行,我们看一下View是从哪儿来的。
这里第55行,View并没有被use,那么就是当前namespace下,包含./framework/View.php文件,看一下output方法是怎么玩的。
使用了php://output来输出内容(View)。
我们整个路由清晰,自定义一个自己的路由器~
笔者在./shangfan/route/base/h5/mall.php文件中,添加一个这样的代码。
Route::get(‘/h5/mall/Heihu’, ‘\shangfan\controller\develop\h5\mall\HeihuController@index’);
创建./shangfan/controller/develop/h5/mall/HeihuController.php文件内容如下:
<?php
namespace shangfan\controller\develop\h5\mall;
class HeihuController {
public function index(){
echo ‘Hello, World!’;
}
}
访问:
我们路由清晰,开始挖掘漏洞~
0x02 管理员密码重置漏洞
在./install/action.php文件中,并没有执行exit。
这导致了可以进行系统重装。
在第54行中,可以看到先执行删除admin用户而又在55行insert了一条新的admin用户,这样会导致admin密码被修改。
POST提交:install/action.php
Payload:type=setup3&passwd1=admin123&passwd2=admin123
成功修改admin密码。
0x03 无数个SQL注入漏洞
为什么说是无数个SQL注入呢?我们详细说一下。
跟进./shangfan/controller/develop/web/admin/LoginController.class.php文件。
WTF?是一个空壳,我们这个时候注意extends关键字,再次跟进extends后的./shangfan/controller/base/web/admin/LoginController.php文件。
我们从37行可以看到,调用出SystemAdminUser下的login方法将request扔进去了。
我们先跟进framework/Request.php文件,看一下是怎么玩的。
果然是用来处理外部请求的。
那么我们跟进SystemAdminUser模型(shangfan\model\base\SystemAdminUser.php文件),看一下处理逻辑。
可以看到28与29行调用了request类下的post方法,我们看一下post是否有做过滤。
$validator默认为true,这里我们进入到了一个过滤方法fliterParam,跟进看一下是如何过滤的。
可以看到最终数组或字符串都会被进行stringRemoveXss静态方法处理,我们跟进。
只是对XSS进行过滤处理,SQL注入却没有考虑!此时我们回到SystemAdminUser模型中,看一下其他处理逻辑,是否有过滤的情况。
可以看到直接将未过滤的POST请求拼接成SQL语句,最后执行first方法,这种SQL语句结构预处理也救不了它!
我们不妨看一下first方法是怎么玩的。通读嘛,就得通读个遍。
在该类中并没有找到first方法,那么必定是父类继承下来的,BaseModel没有被use进来,那么就在该命名空间下,我们跟进./shangfan/model/base/BaseModel.php文件查找first方法。
BaseModel下也没有任何方法,Model类被use,我们跟进./framework/Model.php文件查找first方法。
221行调用了db方法,没关系,我们跟进他,db方法在169行有return Database::getInstance方法,我们跟进Database类。
跟进./framework/database/Database.php文件看一下getInstance方法。
单例模式,返回Database类自己本身。
我们的初衷是找first方法,看一下first方法的逻辑。
可以看到81行有mysqli_query方法。那么SQL注入无疑~
可以看到成功盲注,这里笔者构造一下万能密码。
Payload:?user=’ UNION SELECT 1,2,3,’d6a6bc0db10694a2d90e3a69648f3a03′,5,6,7,8 #&passwd=hacker
这种程序只要外部参数提交进SQL中,一般都存在SQL注入。
0x04 存储型XSS漏洞
刚刚不是程序存在过滤吗?为什么会导致XSS漏洞?我们再认真看一遍XSS处理逻辑。
./shangfan.com/framework/Request.php文件下的stringRemoveXss方法。
看一下360行与367行,只有在正则匹配到“<内容>”才会进入到第367行,那么如果我们的XSS点本身就在标签之内,就可以执行事件形XSS。
笔者找到一处a标签内的可控点,单击保存。
在首页单击。
0x05 读后感
为什么有时间写框架,却没时间写安全的代码。
来源:freebuf.com 2020-10-29 18:13:31 by: Heihu577
请登录后发表评论
注册