安全-Pass07之黑名单空格绕过(upload-labs)

Posted 小狐狸FM

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了安全-Pass07之黑名单空格绕过(upload-labs)相关的知识,希望对你有一定的参考价值。

前言

  • phpHypertext Preprocessor超文本预处理器,多用于web后端
  • BUUCTF的upload-labs在线靶场和本地的靶场有点差别,如果用文章的方法没法绕过时,注意看一下源码是否一致

相关介绍

PHP 百度百科

Upload-labs文件上传漏洞(空格绕过)——Pass06

PHP: PHP 手册 - Manual

其他介绍

文件上传绕过思路集合

upload-labs靶场下载

upload-labs在线靶场-BUUCTF

蚁剑AntSword

菜刀Cknife

Seay

一、题目


php后端代码

<?php
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
        $file_name = $_FILES['upload_file']['name'];
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA

        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
            if (move_uploaded_file($temp_file,$img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件不允许上传';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}
?>

二、WriteUp

[1]. 函数介绍

deldot是靶场自己定义的函数,不是PHP自带的函数

PHP函数介绍
file_exists(路径)判断指定的文件或目录是否存在,不存在返回true,否则返回false
in_array(变量,数组)如果变量存在于数组就返回true,否则返回false
isset(变量)如果变量存在且值不为null返回true,否则返回false
move_uploaded_file(文件路径,文件夹路径)将文件移动到指定文件夹下
strrchr(字符串,字符)如果字符存在于字符串时,返回第一次找到的字符至字符串末尾的子串。不存在于字符串时就返回false
strtolower(字符串)将字符串全部转换成小写
str_ireplace(字符串1,字符串2,字符串3)在字符串3中搜索,如果含有字符串1的子串就替换成字符串2

[2]. 源码审计

(1). 变量判断

  • php代码进行审计,if语句比较多最好从外往内分析
  • 第一条if语句只是判断了一下提交的post请求中submit参数是否被设置且非空
    PHP:isset - Manual

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {//第一条if语句
	//代码
}

(2). 路径判断

  • 第二条语句判断了一下文件上传的路径存不存在,存在的话就执行里面的代码,不存在就给$msg设置回显信息
  • 通过Seay审计的全局搜索功能可以找到UPLOAD_PATH是在config.php中被定义的
    Pass-02\\index.php的代码中包含了上一级目录下的config.php
    然后这个变量就可以在Pass-02\\index.php中直接使用
  • ../表示访问上一级的目录,所以upload-labs-master\\Pass-02\\index.php包含的是upload-labs-master\\config.php
  • UPLOAD_PATH变量在upload-labs-master\\Pass-02\\index.php中被调用时,就会设置上传的父文件夹为upload-labs-master\\upload

$is_upload = false;
$msg = null;
if (xxx) {//第一条if语句
    if (file_exists(UPLOAD_PATH)) {//第二条if语句
    	//代码
    }else {//第二条if语句为假
        $msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
    }
}

(3). 黑名单

$deny_ext是一个数组类型,存储了需要被过滤的后缀名
之后会对文件的后缀判断,如果后缀符合其中的一个,文件就会被过滤

".php",".php5",".php4",".php3",".php2",
".html",".htm",".phtml",".pht",".pHp",
".pHp5",".pHp4",".pHp3",".pHp2",".Html",
".Htm",".pHtml",".jsp",".jspa",".jspx",
".jsw",".jsv",".jspf",".jtml",".jSp",
".jSpx",".jSpa",".jSw",".jSv",".jSpf",
".jHtml",".asp",".aspx",".asa",".asax",
".ascx",".ashx",".asmx",".cer",".aSp",
".aSpx",".aSa",".aSax",".aScx",".aShx",
".aSmx",".cEr",".sWf",".swf",".htaccess",".ini"
//代码
if (xxx) {//第一条if语句
    if (xxx) {//第二条if语句
    	$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
		//代码
   	}else {//第二条if语句为假
        //代码
    }
}  

(4). 获取上传文件名

$_FILES['upload_file']['name']会获取上传文件的名称,如下图的test3.txt

//代码
if (xxx) {//第一条if语句
    if (xxx) {//第二条if语句
    	//代码
    	$file_name = $_FILES['upload_file']['name'];
		//代码
   	}else {//第二条if语句为假
        //代码
    }
}  

(5). 删除尾部小数点

  • deldot($file_name)将会删除变量$file_name中的最后一个小数点,经过查询发现这个函数不是php自带的,而是靶场中自己定义的
  • 通过Seay源码审计系统可以找到函数的位置是在common.php文件中
    如果不想去思考其中的算法,可以使用php的集成开发环境phpstorm运行一下函数看看结果



//代码
if (xxx) {//第一条if语句
    if (xxx) {//第二条if语句
    	//代码
        $file_name = deldot($file_name);//删除文件名末尾的点
		//代码
	}else{//第二条if语句为假
		//代码
	}
}

(6). 获取文件后缀

  • strrchr($file_name, '.')会返回小数点和文件后缀组成的子串。
    假设文件名为test3.php.jpg,得到的$file_txt值就是.jpg
  • PHP: strrchr - Manual
//代码
if (xxx) {//第一条if语句
    if (xxx) {//第二条if语句
    	//代码
        $file_ext = strrchr($file_name, '.');
		//代码
	}else{//第二条if语句为假
		//代码
	}
}

(7). 小写转换

  • strtolower($file_ext)就是对得到的字符串后缀进行全小写处理,这样就没法进行文件后缀的大小写绕过了
    PHP: strtolower - Manual
//代码
if (xxx) {//第一条if语句
    if (xxx) {//第二条if语句
    	//代码
        $file_ext = strtolower($file_ext); //转换为小写
		//代码
	}else{//第二条if语句为假
		//代码
	}
}

(8). 置空::$DATA

  • 在window的时候如果文件名+::$DATA会把::$DATA之后的数据当成文件流处理,不会检测后缀名,具体参考【文件上传绕过】八、: : $ DATA上传绕过
    如果文件名为test.php::$DATA.jpg时,在windows中会删除::$DATA及之后的内容,则上传window服务器后的文件名为test.php
  • str_ireplace('::$DATA', '', $file_ext)将会用空字符来替换变量$file_ext中的::$DATA子串,防止了::$DATA的上传绕过
  • PHP: str_ireplace - Manual
//代码
if (xxx) {//第一条if语句
    if (xxx) {//第二条if语句
    	//代码
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
		//代码
	}else{//第二条if语句为假
		//代码
	}
}

(9). 黑名单过滤

  • $file_ext存储的是文件后缀(含有小数点)
    数组$deny_ext的值如下,当文件的后缀不为其中的时,才能进入if语句
  • PHP: in_array - Manual
".php",".php5",".php4",".php3",".php2",
".html",".htm",".phtml",".pht",".pHp",
".pHp5",".pHp4",".pHp3",".pHp2",".Html",
".Htm",".pHtml",".jsp",".jspa",".jspx",
".jsw",".jsv",".jspf",".jtml",".jSp",
".jSpx",".jSpa",".jSw",".jSv",".jSpf",
".jHtml",".asp",".aspx",".asa",".asax",
".ascx",".ashx",".asmx",".cer",".aSp",
".aSpx",".aSa",".aSax",".aScx",".aShx",
".aSmx",".cEr",".sWf",".swf",".htaccess",".ini"
//代码
if (xxx) {//第一条if语句
    if (xxx) {//第二条if语句
    	//代码
    	if(!in_array($file_ext, $deny_ext)) {//第三条if语句
            //代码
        } else {//第三条if语句为假
            $msg = '此文件不允许上传';
        }
	}else{//第二条if语句为假
		//代码
	}
}

(10). 文件存储路径设置

  • 在上传文件的时候,文件都会被存储在一个临时的文件夹下
    我们不需要知道具体路径,只需要通过tmp_name参数获取路径即可
  • UPLOAD_PATH的值为../,在config.php文件中定义
    $file_ext是文件的后缀,包含了小数点
    rand(1000,9999)则是从1000到9999数字中取随机数,范围左闭右闭
    date("YmdHis")用于获取当前的时间,以xxxx年xx月xx日xx时xx分xx秒为格式
    两个字符串变量之间的连接用小数点
  • PHP: rand - Manual
    PHP: date - Manual
//代码
if (xxx) {//第一条if语句
    if (xxx) {//第二条if语句
    	//代码
    	if(xxx) {//第三条if语句
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; 
            //代码
        } else {//第三条if语句为假
            //代码
        }
	}else{//第二条if语句为假
		//代码
	}
}

(11). 移动临时文件

//代码
if (xxx) {//第一条if语句
    if (xxx) {//第二条if语句
    	//代码
    	if(xxx) {//第三条if语句
            //代码
            if (move_uploaded_file($temp_file,$img_path)) {//第四条if语句
                 $is_upload = true;
            } else {//第四条if语句为假
                $msg = '上传出错!';
            }
        } else {//第三条if语句为假
            //代码
        }
	}else{//第二条if语句为假
		//代码
	}
}

[3]. 空格绕过

  • 源码中没有将文件前后的空格过滤,所以可以在文件末尾添加一个空格
  • 上传到服务器的时候,得到的后缀就是.php空格,从而绕过黑名单的限制
    又因为.php空格不符合windows文件的命名规则,所以服务器存储的时候会将末尾的空格去掉,文件末尾就变回了.php

复制图片链接,并访问,没有报错表示.php文件解析成功


在服务器中,文件是以.php的后缀存储

用蚁剑或菜刀连接shell

以上是关于安全-Pass07之黑名单空格绕过(upload-labs)的主要内容,如果未能解决你的问题,请参考以下文章

安全-Pass11之黑名单双写绕过(upload-labs)

安全-Pass08之黑名单点绕过(upload-labs)

安全-Pass09之黑名单::$DATA绕过(upload-labs)

安全-Pass05之黑名单.user.ini绕过(upload-labs)

安全-Pass10之黑名单点空点绕过(upload-labs)

安全-Pass04之.htaccess绕过(upload-labs)