漏洞概要
-
漏洞名称:ThinkPHP 5.0.x-5.1.x 远程代码执行漏洞
-
参考编号:无
-
威胁等级:严重
-
影响范围:ThinkPHP v5.0.x < 5.0.23,ThinkPHP v5.1.x < 5.0.31
-
漏洞类型:远程代码执行
-
利用难度:容易
-
payload:?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
漏洞概述
2018年12月10日,ThinkPHPv5系列发布安全更新,修复了一处可导致远程代码执行的严重漏洞。此次漏洞由ThinkPHP v5框架代码问题引起,其覆盖面广,且可直接远程执行任何代码和命令。电子商务行业、金融服务行业、互联网游戏行业等网站使用该ThinkPHP框架比较多,需要格外关注。由于ThinkPHP v5框架对控制器名没有进行足够的安全检测,导致在没有开启强制路由的情况下,黑客构造特定的请求,可直接进行远程的代码执行,进而获得服务器权限。
漏洞分析
前期准备
-
为什么从?s=开始
在application/config.php第78行左右有这个定义,var_pathinfo键的默认值就是s
搜索一下,发现在thinkphp/library/think/Request.php中有这个键
用Config::get获取了var_pathinfo的值,也就是s的值,然后变成了$_GET['s']然后把值赋给$_SERVER['PATH_INFO'],判断url里是否有兼容模式参数,然后下面$this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/');
去除了最左边的正斜杠。得出结论,s就是用来获取payload中的index/\think\app/invokefunction这部分的,而它的值在源码里面就作为pathinfo。知道这个后,继续跟着程序执行流程
执行到thinkphp\library\think\Route.php#check()
其中,$url实际上传入的就是刚才的pathinfo的值。然后$url = str_replace($depr, '|', $url);
把所有/换成了|。那么现在就是index|\think\app|invokefunction
这个样子。但是因为没有定义好的路由规则,最后还是执行了
之后在thinkphp\library\think\App.php中
可以看到,如果没有定义好路由规则,也就是刚才的return了false,并且还有强制路由的话就会抛出错误。但是这个漏洞利用条件之一就是不开启强制路由~。
那么再往下走,就会到parseUrl
然后parseUrl用parseUrlPath()方法,用$path = explode('/', $url);
来把这个url给处理为了一个数组,返回了一个path数组
这样实际上就是分成了模型、控制器、方法了。最终逐个获取的位置是parseUrl方法的:
漏洞原因
当程序执行到thinkphp\library\think\App.php#exec(),会进入到module分支,来到module方法,在:
这里的result数组实际上就是刚才parseUrl返回的那个path数组,
1=>控制器,2=>操作名(也叫做方法名)。在这里获取了之后,程序没有再对控制器、方法名过滤导致了任意控制器下任意方法的调用。然后会使用加载器
加载think\app,在thinkphp\library\think\Loader.php#controller()
然后在invokeClass中使用反射API来动态实例化一个类,并根据提供的参数调用其构造函数。这在需要动态创建对象而不直接使用new
关键字时非常有用。
这个函数的作用是把think\app实例化成一个类(描述不准确),然后就可以调用这个类的函数了
返回到了$instance变量中,这时控制器这块已经基本上处理完了,think\App实际上就是thinkphp\library\think\App.php这个php文件里的App类(在MVC架构中,某些类也是可以叫做控制器),think是一个命名空间。意思就是think命名空间下的都是thinkphp的核心代码。
new \ReflectionClass($class)
:这行代码创建了一个ReflectionClass
对象,它用于获取关于给定类的信息,比如它的构造函数、方法、属性等。
$reflect->getConstructor()
:这行代码获取了类的构造函数。如果类没有定义构造函数,则返回null
。
$constructor ? self::bindParams($constructor, $vars) : []
:这是一个三元运算符。如果$constructor
不为null
(即类有构造函数),则调用self::bindParams
方法,该方法用于将$vars
数组中的值绑定到构造函数的参数上。如果$constructor
为null
,则$args
数组为空。
$reflect->newInstanceArgs($args)
:这行代码使用ReflectionClass
对象的newInstanceArgs
方法来实例化类。这个方法接受一个参数数组,这些参数将传递给类的构造函数。如果之前没有构造函数或者没有提供参数,则不会调用构造函数。
接下来就是获取方法,调用反射执行类的方法。App类里的invokefunction方法使用反射API来动态调用一个函数,并根据提供的参数传递给函数。这在需要动态调用函数而不直接使用函数调用语法时非常有用
它先获取了$function的信息,然后把$vars数组中的参数与函数绑定,然后使用invokeArgs方法调用了这个函数以及绑定的参数
`self::bindParams($reflect, $vars)
:这行代码调用了一个名为bindParams
的静态方法,该方法用于将$vars
数组中的值绑定到函数的参数上。$reflect
对象提供了函数参数的信息。
self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info')
:这是一个条件语句,用于记录函数的执行信息。如果self::$debug
为true
,则调用Log::record
方法记录一条信息日志,其中包含了函数的名称和执行信息。$reflect->__toString()
返回函数的名称。
$reflect->invokeArgs($args)
:这行代码使用ReflectionFunction
对象的invokeArgs
方法来调用函数。这个方法接受一个参数数组,这些参数将传递给函数。
总结
所以总体流程如下:
-
传入的
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami
,其中s(var_pathinfo)会取值index/\think\app/invokefunction -
s会先处理成为
index|\think\app|invokefunction
,之后进一步被处理为path数组 -
然后进入module函数中,$controller得到值为/think/app,作为参数执行controlle函数
-
/think/app作为参数在invokeClass函数中被实例化,之后就可以调用,\think\app的函数
-
执行了App里的invokeFunction,并给其传入了参数1:
function=call_user_func_array
,参数2:vars[]=system&vars[1][]=whoami
,相当于执行了call_user_func_array('system', ['whoami'])
,也就是system('whoami')。执行了命令。
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)