漏洞分析环境:https://github.com/vulnspy/thinkphp-5.1.29与phpstudy php-7.0.12-nts+apache
Thinkphp框架中url的访问为index.php/模块/控制器/操作,index.php称为入口文件(pathinfo方式的URL访问),也可使用兼容模式,通过变量s传参/模块/控制器/操作。
根据参考链接中的关键代码,追溯漏洞源文件/thinkphp/library/think/route/dispatch/Module.php,如下图所示:
继续跟踪result数组,如下图所示:
发现调用父类init函数,module类继承于抽象类Dispatch,追踪Dispatch如下图所示:
观察判断语句,通过伪变量this对象引用到rule.php文件中,执行doAfter函数,获取路由的后置操作。如下图所示:
这里稍微引入一下路由后置操作简介,至于大佬可以加以补充,小弟就不献丑了
Thinkphp路由为三种模式:
1、普通模式(采用默认的pathinfo方式url):'url_route_on' => false
2、混合模式(该方式下面,只需要对需要定义路由规则的访问地址定义路由规则,其它的仍然按照默认的PATH_INFO模式访问URL):'url_route_on' => true
3、强制模式(必须定义路由才可访问):'url_route_must' => true 'url_route_on' => true
Thinkphp 5.0以上版本对新的路由功能做了新的增强,支持路由到模块(模块/控制器/操作)、控制器(控制器类/操作)、类(任何类库),也是此次漏洞的原因之一
根据以上描述,分析此次漏洞环境代码,采用为混合模式,在采用s变量获取,触发漏洞。分析获取pathifo的过程,如下图所示:
第一步先判断pathinfo是否有兼容模式的参数,第二步分析pathinfo信息,将ORIG_PATH_INFO,REDIRECT_PATH_INFO,REDIRECT_URL三种模式循环赋值给变量$type,使用server函数,将变量$type转换为大写存入 server数组中,当变量server数组已被赋值,则返回server数组,否则返回空,如下图所示:
最终当变量pathinfo不为空时,$pathinfo为删除/符号前后空白字符的字符串,追溯pathinfo函数,找到path函数,如下图所示:
跟踪path函数,找到App.php文件中的routeCheck函数,如下图所示:
可以观察到此函数返回一个Dispatch对象,而module文件中$result = $this->dispatch,继续跟踪Check函数,如下图所示:
返回值为创建的新对象UrlDispatch,跟踪UrlDispatch类,发现为Url的别名引用,如下图所示:
跟踪Url类,发现继承于抽象类Dispatch,再由魔术方法__construct在方法被实现时调用,观察此方法,如下图所示:
此方法用伪变量this将dispatch对象赋值为$dispatch,返回观察Url类,如下图所示:
init方法返回到新对象Module的init方法中,也就是Module.php中parent::init(),其中dispatch通过parseUrl函数赋值给$result,观察parseUrl,如下图所示:
变量module由getConfig函数获取app_multi_module的值,观察得知app_multi_module为真,得到删除第一个数组的变量$path,最后返回一个封装路由route,包含变量module、controller和action,并传递给变量result。
通过以上分析得到变量result的生成结果,当使用explode分割字符串时,输入/index/\think\request/cache,得到如图所示:
通过上述删除第一个数组,赋值给module.php中的变量module,如下图所示:
此时变量module为index,继续观察找到控制器变量controller,如下图所示:
此时变量controller为\think\request,继续观察找到操作名变量actionName,如下图所示:
此时变量actionName为cache,最后进入请求操作,跟踪到操作器controller文件中,找到cache方法,如下图所示:
通过特殊构造达到执行phpinfo的效果,将变量key设置为1|phpinfo,跳过判断是否存在于匿名类中,并通过true===1,进入list函数将变量key和fun分别赋值为1和phpinfo(根据php-7.0.12-nts环境,list赋值为从右向左),从而达到运行phpinfo函数。
第二个poc为:
/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=php%20-r%20'phpinfo();'
也等同于:
/index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=php%20-r%20'phpinfo();’
跟踪到invokefunction函数,如下图所示:
通过将function设置为call_user_func_array,vars[0]=system,即可生成system()函数,通过vars[1][]对上述call_user_func_array返回的回调函数system设置参数变量,达到运行系统命令。
参考链接:
https://www.jianshu.com/p/73ed6e42d389