【上传专题】服务器端过滤及其绕过方式

上一篇文章,讲述了如何绕过前端文件类型。

详情见:http://793404905.blog.51cto.com/6179428/1566743

1、引言

这一篇讲述一些常见的服务端过滤方式,以及各种过滤方式存在的隐患。并给出怎样处理服务端和前端过滤,以达到更加安全的上传机制。

2、本文大纲

1)Content-Type(Mime Type)检测过滤,以及如何绕过;

2)文件扩展名检测;

3)文件头检测;

4)文件加载检测。

3、Content-Type 检测过滤

按照正常的上传方式,会根据上传的文件类型,指定Content-Type类型,例如:jpg文件对应的Content-Type是image/jpeg;

见下例:

package com.fileupload.servlets;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

public class UploadFilterExtServlet extends HttpServlet {

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		if (!ServletFileUpload.isMultipartContent(request)) {
			response.getWriter().print("NOT MultiPart Request");
			return;
		}

		String webPath = this.getServletContext().getRealPath("");

		DiskFileItemFactory factory = new DiskFileItemFactory();
		factory.setSizeThreshold(1024 * 1024);
		factory.setRepository(new File(webPath + File.separator + "tmp")); // 临时仓库

		ServletFileUpload fileUpload = new ServletFileUpload(factory);
		fileUpload.setFileSizeMax(1024 * 1024 * 5);
		fileUpload.setSizeMax(1024 * 1024 * 6);
		fileUpload.setHeaderEncoding("utf-8");

		try {
			List<FileItem> fileItems = fileUpload.parseRequest(request);
			for (FileItem fileItem : fileItems) {
				String fieldName = fileItem.getFieldName();  // 字段名称
				String name = fileItem.getName();                      // 如果是表单字段,那么为空;否则为文件名
				String contentType = fileItem.getContentType(); // 获取上传文件的Content-Type类型
				if (!fileItem.isFormField()) { // 非表单字段,即上传文件
					File file = new File(webPath + File.separator + "upImage" + File.separator + name);
					if (!file.getParentFile().exists()) {
						file.mkdir();
					}
					if (contentType.equalsIgnoreCase("image/jpeg")) {
						fileItem.write(file);
					}else {
						if (file.exists() &&  file.isFile()) {
							fileItem.delete();
							response.getWriter().print("Invalid File.");
						}
					}
				}

			}

		} catch (FileUploadException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

}

在修改Content-Type之后,服务端将认为此次上传是合法,因此也就绕过了Content-Type的限制。

4、文件扩展名检测

如果在java中使用文件扩展名,并不存在0x00截断的问题,但是如果是asp那么会出现0x00文件截断问题,例如:上传test.txt.jpg 将.修改为0x00,那么系统会认为test.txt才是其文件名称,具体这里不做介绍,但是作为一种相对简单还是有一定效果的检测方式,文件扩展名检测一般是必须的。但是并不代表其是种安全的依靠。

简单而言,我们可以修改文件名以jpg后缀即可,也就可以上传非法文件了。

if (contentType.endsWith("jpg")) { // 将3中代码,判断content-type修改为判断jpg后缀
    fileItem.write(file);
}

如下图:

那么同样可以上传非图片的文件,可能你会认为上传在服务器上的该文件,已经命名为jpg文件,顶多无法显示,如果你这样想就大错特错,因为可以将非法的脚本嵌入到文件中。并且文件名扩展的检测一般使用白名单比较好,因为黑名单难免会有遗漏,一旦遗漏了,也可能会有致命的问题。

5、文件头检测

通常一个文件会有一种标识,即表明该文件的类型。因此采用4中的方式上传一个txt文件,虽然其绕过了后缀名的检测,但是此时我们可以对该文件进行检测,初步判定该文件是否是jpg文件,也就是通过文件头来判定。

文件头一般是一个文件的开头字节内容,如下代码,展示java获取文件头的方式:

package com.fileupload.types;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.codec.binary.Hex;

/**
 *  判断文件头类型是否合法
 * @author wangzp
 *
 */
public class FileType {

		public final static String JPG = "FFD8FF";

		public final static String PNG = "89504E47";

		public final static String GIF = "47494638";

		public final static String BMP = "424D";

		public final static Map<String, String> fileTypes = new HashMap<String, String>();

		static {
			fileTypes.put("jpg", JPG);
			fileTypes.put("png", PNG);
			fileTypes.put("gif", GIF);
			fileTypes.put("bmp", BMP);
		}

		/**
		 * 获取文件头
		 * @param filepath
		 * @return
		 * @throws IOException 
		 */
		public static String getFileHeader(File file) throws IOException {

			FileInputStream input = new FileInputStream(file);

			byte[] buffer = new byte[4];
			input.read(buffer, 0, buffer.length);
			input.close();

			return new String(Hex.encodeHex(buffer));
		}

