1. redhat初赛
2. redhat复赛
2.1 粤湾投资(cms)
爱客猴框架:https://www.aikehou.com/
漏洞1 任意文件上传
漏洞路径:\www\Apps\Home\Controller\ UploadfileController.class.php
<?php
/*
* Author: [ Copy Lian ]
* Date: [ 2015.05.08 ]
* Description [ 上传文件控制器 ]
*/
namespace Home\Controller;
class UploadfileController extends CommonController
{
/**
* [index 文件上传代码]
* @return [type] [description]
*/
public function index(){
if(IS_POST){
/**
* 1、如果是图片直接调用uploadPhoto()
* 2、如果是替他附件则按照当前配置设置
*/
$type = I('post.type','files');
//在thinkphp中为了安全的原因建议统一使用 I 函数来获取变量值,也就是说这些post的值我们都是可以控制的。
$saveDir = I('post.savedir');
$thumbw = I('post.thumbw');
$thumbh = I('post.thumbh');
$filesize = I('post.filesize');
if($type == 'images' || preg_match('/^image\//', $_FILES['Filedata']['type'][0])){
//这里如果匹配到image/结构,进入判断,^表开头。
$type = "images";
//是否添加水印
$water = I('post.water');
if($water){
$water_site = C('SITE_SYSTEM_IMG_WATER');
// 配置操作函数C(大写字母C)。ThinkPHP按照默认的顺序加载完配置之后,配置全局有效,在框架作用范围内(一般指应用目录下),所有配置都可以直接使用C函数读取(包括ThinkPHP默认配置)。
$water_new = isset($water_site) && !empty($water_site) ? $water_site : C('SYSTEM_IMG_WATER');
//$info = uploadPhoto('photo/'.$type.'',$thumbw,$thumbh,array('open'=>1));
$info = uploadPhoto($saveDir.'/'.$type,$thumbw,$thumbh,$water_new,$filesize);
} else {
$info = uploadPhoto($saveDir.'/'.$type,$thumbw,$thumbh,'',$filesize);
}
if(!is_array($info)){
$error_data['info'] = $info;
$error_data['status'] = 0;
$this->ajaxReturn($error_data);
}
$file['oringinal_type'] = 'images';
$file['status'] = 1;
$file['name'] = $_FILES['Filedata']['name'][0];
$file['savename'] = $info[0]['savename'];
$file['photo'] = substr($info[0]['savepath'],1).$info[0]['savename'];
$file['thumb'] = $info[0]['thumbpath'];
$file['location'] = 'upload';
$this->ajaxReturn($file);
} else {
$type = 'files';
$upload = new \Think\Upload();
$upload->maxSize = $filesize*1024; //文件上传的最大文件大小,附件最大100M
$upload->rootPath = "./"; //文件上传保存的根路径
$upload->savePath = $upload->rootPath."Public/Uploads/".$saveDir."/" . $type ."/";//文件上传的保存路径
$upload->saveName = date("YmdHis")."_".uniqid(); //上传文件的保存规则
$upload->replace = true; //存在同名文件是否是覆盖
$upload->autoSub = true;//自动使用子目录保存上传文件
$upload->subName = date("Ymd");//子目录创建方式,采用数组或者字符串方式定义
$upload->hash = true;//是否生成文件的hash编码 默认为true
if(!$info = $upload->upload()){
$error_data['status'] = 0;
$error_data['info'] = $upload->getError();
$this->ajaxReturn($error_data);
} else {
//处理文件图标
//excel
if(in_array($info[0]['ext'], array('xls','xlsx'))){
$info[0]['ext'] = 'xls';
}
//ppt
if(in_array($info[0]['ext'], array('ppt','pptx'))){
$info[0]['ext'] = 'ppt';
}
//word
if(in_array($info[0]['ext'], array('doc','docx'))){
$info[0]['ext'] = 'doc';
}
//音频
if(in_array($info[0]['ext'], array('wma','mp3','mid','wav'))){
$info[0]['ext'] = 'mp3';
}
//视频
if(in_array($info[0]['ext'], array('avi','mov','mpeg','mpg','swf','mp4'))){
$info[0]['ext'] = 'vedio';
}
//zip
if(in_array($info[0]['ext'], array('zip','rar','7z'))){
$info[0]['ext'] = 'zip';
}
//判断是否存在文件不存在则取默认图片
if(!file_exists("./Public/images/uploadfile/".$info[0]['ext'].".png")){
$info[0]['ext'] = "readme";
}
$info[0]['status'] = 1;
$info[0]['oringinal_type'] = 'files';
$info[0]['savepathall'] = substr($info[0]['savepath'],1).$info[0]['savename'];
$info[0]['location'] = 'upload';
$this->ajaxReturn($info[0]);
}
}
} else {
//缩略图配置
$site_thumbw = C('SITE_SYSTEM_THUMB_WIDTH');
$site_thumbh = C('SITE_SYSTEM_THUMB_HEIGHT');
$thumbw = isset($site_thumbw) && !empty($site_thumbw) ? C('SITE_SYSTEM_THUMB_WIDTH') : C('SYSTEM_THUMB_WIDTH');
$thumbh = isset($site_thumbh) && !empty($site_thumbh) ? C('SITE_SYSTEM_THUMB_HEIGHT') : C('SYSTEM_THUMB_HEIGHT');
$this->thumbw = I('get.thumbw',$thumbw);
$this->thumbh = I('get.thumbh',$thumbh);
$this->thumbw = $this->thumbw === 0 ? $thumbw : $this->thumbw;
$this->thumbh = $this->thumbh === 0 ? $thumbh : $this->thumbh;
$this->type = I('get.type',''); //类型
$this->myid = I('get.myid',''); //返回容器内容的值
$this->iframe = I('get.iframe',''); //返回的iframe容器
$this->field = I('get.field','');
$this->returntype = I('get.returntype','multiple'); //返回文件的个数类型,单个-single,多个-multiple,默认是多个
//控制上传文件大小
$this->filesize = I('get.filesize');
if(empty($this->filesize)){
if($this->type == 'images'){
$this->filesize = 3072; //图片默认3M
} else {
$this->filesize = 102400; //附件默认100M
}
}
//上传目录
$this->savedir = I('get.savedir','uploadfile');
$this->display();
}
}
/**
* [del 上传文件]
* @return [type] [description]
*/
public function del()
{
if(IS_POST){
$type = I('post.oringinal_type');
$location = I('post.location');
if($location == 'upload'){
if($type == 'images'){
$photo = I('post.photo');
$thumb = I('post.thumb');
if(file_exists('.'.$photo)){
unlink('.'.$photo);
}
if(file_exists('.'.$thumb)){
unlink('.'.$thumb);
}
} else {
$savepathall = I('post.savepathall');
if(file_exists('.'.$savepathall)){
unlink('.'.$savepathall);
}
}
}
$this->success(L('_DEL_SUCCESS_'));
} else {
$this->error(L('_ACCESS_ERROR_'));
}
}
/**
* [files 获取本地文件]
* @return [type] [description]
*/
public function files()
{
//上传类型
$this->returntype = I('get.returntype');
//获取类型
$this->savedir = I('get.savedir');
//获取类型
$this->type = I('get.type','files');
//返回容器
$this->origin_domid = I('get.myid');
//返回容器的iframe
$this->iframe = I('get.iframe');
//最原始目录
$this->oringi_path = "./Public/Uploads/".$this->savedir."/".$this->type;
//获取目录
$uploadfilePath = I('get.dir',"");
if(empty($uploadfilePath)){
$uploadfilePath = $this->oringi_path."/*";
} else {
$uploadfilePath = decode($uploadfilePath)."/*";
}
//取出来的中文会乱码,暂时不考虑
$dirs = glob($uploadfilePath,GLOB_NOESCAPE);
$dirall = array();
foreach ($dirs as $key => $value) {
if(is_file($value)){
//判断文件类型
$pathinfo = pathinfo($value);
$imagesarr = array('jpg','png','gif','bmp');
if(in_array($pathinfo['extension'],$imagesarr)){
$pathinfo['type'] = 'images';
} else {
$pathinfo['type'] = 'files';
if(!file_exists("./Public/images/uploadfile/".$pathinfo['extension'].".png")){
$pathinfo['extension'] = 'readme';
}
}
$pathinfo['filepath'] = substr($value,1);
$dirall['files'][] = $pathinfo;
} else if(is_dir($value)) {
$arr = explode("/", $value);
$dirarr = array('path'=>$value,'pathname'=>$arr[count($arr)-1]);
$dirall['dirs'][] = $dirarr;
}
}
//当前目录
$this->path = substr($uploadfilePath,0,strlen($uploadfilePath)-2);
//上一目录
$prevarr = explode("/", $this->path);
unset($prevarr[count($prevarr)-1]);
$prevpath = implode("/",$prevarr);
$this->prevpath = $prevpath;
$this->data = $dirall;
$this->display();
}
}
?>
两种上传操作,第一种是图片的上传,满足判断条件后调用uploadPhoto;
而第二种是其他文件的上传,没有任何过滤。(我们就利用这点来getshell)
漏洞2 日志泄露+ssti模板注入
日志泄露
漏洞文件:\www\Apps\Runtime\Logs\Home\19_11_20.log
[ 2019-11-20T00:43:54+08:00 ] 127.0.0.1 /aikehou/echo/index.php/Jquery/?template_file=/etc/passwd
可以看出这是主办方直接测试用的payload,稍加改动即可去读flag。
ssti模板注入(不理解)
上述可以直接读flag源于ssti模板注入造成的前台任意文件读取,跟踪下过程。
漏洞文件:Apps/Home/Controller/JqueryController.class.php
在sublime里对template_file进行find in folder,得到的第一条正好是漏洞文件所在路径,双击框选部分直接跳转到目标代码。
漏洞函数:index()
public function index(){
if(!isset($_GET['template_file'])) {
$this->seoData = array('title' => 'Jquery插件', 'keywords' => 'Jquery插件', 'description' => 'Jquery插件');
$this->display();
}
else{
$this->display($_GET['template_file']);
# display函数对template_file进行处理
}
}
}
Goto Definition1
在Controller.class.php中,直接将我们传递的template_file参数代入进display方法中,跟进:
protected function display($templateFile='',$charset='',$contentType='',$content='',$prefix='') {
$this->view->display($templateFile,$charset,$contentType,$content,$prefix);
}
Goto Definition2
这里除了$templateFile
,其余传递参数都为空,这里将templateFile传递进了fetch()方法。
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');
}
Goto Definition3
$this->fetch
,肯定就是在当前php文件里找了
这里可以将传入的$templateFile
直接当做模板渲染,param string $content
模板输出内容。
/**
* 解析和获取模板内容 用于输出
* @access public
* @param string $templateFile 模板文件名
* @param string $content 模板输出内容
* @param string $prefix 模板缓存前缀
* @return string
*/
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;
}
将/flag直接作为模板进行渲染,/index.php/Jquery/index?template_file=/flag
问题:1:渲染模板的原理是什么?
问题2:这里的路由也不明白怎么找的?
问题3:修补方案不理解?
判断$_GET['template_file']
的路径是不是在模板文件夹的目录下(模板文件夹在哪里???)。
<?php
# 判断指定文件夹下是否存在指定文件
function dir_exist_file($path,$filename)
{
if (!is_dir($path)) {
return false;
}
$files = scandir($path);
foreach ($files as $key => $value) {
// echo $value;
if($filename === $value){
return true;
}else{
continue;
}
}
}
if(dir_exist_file("c:\\","Users")){
# $path放模板文件夹所在位置,$filename即为$template_file
echo "file exists!!";
}
?>
2.2 粤湾租赁
2.5 总结
First:
将代码拖到sublime,find in folder下列内容:
/etc/passwd
flag
upload
参考资料
1、官方writeup:
https://mp.weixin.qq.com/s/7DBMDr85_nBCjBkQoqlOWQ
2、详细writeup:
https://xz.aliyun.com/t/6826#toc-1
https://threezh1.com/2019/12/18/2019%20Redhat%E5%86%B3%E8%B5%9B%20Writeup/