thinkphp部分版本存在重大url漏洞
Thinkphp 5.1.0 - 5.1.31
Thinkphp 5.0.5 - 5.0.23
1、现象:
下载thinkphp_5.0.21
直接访问:http://root.com/tp/public/index.php?s=index/\think\app/invokefunction&function=phpinfo&vars[0]=100
是的,直接输出了phpinfo信息。
2、原因分析:
源于thinkphp对于url的处理上
默认配置为:
// PATHINFO变量名 用于兼容模式
'var_pathinfo' => 's',
// 是否强制使用路由
'url_route_must' => false,
由于默认没开启强制使用路由,所有就直接解析url了。
根据分隔符 $config[‘pathinfo_depr’] , 以上地址被解析为如下数组
array(2) {
["type"] => string(6) "module"
["module"] => array(3) {
[0] => string(5) "index"
[1] => string(10) "\think\app"
[2] => string(14) "invokefunction"
}
}
追踪到 thinkphp\library\think\App.php:553开始
实例化控制器:
$instance = Loader::controller(
$controller,
$config['url_controller_layer'],
$config['controller_suffix'],
$config['empty_controller']
);
进入Loader的controller方法
public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
{
list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);
if (class_exists($class)) {
return App::invokeClass($class);
}
if ($empty) {
$emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix);
if (class_exists($emptyClass)) {
return new $emptyClass(Request::instance());
}
}
throw new ClassNotFoundException('class not exists:' . $class, $class);
}
进入getModuleAndClass方法
protected static function getModuleAndClass($name, $layer, $appendSuffix)
{
if (false !== strpos($name, '\\')) {
$module = Request::instance()->module();
$class = $name;
} else {
if (strpos($name, '/')) {
list($module, $name) = explode('/', $name, 2);
} else {
$module = Request::instance()->module();
}
$class = self::parseClass($module, $layer, $name, $appendSuffix);
}
return [$module, $class];
}
当$name为 \think\app 时 strpos 函数是有值的,于是 $class = $name;
好了,打印 上面的 $instance ,结果为
object(think\App)#5 (0) {
}
追踪到 thinkphp\library\think\App.php:585
if (is_callable([$instance, $action])) {
// 执行操作方法
$call = [$instance, $action];
// 严格获取当前操作方法名
$reflect = new \ReflectionMethod($instance, $action);
$methodName = $reflect->getName();
$suffix = $config['action_suffix'];
$actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName;
$request->action($actionName);
} elseif (is_callable([$instance, '_empty'])) {
// 空操作
$call = [$instance, '_empty'];
$vars = [$actionName];
} else {
// 操作不存在
throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
}
Hook::listen('action_begin', $call);
return self::invokeMethod($call, $vars);
于是,最终由 invokeMethod 通过反射类方法 执行了 think\app 下的 invokeFunction($function, $vars = []) 方法
通过反射来设定了参数必须是 function(必填) 和 vars(可选),类似于函数传参一样。
做种由 $reflect->invokeArgs($args) 来传参执行。
3、修复
问题的根源就是 getModuleAndClass 对控制器的过滤。
1、设置请求路由
2、强制使用路由 url_route_must = true
修改以上两步,再次请求 http://root.com/tp/public/index.php?s=index/\think\app/invokefunction&function=phpinfo&vars[0]=100
报错:当前访问路由未定义