Thinkphp5.0.x代码执行分析

文章首发星盟安全团队公众号

复现

环境:

1.php版本:7.3

2.thinkphp5.0.23

3.macos

poc:

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

分析

该漏洞的起因是routcheck函数中的Route::parseUrl函数。

我们跟进这个函数


这里会将‘/’符号替换成’|’,接着会把路由封装起来。


可以看到tp5在解析URL的时候只是将URL按分割符分割,并没有进行安全检测。继续往后跟:

跟完Route后,回到App.php,在139行有个self::exec($dispatch, $config)函数,这里dispatch参数就之前进行路由封装后的值。

跟进看看:

这里会因为type是model执行module函数,跟进self::module函数:

这里$result是第一个参数,也是之前路由封装后的值

在571行,有个Loader::controller函数

这里的$controller是由$result得来的

跟进controller函数:

可以看到如果这里的class是存在的就直接返回

但是前面有个getModuleAndClass函数,参数$name=\think\app,

如果name中有’\‘符号就直接返回是类名。即此时类名为\think\app。而如果其为正常的类似index的正规控制器名的话,会调用parseClass拼接出类名来,类似app\index\controller\Index。

也就是通过url调用其任意的public方法,同时解析url中的额外参数,当作方法的参数传入。

回到moudle函数中,将会通过反射获取操作方法名,即invokefunction

接着就会调用其具体的操作方法了

1
return self::invokeMethod($call, $vars); //$call = {think\App , "invokefunction"}

我们跟进invokeMethod:

这里有个self::bindParams方法,是用来获取url后面参数变量的。

1
$reflect->invokeArgs(isset($class) ? $class : null, $args);

最后是通过反射执行$class方法d的,也就是我们控制的invokeFunction方法。跟进incokeFunction方法:

该方法也是获取url后面的变量

最后$reflect->invokeArgs($args);也是用反射机制让args中的call_user_func_array函数执行.

对ReflectionMethod::invokeArgs不知道的可以看向这里:https://www.php.net/manual/zh/reflectionmethod.invokeargs.php

所以最后这里就是执行了call_user_func_array({‘system’,{‘whoami’}})

以下是整个漏洞的流程图

最后

不对的地方,还望各位师傅斧正,一起交流一起成长。

参考

https://paper.seebug.org/770/

https://blog.riskivy.com/thinkphp5-x%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/