我们先看处理请求的Request类,在thinkphp/ library/think/Request.php。
Method方法来获取当前的请求类型。
isGet、isPOST、isPUT等方法都调用了method方法来做请求类型的判断。
而在默认情况下,是有表单伪装变量的
在默认情况下,该变量值为_method
在Method方法中,将表单伪装变量对该方法的变量进行覆盖,可以实现对该类的所有函数进行调用。
在Request类中,我们看一下__construct析构方法。
如果该类属性不存在,就会调用默认配置文件中的default_filter的值。
如果该类属性存在,就会对$option数组进行遍历,将该类属性赋值为$option数组中键对应的值 。
因此,我们可以构造一个Payload:s=ipconfig&_mehthod=__construct$method=&filter[]=system
这个Payload只适用于thinkphp5.0.10版本,为什么?我们来看看thinkphp5.0.23版本的改进。
ThinkPHP5.0.23版本代码执行漏洞
在5.0.23版本中,App类 在thinkphp/ library/think/App.php中,module方法新增设置了
filter属性值,初始化了filter属性值,也导致在5.0.10版本中被重新覆盖为配置文件中的默认值导致无法进行利用。
在返回到Request类中,Param方法也调用了method方法,它用于获取当前请求的参数,传入了默认值true。
再看看method方法
当传过来的值为true时,会执行下面的代码。
再去看看server方法
那么,在此时server方法中的name值为REQUEST_METHOD,然后会调用input方法。
再进行跟踪input方法
$filter = $this->getFilter($filter, $default);
看注释是解析过滤器
再跟踪getFilter方法
此时的$filter= ''和$default=null
因此,执行了$filter = $filter ?: $this->filter;这段代码。
$this->filter被赋值给了$filter,也就是请求中的filter参数。
再回到input方法中,在解析过滤器之后,判断了$data是否为数组,如果不是,则将每个值作为一个参数并且用filterValue方法进行过滤
在server方法中,$this->server接受的是$_SERVER超全局变量。那么在调用析构方法的时候,我们也可以对$this->server的值进行覆盖。
在filterValue方法中,调用call_user_func方法导致了代码执行。
再回到Param方法,哪个地方会调用Param方法呢?
用phpstorm搜索一下
在App类中,找到一处调用了$request->param();
如果开启了debug模式,就可以实现代码执行了。
在调用param方法之前,进行了未设置调度信息则进行 URL 路由检测的功能。用routeCheck方法来设置$dispatch。然后用exec方法
$config变量是通过initCommon方法中的init方法初始化的。
再来看看routeCheck方法
加载config文件导入路由配置
然后通过Route::import加载路由。
在入口文件 public/index.php中加载了/thinkphp/start.php文件
在start.php文件中加载了基础文件base.php
在base.php中有注册自动加载功能
跟踪register方法
对vendor下的php文件注册系统自动加载
在vendor/topthink/think-captcha/src/helper.php中加载了验证码路由类。
上面执行了Route::import方法之后,此时,我们打印一下Route::rules的值看看。
然后我们再返回到Route::check路由检测(根据路由定义返回不同的URL调度)跟踪check方法
Check方法调用了Request类中的method方法,把method方法执行的结果小写赋值给$method变量。
在Request类中method方法判断了如果$this->method存在的话,那么就直接返回。
那么我们在调用析构函数覆盖变量的时候,可以直接将method覆盖掉。$method是可控的了
将$method变量的值传入$rules的方法中获取对应的路由信息,赋值给$rules这个变量。
返回路由规则检测,$method给的值不同返回的结果也会不同。$method=get的时候
$rules的值是不一样的。
$url = str_replace($depr, '|', $url);
$item = str_replace('|', '/', $url);
$url在check方法中为参数,在App类中routeCheck方法调用
$path是经过$request->path();调用Request类中的path方法得到的。
如果is_null($this->path)为真的时候,会调用pathinfo函数来获取。
$_SERVER['PATH_INFO']的值为Config配置文件中
默认的值's'
url中的s参数可以设置App类中的routeCheck方法中的$path参数。
Router类中的check方法控制了check函数中的$item变量也就控制了check方法最终返回的值,同时也控制了App类中的调度信息$dispath
$dispath在App类中的run方法中被exec方法调用。
跟踪exec方法
这里使用了switch语句判断$dispath['type']来执行相应的代码。
之前,我们需要调用Request类中的param方法来对filter变量的覆盖。
如果$dispath['type']是controller或者是method的时候可以直接调用param方法。
当我们让$dispath['type']=function的时候,调用了invokeFunction方法, invokeFunction方法调用了bindParams方法也对param方法进行了调用。
我们控制了url中的s参数的值可以设置不同的$method,让routeCheck返回$dispath。
我们将控制的url参数s的设置为captcha,并且设置post数据_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ipconfig
此时,调用了check方法后会进入checkRoute方法。
再调用了CheckRule方法
又调用了parseRule方法
最后经过路由处理后,返回$result
在调用exec方法以后,触发调用了Request类中的param方法,将Request类中的server变量覆盖,再经过Request类中的input方法的调用导致了代码执行漏洞。