		/**
		 * 验证文件头类型是否合法
		 * @param fileType
		 * @param file
		 * @return
		 * @throws IOException
		 */
		public static boolean isValidFile(String fileType, File file) throws IOException {
			String fileHeader = getFileHeader(file);
			String fileTypeHeader = fileTypes.get(fileType);

			if (fileHeader == null || fileTypeHeader == null) {
				return false;
			}

			if (fileHeader.startsWith(fileTypeHeader)) {
				return true;
			}
			return false;
		}

}

由此,我们可以在上传之后判断该文件是否是合法文件,如下代码展示:

if (name.endsWith("jpg")) {
						fileItem.write(file);
						if (!FileType.isValidFile("jpg", file)) {
//							fileItem.delete();
							file.delete();
							return;
						}else {
							response.getWriter().print("Invalid File Header.");
							return;
						}

					}

代码有点粗糙,但基本可以展示出使用test.txt伪装的jpg文件,是无法上传成功的;但是这不是绝对的,因此可以在图片中加入虚假的文件头。那么面对这种情况,该如何解决呢?接下来将使用文件加载检测。

6、文件加载检测

文件加载实际上是对文件的预览方式,可以分为一次渲染和二次渲染;一般而言二次渲染后的图片很难攻入,很难在图片中嵌入代码,因此个人建议使用二次渲染,至少应该一次渲染,如果渲染失败,可以认为该文件是非法文件。不让其上传。

在本文中,不介绍文件加载检测过程,将在后续文章中介绍图片渲染的方法。

时间: 2024-08-11 00:16:43

【上传专题】服务器端过滤及其绕过方式的相关文章

【上传专题】仅前端过滤

在论坛等平台中,上传各种附件是必不可少的功能,往往也是黑客们容易攻击的地方.在上传专题中,与大家分享一些关于如何破解上传,以及如何防御,通过两者的博弈方式,体现出上传附件攻防的微妙之处. 1. 概述 本文介绍前端过滤上传附件扩展名,如何使用burosuite绕过前端过滤,上传非法文件. 2. 前提条件 1)burpsuite工具: 2)apache commons fileupload 相关jar包: 3. 前端代码示例 <!DOCTYPE HTML PUBLIC "-//W3C//DTD

android 用户头像,图片裁剪,上传并附带用户数据base64code 方式

图片上传的文件流我上一篇博客写了,这一篇我们说一下base64,base64上传方式就是将图片转换成base64码,然后把base64码以字符串的方式上传,然后服务器接收到以后再解码就可以了,相对于文件流来说比较简单: 用户头像上传我们首先要获得图片的url然后再裁剪图片,然后把裁剪后的图片转换成base64然后在上传: 下边是安卓端代码: 首先我们要获得裁剪后的图片:一,选择图片: 代码如下,通过对话框选择获得图片的方式: activity: /* * 提示对话框 */ private voi

SpringMVC上传文件的三种解析方式

springMVC上传文件后,在action解析file文件的三种方式. jsp页面的写法: <form action="parserUploadFile1" method="post" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit"

android客户端把SD卡文件上传到服务器端并保存在PC硬盘文件夹中

在局域网内,实现从android客户端把手机SD卡上的文件上传到PC服务器端,并保存在PC硬盘的指定文件夹下.同时把PC端硬盘文件的目录和对文件的描述信息保存在mysql数据库中. 1.客户端关键代码: (1)获得SD卡上的文件 /** * 获得文件路径和状态信息 * * @return */ private String getFiles() { File path = null; // 判断SD卡是否存在可用 if (Environment.getExternalStorageState()

记录: 百度webuploader 分片文件上传java服务器端(spring mvc)示例的优化

最近项目上用到文件分片上传,于是找到了百度的一个开源前端控件webuploader. 于是尝试使用. 下载下来后,它提供的服务器端示例代码是php版的,那么Java版的呢? 其实,上传文件都是按照rfc1867标注来的, 只是分段上传需要在前端多做点事情.分段上传原理其实就是在前端使用JavaScript对文件进行分割成不同小块,然后每次ajax请求就post一小块,直到全部收到为止. 但是,为了确保后端能判断文件是否完整的收到,需要得知当前是第几块,一共多少块,每个分段的大小是多少(前后端同学

html2canvas截屏后上传到服务器端(springmvc3)

利用html2canvas截屏后可以生成base64类型的图片,但是这样的图片很明显不能被引用或者被SNS工具来分享.这样就必须截屏后再上传数据到后台服务器端重新保存. 前端js代码: $("#saveImage").click(function() {html2canvas($('#mypics'), {onrendered : function(canvas) {var myImage = canvas.toDataURL("image/jpeg");//并将图

iOS:图片上传时两种图片压缩方式的比较

上传图片不全面的想法:把图片保存到本地,然后把图片的路径上传到服务器,最后又由服务器把路径返回,这种方式不具有扩展性,如果用户换了手机,那么新手机的沙盒中就没有服务器返回的图片路径了,此时就无法获取之前已经上传了的头像了,在项目中明显的不可行. 上传图片的正确方式:上传头像到服务器一般是将图片NSData上传到服务器,服务器返回一个图片NSString地址,之后再将NSString的路径转为url并通过url请求去更新用户头像(用户头像此时更新的便是NSString) 代码为: AFHTTPRe

winform程序压缩文件上传,服务器端asp.net mvc进行接收解压

期间编程没什么难度,唯一可能忽略导致结果失败是asp.net  mvc配置 对于压缩文件大的话,需要配置mvc的最大接收量: <system.web> <httpRuntime maxRequestLength="2147483647" executionTimeout="3600" /> <!--允许上传数据大小-> </system.web> <system.webServer> <!--允许上传

jar包上传到服务器端的那些事

做为一个彻头彻尾的菜鸟,最近需要在服务器上跑代码,揪心的事可真不少.      在本地写完代码了,得打包上传到服务器.问题来了,那么多第三方的jar包怎么办?     要么打成 Runnable的jar包吧,每次打的包都好大,上传效率太低了.要是遇上那种bug还有很多的代码,那就够麻烦了.要么打成普通的jar包,不把第三方的包包含进来,然后在profile中(linux系统下)修改classpath的值,这样也是可以的,但是如果第三方的Jar包很多,需要修改的classpath就很多了,也不大方