尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

自己搭建ThinkPHP5.1.29平台复现控制器名校验缺陷导致远程代码执行

自己搭建ThinkPHP5.1.29平台复现控制器名校验缺陷导致远程代码执行 免责声明 本文全部实验操作均在本人完全可控的自建本地靶场环境完成文章仅用于网络安全原理学习与技术研究。 根据《中华人民共和国网络安全法》未经授权对任何第三方系统进行探测、命令执行、文件写入等操作属于违法行为。严禁复制、使用本文中的 Payload、代码应用到未获得授权的设备上。若读者将文中内容用于非法用途一切法律责任由行为人本人承担与本文作者无关。个人水平有限如有不足欢迎指正。————————————————漏洞概要该漏洞根源是 ThinkPHP 框架底层对控制器名称缺少严格合法性校验。在网站未开启强制路由时攻击者可利用漏洞调用任意类中的任意方法从而实现远程代码执行。 受影响版本 ThinkPHP5 5.0.7 ~ 5.0.22、ThinkPHP5 5.1.0 ~ 5.1.30 针对不同版本环境所使用的攻击载荷Payload需要相应调整。环境准备top-think/framework: ThinkPHP Framework下载think-5.1.29.zip下载framework-5.1.29.zip将think-5.1.29.zip解压到小皮面板根目录在其中创建thinkphp目录将framework解压到thinkphp目录添加虚拟主机默认情况下安装的thinkphp是没有开启强制路由访问选项而是默认开启路由兼容模式什么是路由在 Web 框架中用户访问/index.php/user/login框架需要知道/user/login应该交给哪个控制器、哪个方法处理。这个过程就叫路由简单来说URL-路由解析-找到对应的控制器-找到对应的方法-执行程序例如/user/login可能对应UserController-login()什么是thinkphp路由兼容模式可以理解为不使用完整的路由定义而是允许框架根据 URL 中的模块/控制器/操作结构自动找到对应的方法例如普通的 ThinkPHP URLhttp://example.com/index.php/index/user/login可以拆成/index → 模块Module /user → 控制器Controller /login → 操作/方法Action也就是入口文件 / 模块 / 控制器 / 操作。框架根据这个结构去执行Index\Controller\UserController::login()什么叫“兼容模式”ThinkPHP 不同版本的路由机制有所区别。所谓兼容模式主要就是为了兼容传统的 URL 访问方式。让新的路由机制能够兼容旧版本 ThinkPHP 的访问方式。例如原本使用 ThinkPHP 早期的/index.php?mIndexcUseralogin也可以通过类似/index.php/Index/User/login这样的路径形式访问。其中m 是 Module模块c 是Controller控制器a 是Action操作所以可以把它理解成传统参数形式 ↓ mIndex cUser alogin ↓ 兼容模式解析 ↓ Index / User / login兼容模式关注的是URL 怎么被解析、怎么兼容传统访问方式。例如/index.php/Index/User/login框架能够按照Index → User → login进行解析。var_pathinfo是 ThinkPHP 的一个配置项。它用来告诉框架PATH_INFO 放在哪个请求参数里这里的PATH_INFO可以先简单理解成URL 中用来描述模块、控制器、操作的那一段路径信息。例如/index.php/Index/User/login。其中/Index/User/login就是 PATH_INFO。var_pathinfo s,意思是把 s 指定为存放 PATH_INFO 的参数名。所以如果请求/index.php?s/Index/User/login。那么可以理解成s /Index/User/login ↓ PATH_INFO ↓ Index / User / login ↓ 模块 / 控制器 / 操作s的作用因为 PHP 接收到 URL 后本质上会得到请求参数。例如/index.php?s/Index/User/login。PHP 可以看到$_GET[s]它的值就是/Index/User/loginThinkPHP 通过var_pathinfo s知道如果请求参数里有s那么s里面的值就是我要解析的 PATH_INFO。于是$_GET[s] ↓ /Index/User/login ↓ ThinkPHP 路由解析 ↓ Index / User / login什么是thinkphp强制路由访问网站时URL 必须匹配开发者定义的路由规则不能再按照 ThinkPHP 默认的“模块/控制器/操作”方式直接访问。默认情况假设有一个控制器class User { public function login() { // ... } }按照 ThinkPHP 默认的 URL 解析方式可能可以直接通过/index.php/Index/User/login访问。也就是index → 模块 user → 控制器 login → 操作框架自动按照这个结构寻找对应代码。2. 开启强制路由之后开发者定义Route::get(login, index/user/login);此时规定/login才是访问index/user/login的入口。传递的参数变成了login如果访问/index.php/index/user/login即使这个控制器和方法真实存在也不一定能够直接访问。因为现在框架要求请求 URL ↓ 是否匹配已经定义的路由 ↓ 是 → 执行对应控制器/方法 否 → 拒绝或无法正常路由强制路由关注的是访问 URL 时是否必须经过已经定义的路由规则。兼容模式 解决“怎么解析 URL”强制路由 规定“URL 必须怎么访问”兼容模式漏洞出现的原因当 ThinkPHP 开启路由兼容模式非强制路由模式时框架路由调度代码对 URL 传入的控制器名、方法名缺少严格的白名单过滤与合法性校验。 在用户可控请求参数的场景下攻击者能够自由传入任意控制器名称与方法名称。 如果框架内置或者项目代码中存在可触发远程代码执行RCE的类 / 方法攻击者就可以通过路由调度机制直接访问到这些危险类中的危险方法最终实现任意 PHP 代码执行。如?sindex/\think\Request/inputfilter[]systemdatawhoami第一个\表示从全局命名空间开始index 为模块\think\Request 是完整类名input 为要调用的 public 方法断点执行1.控制器的名获取方式?sindex/\think\Request/inputfilter[]systemdatawhoami在$controller代码段打下断点。可以看到控制器的名字是从$result 中获取的而 $result 的值来源于兼容模式下的 pathinfo即s参数。strip_tags:从字符串中去除 HTML 和 PHP 标签$result[1] ?: $this-rule-getConfig(default_controller)是 PHP三元运算符简写形式等价于$result[1] ? $result[1] : $this-rule-getConfig(default_controller)逻辑规则如果 $result[1] 存在且不为空、不为 false → 返回 $result[1]如果 $result[1] 为空 / 不存在 → 获取配置项 default_controller默认控制器名称这里 $controller 承载的值就是 \think\Request框架拿到这个类名去实例化随后调用 input() 方法最终实现 RCE。2.操作名操作名为input接着程序会跳回 App 类的 run 方法进而调用 Dispatch 类的 run 方法该方法会调用关键函数 exec执行调度。this-controller也就是request类this-actionName也就是input方法到这里通过 PHP 反射机制调用request类中的input方法传参filter和data$data $this-app-invokeReflectMethod($instance, $reflect, $vars);$reflect:ReflectionMethod 对象.方法名input。所属类\think\Request$vars也就是 URL 传入的 GET 参数?filter[]systemdatapwd最终在这里调用 \think\Request 类里面的 input () 方法到第1358行因为data不是数组进入filterValue函数array_pop($filters);弹出数组最后一个元素返回它同时原数组被截断删掉最后一项。foreach 是 PHP 用来遍历数组的循环语句。$filters待遍历的数组is_callable(参数)作用判断传入的值是否是一个可以被调用的函数 / 方法名称返回 true / false。于是在这里调用回调函数call_user_func(回调函数, 参数);功能通过字符串形式的函数名动态调用函数。程序执行完 call_user_func(system,whoami)成功调用系统函数执行命令。system 函数获取 whoami 的执行结果并覆盖变量 $value。随后执行 return $value;将携带命令执行结果的变量向上返回给上层调用方法最终在页面中输出命令执行回显。漏洞原理总结1.路由解析机制缺陷 框架对 URL 中s参数可控攻击者可以自由指定控制器类名与调用方法。正常场景只会调用开发者自定义控制器而攻击者直接指定框架内置类 \think\Request并调用类中的 input() 方法。2.input 方法内置过滤函数存在安全隐患 input() 支持传入数组形式的过滤参数 filter参数会传递到私有方法 filterValue() 进行处理3。代码使用 array_pop() 将数组最后一项取出作为默认值通过 foreach 循环遍历剩余过滤器使用 is_callable() 判断过滤器名称是否为可调用函数若满足条件通过 call_user_func($filter,$value) 动态调用指定函数。4.缺少危险函数限制 框架仅判断函数是否可调用没有对 system、exec 等命令执行函数设置黑名单。攻击者传入 filter[]systemfilter[]占位字符datawhoami即可动态调用 system() 执行系统命令。在更新中对做出了修改
返回列表