个人博客地址
http://www.darkerbox.com
欢迎大家学习交流
参考网址:
https://paper.seebug.org/760/
漏洞代码
https://github.com/vulnspy/thinkphp-5.1.29
程序未对控制器进行过滤,导致攻击者可以通过引入命名空间\
符号来调用任意类的任意方法。
漏洞影响版本:
ThinkPHP 5.0.5-5.0.22
ThinkPHP 5.1.0-5.1.30
最好先看上面的参考网址再回来看下面的漏洞利用。因为下面直接分析的exp的数据流走向
搭建好后,先用exp打一下,看看是否成功。
http://127.0.0.1/tp/public/index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
先主要分析一下我们传入的参数是怎么到程序里的。
public
目录下的Index.php
是程序的入口文件,文件里调用了App.php
中的run
方法
这里的App.php是指thinkphp\libray\think
目录下的App.php。
在这个文件下的第403行。路由检测,
因为我加了几行注释。实际行数应该在403行附近。
跟进$dispatch = $this->routeCheck()->init();
中的routeCheck函数,在App.php的600行获取应用调度信息。
继续跟进$path = $this->request->path();
的path方法,跳转到Request.php的716行的path方法。
该方法中调用了pathinfo
方法
继续跟进pathinfo
方法。同样在request.php的680行,找到了pathinfo方法,。
$this->config[‘var_pathinfo’]的值就是config目录下的app.php文件中var_pathinfo的值,
那么拼接起来就是$_GET[‘s’]。通过$pathinfo = $_GET[$this->config['var_pathinfo']];
将exp中的s参数的值传给了$pathinfo变量。那么此时$pathinfo的值是/index/\think\app/invokefunction
。
pathinfo方法最后返回的时候,值还是/index/\think\app/invokefunction
。
这就成功获取s参数的值。之后function参数的值之后会说。
获取参数之后,也就是$path = $this->request->path();
执行完了
注意上图603行的是否强制路由模式。config目录下的App.php。默认为false
。
之后在606行进入check方法,传入了两个参数。KaTeX parse error: Undefined control sequence: \think at position 22: …我们s参数的值`/index/\̲t̲h̲i̲n̲k̲\app/invokefunc…must上面说过了是false。
跟进check方法。
在check方法中,如果$must为true,则会抛出异常。在上面说过了$must是false。默认是fasle。
函数最后实例化UrlDispatch对象。UrlDispatch继承了Dispathch。
跟进之后就到了Dispathch的构造函数。$dispatch
是可控的。
构造函数执行完,接着返回到App.php的403换行,此时routecheck函数的返回值就是一个urldispatch对象。接着又调用了init方法。
跟进init方法。此时$this->dispatch的值为index|\think\app|invokefunction
(之前做过字符串替换)。也就是模块|控制器|操作
。那么此时模块:index,控制器:think\app,操作:invokefunction。
跟进parseUrl。
在parseurl中调用了parseUrlPath方法,此时$url的值index|\think\app|invokefunction
。
这个方法主要是分开index|\think\app|invokefunction
为一个数组。放到$path变量中,此时$path的值如下图。很明显了。0就是模块,1是控制器,2是操作。
之后返回$path和$var。$var为空。
返回之后,又回到了parseurl函数,然后再执行到返回。回到了Init函数。
此时执行到了return (new Module($this->request, $this->rule, $result))->init();
。开始new一个Model对象调用Init方法。
在Init方法中的70行,获取控制器名字。
这是一个关键点,这里没有做过滤。漏洞就出现在这个地方。没有对控制器做过滤导致可以执行任意类的任意方法。
此时,返回到了App.php。终于把$dispatch = $this->routeCheck()->init();
执行完毕了。
跟进。
一直跟进,。会在dispatch.php中的168行调用exec方法。
进入该方法。该方法里实例化控制器
进入contrloller方法,$name就是控制器名think\app
跟进parseModuleAndClass方法。又是一个重点方法。
如下图。该方法判断KaTeX parse error: Can't use function '\`' in math mode at position 9: name是否有`\̲`̲。如果有的话。直接将值赋给class,然后返回。众所周知,$name的值是\think\app
,有\
,所以直接返回。
返回之后回到controller
方法。
判断$class类是否存在,此时$class的值是\think\app
。这个类是存在的。就是App.php。
如果存在,则return $this->__get($class)
。这应该就是具体的实例化操作了。里面用了反射。虽然我不是很懂。
之后一直返回,回到了model.php。此时$instace已经实例化完毕了。
到了105行,105行之后就是在获取操作名invokefunction
。并且在112行new了一个反射方法对象。
此时$reflect的值如下图。可以发现原来我们的think\app
变成了think\Container
。
这是由于app继承了Contatiner。invokefunciton方法是属于Containter的。
118行之后,会获取function参数值和vars参数值。
在136行执行了方法。
此时参数值如下图。$instance一直是很多实例的数组。
进入这个invokeReflectMethod
方法。。绑定参数,
再进入invokArgs方法。
直接跳转到invokeFunction方法内部。这个invokdefunction方法对应着exphttp://127.0.0.1/tp/public/index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
中的invokdefunction。exp中的function参数值就是方法中的第一个参数值。vars参数是放到第二个参数值。
然后调用了call_user_func_array。此时参数值如下图
实际就是执行了
call_user_func_array('call_user_func_array',['phpinfo',['1']]);
执行后就是一直的返回,然后输出结果到页面。
如有不正确的地方请指出