Java 通过魔数判断上传文件的类型

这里所说的表示不同文件类型的魔术数字,指定是文件的最开头的几个用于唯一区别其它文件类型的字节,有了这些魔术数字,我们就可以很方便的区别不同的文件,这也使得编程变得更加容易,因为我减少了我们用于区别一个文件的文件类型所要花费的时间。

比如,一个JPEG文件,它开头的一些字节可能是类似这样的”ffd8 ffe0 0010 4a46 4946 0001 0101 0047 ……JFIF…..G“,这里”ffd8“就表示了这个文件是一个JPEG类型的文件,”ffe0“表示这是JFIF类型结构。

以下例出的是一些我们常见的文件类型,以及它用于判断这种文件的类型的几个开始字节及所对尖的ASCII数字:

图片文件

文件类型扩展名16进制数字
xx这里表示变量
Ascii数字
. = 不是Ascii字符
Bitmap format.bmp42 4dBM
FITS format.fits53 49 4d 50 4c 45SIMPLE
GIF format.gif47 49 46 38GIF8
Graphics Kernel System.gks47 4b 53 4dGKSM
IRIS rgb format.rgb01 da..
ITC (CMU WM) format.itcf1 00 40 bb….
JPEG File Interchange Format.jpgff d8 ff e0….
NIFF (Navy TIFF).nif49 49 4e 31IIN1
PM format.pm56 49 45 57VIEW
PNG format.png89 50 4e 47.PNG
Postscript format.[e]ps25 21%!
Sun Rasterfile.ras59 a6 6a 95Y.j.
Targa format.tgaxx xx xx
TIFF format (Motorola – big endian).tif4d 4d 00 2aMM.*
TIFF format (Intel – little endian).tif49 49 2a 00II*.
X11 Bitmap format.xbmxx xx 
XCF Gimp file structure.xcf67 69 6d 70 20 78 63 66 20 76gimp xcf
Xfig format.fig23 46 49 47#FIG
XPM format.xpm2f 2a 20 58 50 4d 20 2a 2f/* XPM */

压缩文件

文件类型扩展名16进制数字
xx这里表示变量
Ascii数字
. = 不是Ascii字符
Bzip.bz42 5aBZ
Compress.Z1f 9d..
gzip format.gz1f 8b..
pkzip format.zip50 4b 03 04PK..

存档文件

文件类型扩展名16进制数字
xx这里表示变量
Ascii数字
. = 不是Ascii字符
TAR (pre-POSIX).tarxx xx(a filename)
TAR (POSIX).tar75 73 74 61 72ustar (offset by 257 bytes)

可执行文件

文件类型扩展名16进制数字
xx这里表示变量
Ascii数字
. = 不是Ascii字符
MS-DOS, OS/2 or MS Windows 4d 5aMZ
Unix elf 7f 45 4c 46.ELF

其它文件

 

文件类型扩展名16进制数字
xx这里表示变量
Ascii数字
. = 不是Ascii字符
pgp public ring 99 00..
pgp security ring 95 01..
pgp security ring 95 00..
pgp encrypted data a6 00¦.

 通常,在WEB系统中,上传文件时都需要做文件的类型校验,大致有如下几种方法:

1. 通过后缀名,如exe,jpg,bmp,rar,zip等等。

2. 通过读取文件,获取文件的Content-type来判断。

3. 通过读取文件流,根据文件流中特定的一些字节标识来区分不同类型的文件。

4. 若是图片,则通过缩放来判断,可以缩放的为图片,不可以的则不是。

然而,在安全性较高的业务场景中,1,2两种方法的校验会被轻易绕过。

1. 伪造后缀名,如图片的,非常容易修改。

2. 伪造文件的Content-type,这个稍微复杂点,为了直观,截图如下:

 

 

3.较安全,但是要读取文件,并有16进制转换等操作,性能稍差,但能满足一定条件下对安全的要求,所以建议使用。

  但是文件头的信息也可以伪造,截图如下,对于图片可以采用图片缩放或者获取图片宽高的方法避免伪造头信息漏洞。

 

                                                      被伪装成gif的恶意图片文件

对应的Java代码如下:

import org.apache.commons.lang3.StringUtils;
 
/**
 * 文件类型与对应的文件魔数枚举类 
 *
 */
public enum FileTypeEnum { 
    
	/** JPEG  (jpg)*/
    JPEG("JPG", "FFD8FF"),
 
    /** PNG */
    PNG("PNG", "89504E47"),
 
    /** GIF */
    GIF("GIF", "47494638"),
  
    /** TIFF (tif)  */
    TIFF("TIF", "49492A00"),
 
    /** Windows bitmap (bmp) */
    BMP("BMP","424D"),
    
    BMP_16("BMP","424D228C010000000000"), //16色位图(bmp) 
    
	BMP_24("BMP","424D8240090000000000"), //24位位图(bmp)  
	
	BMP_256("BMP","424D8E1B030000000000"), //256色位图(bmp)     
 
    /** CAD  (dwg) */
    DWG("DWG", "41433130"),
 
    /** Adobe photoshop  (psd)*/
    PSD("PSD", "38425053"),
 
    /** Rich Text Format  (rtf)*/
    RTF("RTF", "7B5C727466"),
 
    /** XML */
    XML("XML", "3C3F786D6C"),
 
    /** HTML (html)*/
    HTML("HTML", "68746D6C3E"),
    
    /** Email [thorough only] (eml)*/
    EML("EML", "44656C69766572792D646174653A"),  
	
    /** Outlook Express (dbx) */
    DBX("DBX", "CFAD12FEC5FD746F "),
 
    /** Outlook (pst)*/
    PST("", "2142444E"),
 
    /** doc;xls;dot;ppt;xla;ppa;pps;pot;msi;sdw;db */
    OLE2("OLE2", "0xD0CF11E0A1B11AE1"),
 
    /** Microsoft Word/Excel 注意:word 和 excel的文件头一样 */
    XLS("XLS", "D0CF11E0"),
    
    /** Microsoft Word/Excel 注意:word 和 excel的文件头一样 */
    DOC("DOC", "D0CF11E0"),
    
    /** Microsoft Word/Excel 2007以上版本文件 注意:word 和 excel的文件头一样 */
    DOCX("DOCX", "504B0304"),  
 
    /** Microsoft Word/Excel 2007以上版本文件 注意:word 和 excel的文件头一样 504B030414000600080000002100*/
    XLSX("XLSX", "504B0304"), 
 
    /** Microsoft Access (mdb)*/
    MDB("MDB", "5374616E64617264204A"),
 
    /** Word Perfect (wpd)*/
    WPB("WPB", "FF575043"),
 
    /** Postscript */
    EPS("EPS", "252150532D41646F6265"),
 
    /** Postscript */
    PS("PS", "252150532D41646F6265"),
 
    /** Adobe Acrobat (pdf)  */
    PDF("PDF", "255044462D312E"),
    
    /** Quicken (qdf) */
    QDF("qdf", "AC9EBD8F"),
    
    /** QuickBooks Backup (qdb) */
    QDB("qbb", "458600000600"),
    
    /** Windows Password  (pwl)*/
    PWL("PWL", "E3828596"),
 
    /** ZIP Archive */
    ZIP("", "504B0304"),
 
    /** ARAR Archive */
    RAR("", "52617221"),
 
    /** WAVE (wav) */
    WAV("WAV", "57415645"),
 
    /** AVI */
    AVI("AVI", "41564920"),
 
    /** Real Audio (ram)*/
    RAM("RAM", "2E7261FD"),
 
    /** Real Media (rm) rmvb/rm相同  */
    RM("RM", "2E524D46"),
    
    /** Real Media (rm) rmvb/rm相同  */
    RMVB("RMVB", "2E524D46000000120001"),  
    
    /** MPEG (mpg)  */
    MPG("MPG", "000001BA"),
	
    /** Quicktime  (mov)*/
    MOV("MOV", "6D6F6F76"),
 
    /** Windows Media (asf) */
    ASF("ASF", "3026B2758E66CF11"),
 
    /** ARJ Archive */
    ARJ("ARJ", "60EA"),
 
    /** MIDI (mid) */
    MID("MID", "4D546864"),
    
    /** MP4 */  
    MP4("MP4", "00000020667479706D70"), 
    
    /** MP3 */  
    MP3("MP3", "49443303000000002176"),  
    
    /** FLV */  
    FLV("FLV", "464C5601050000000900"), 
	
    /** 1F8B0800000000000000 */
    GZ("GZ", "1F8B08"),
    
    /** CSS */ 
    CSS("CSS", "48544D4C207B0D0A0942"),
    
    /**  JS */  
    JS("JS", "696B2E71623D696B2E71"), 
	 
    /**  Visio */  
    VSD("VSD", "d0cf11e0a1b11ae10000"),
    
    /** WPS文字wps、表格et、演示dps都是一样的 */  
    WPS("WPS", "d0cf11e0a1b11ae10000"),
    
    /** torrent */  
    TORRENT("TORRENT", "6431303A637265617465"), 
    
    /** JSP Archive */  
    JSP("JSP", "3C2540207061676520"),  
    
    /** JAVA Archive */  
    JAVA("JAVA", "7061636B61676520"), 
    
    /** CLASS Archive */  
    CLASS("CLASS", "CAFEBABE0000002E00"), 
    
    /** JAR Archive */  
    JAR("JAR", "504B03040A000000"), 
    
    /** MF Archive */  
    MF("MF", "4D616E69666573742D56"),

    /** EXE Archive */  
    EXE("EXE", "4D5A9000030000000400"), 

    /** ELF Executable */  
    ELF("ELF", "7F454C4601010100"), 

    /** Lotus 123 v1 */  
    WK1("WK1", "2000604060"),
    
    /** Lotus 123 v3 */  
    WK3("WK3", "00001A0000100400"),
    
    /** Lotus 123 v5 */  
    WK4("WK4", "00001A0002100400"), 

    /** Lotus WordPro v9 */  
    LWP("LWP", "576F726450726F"), 
    
    /** Sage(sly.or.srt.or.slt;sly;srt;slt) */  
    SLY("SLY", "53520100"), 
    
    /** CHM Archive */  
   /* CHM("CHM", "49545346030000006000"),  
    INI("INI", "235468697320636F6E66"), 
    SQL("SQL", "494E5345525420494E54"), 
    BAT("BAT", "406563686F206f66660D"),  
    PROPERTIES("", "6C6F67346A2E726F6F74"), 
    MXP("", "04000000010000001300"),  */
    
	NOT_EXITS_ENUM("", "");
	
	//文件类型对应的名称
	private String fileTypeName;
	
	//文件类型对应的魔数
	private String magicNumberCode;
	
	private FileTypeEnum(String fileTypeName, String magicNumberCode) {
		this.fileTypeName = fileTypeName;
		this.magicNumberCode = magicNumberCode;
	} 
	 
	public String getFileTypeName() {
		return fileTypeName;
	} 
 
	public String getMagicNumberCode() {
		return magicNumberCode;
	}
 
 
	/**
	 * 根据文件类型获取文件类型魔数编码
	 * 默认返回标准件
	 * @param magicNumberCode - 文件类型魔数编码
	 * @return
	 */
	public static FileTypeEnum getByMagicNumberCode(String magicNumberCode) {
		if (StringUtils.isNotBlank(magicNumberCode)) {
			for (FileTypeEnum type : values()) { 
				if (magicNumberCode.toUpperCase().startsWith(type.getMagicNumberCode())) {
					return type; 
				} 
			} 
		}
		
		return FileTypeEnum.NOT_EXITS_ENUM;
	}
	
	/**
	 * 根据文件类型后缀名获取枚举
	 * 
	 * @param fileTypeName - 文件类型后缀名
	 * @return
	 */
	public static FileTypeEnum getByFileTypeName(String fileTypeName) { 
		if (StringUtils.isNotBlank(fileTypeName)) {
			for (FileTypeEnum type : values()) {
				if (type.getFileTypeName().equals(fileTypeName)) {
					return type; 
				} 
			}
		}
		return FileTypeEnum.NOT_EXITS_ENUM;
	}
  
}

 

 

 

  
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
  
/**
 * 通过文件魔数来判断文件类型
 * 可以最大量避免通过后缀名来判断文件类型的漏洞
 * 
 * @author 000125
 *
 */
public class FileTypeUtils {   
	private static final Logger LOGGER = LoggerFactory.getLogger(FileTypeUtils.class);
	
    /**  
     * 获取图片文件实际类型,若不是图片则返回null]
     * @param file 
     * @return fileType  
     */  
    public final static String getImageFileType(File file) {  
        if (isImage(file)) {
            try {
                ImageInputStream iis = ImageIO.createImageInputStream(file);
                Iterator<ImageReader> iter = ImageIO.getImageReaders(iis);
                if (!iter.hasNext()) {
                    return null;
                }
                ImageReader reader = iter.next();
                iis.close();
                return reader.getFormatName();
            } catch (IOException e) {
                return null;
            } catch (Exception e) {
                return null;
            }
        }
        return null;
    }  
  
    /**   
     * 获取文件类型,包括图片,若格式不是已配置的,则返回null
     * @param file 
     * @return fileType  
     */  
    public final static String getFileByFile(File file) {  
        String filetype = null;  
        byte[] b = new byte[50];  
        try {  
            InputStream is = new FileInputStream(file);  
            is.read(b);  
            filetype = getFileTypeByStream(b);  
            is.close();  
        } catch (FileNotFoundException e) {  
            e.printStackTrace();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
        return filetype;  
    }  
      
    /** 
     * 通过数据流(二进制数据)判断文件类型
     * @param b 
     * @return fileType  
     */  
    public final static String getFileTypeByStream(byte[] b) {  
        String magicNumberCode = String.valueOf(getFileHexString(b));  
        
        if (StringUtils.isBlank(magicNumberCode)) { 
            return FileTypeEnum.getByMagicNumberCode(magicNumberCode.toUpperCase()).getFileTypeName();
            
        }
        return FileTypeEnum.NOT_EXITS_ENUM.getFileTypeName();  
    }  
      
    /** 
     * isImage,判断文件是否为图片
     * @param file
     * @return true 是 | false 否 
     */
    public static final boolean isImage(File file){
        boolean flag = false;
        try {
            BufferedImage bufreader = ImageIO.read(file);
            int width = bufreader.getWidth();
            int height = bufreader.getHeight();
            if(width==0 || height==0){
                flag = false;
            }else {
                flag = true;
            }
        } catch (IOException e) {
            flag = false;
        }catch (Exception e) {
            flag = false;
        }
        return flag;
    }
    
   
    /**
     * 通过文件路径判断文件类型
     * @param path
     * @return
     * @throws IOException
     */
    public static FileTypeEnum getFileTypeByPath(String path) {
        // 获取文件头
        String magicNumberCode = null;
		try {
			magicNumberCode = getFileHeader(path);
		} catch (Exception e) { 
			e.printStackTrace();
			return FileTypeEnum.NOT_EXITS_ENUM;
		}
 
        if (StringUtils.isBlank(magicNumberCode)) { 
            return FileTypeEnum.getByMagicNumberCode(magicNumberCode.toUpperCase());
            
        }
 
        return FileTypeEnum.NOT_EXITS_ENUM;
    }

    
    /**
     * 通过文件路径获取文件头(即文件魔数)
     * @param path
     * @return
     * @throws IOException
     */
    public static String getFileHeader(String path) throws Exception {
        byte[] b = new byte[28];
        InputStream inputStream = null;
 
        try {
            inputStream = new FileInputStream(path);
            inputStream.read(b, 0, 28);
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
 
        return getFileHexString(b);
    }
    
    /**   
     * 把文件二进制流转换成十六进制数据
     * @param b 
     * @return fileTypeHex  
     */  
    public final static String getFileHexString(byte[] b) {  
        StringBuilder builder = new StringBuilder();  
        if (b == null || b.length <= 0) {  
            return null;  
        }  
        
        for (int i = 0; i < b.length; i++) {  
            int v = b[i] & 0xFF;  
            String hv = Integer.toHexString(v);  
            if (hv.length() < 2) {  
            	builder.append(0);  
            }  
            builder.append(hv);  
        }  
        return builder.toString();  
    }  
}

这样,不管是传入的文件有后缀名,还是无后缀名,或者修改了后缀名,真正获取到的才是该文件的实际类型,这样避免了一些想通过修改后缀名或者Content-type信息来攻击的因素。但是性能与安全永远是无法同时完美的,安全的同时付出了读取文件的代价。本人建议可采用后缀名与读取文件的方式结合校验,毕竟攻击是少数,后缀名的校验能排除大多数用户,在后缀名获取不到时再通过获取文件真实类型校验,这样来适当提高性能。

 

3.较安全,但是要读取文件,并有16进制转换等操作,性能稍差,但能满足一定条件下对安全的要求,所以建议使用。

  但是文件头的信息也可以伪造,截图如下,对于图片可以采用图片缩放或者获取图片宽高的方法避免伪造头信息漏洞。

 

                                                      被伪装成gif的恶意图片文件

对应的Java代码如下:

Java中,可以通过以下方法来判断上传文件类型: 1. 通过文件名后缀名判断 可以通过文件名中的后缀名来判断文件类型,例如: ```java String fileName = "example.jpg"; if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) { // 文件类型为jpg或jpeg } else if (fileName.endsWith(".png")) { // 文件类型为png } else { // 其他文件类型 } ``` 但需要注意的是,文件名后缀可以被篡改,因此此方法不是十分可靠。 2. 通过文件的MIME类型判断 可以通过文件的MIME类型判断文件类型,例如: ```java import javax.activation.MimetypesFileTypeMap; MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap(); String mimeType = mimeTypesMap.getContentType(filePath); if (mimeType.startsWith("image/")) { // 文件类型为图片 } else if (mimeType.startsWith("video/")) { // 文件类型为视频 } else if (mimeType.startsWith("text/")) { // 文件类型为文本文件 } else { // 其他文件类型 } ``` 需要注意的是,MIME类型也可以被篡改。 3. 通过文件内容判断 可以通过读取文件的内容来判断文件类型,例如: ```java import java.io.*; public static String getFileType(String filePath) throws IOException { InputStream inputStream = new FileInputStream(filePath); byte[] bytes = new byte[4]; inputStream.read(bytes); inputStream.close(); String type = ""; String fileHead = bytesToHexString(bytes); if (fileHead == null || fileHead.length() == 0) { return null; } fileHead = fileHead.toUpperCase(); FileType[] fileTypes = FileType.values(); for (FileType fileType : fileTypes) { if (fileHead.startsWith(fileType.getValue())) { type = fileType.name(); break; } } return type; } public static String bytesToHexString(byte[] bytes) { StringBuilder stringBuilder = new StringBuilder(); if (bytes == null || bytes.length <= 0) { return null; } for (int i = 0; i < bytes.length; i++) { int v = bytes[i] & 0xFF; String hv = Integer.toHexString(v); if (hv.length() < 2) { stringBuilder.append(0); } stringBuilder.append(hv); } return stringBuilder.toString(); } enum FileType { JPEG("FFD8FF"), // JPEG (jpg) PNG("89504E47"), // PNG (png) GIF("47494638"), // GIF (gif) TIFF("49492A00"), // TIFF (tif) BMP("424D"), // Windows Bitmap (bmp) DWG("41433130"), // CAD (dwg) PSD("38425053"), // Adobe Photoshop (psd) RTF("7B5C727466"), // Rich Text Format (rtf) XML("3C3F786D6C"), // XML (xml) HTML("68746D6C3E"), // HTML (html) PDF("255044462D312E"), // Adobe PDF (pdf) ZIP("504B0304"), // ZIP Archive (zip) RAR("52617221"), // RAR Archive (rar) WAV("57415645"), // Wave (wav) AVI("41564920"), // AVI (avi) MP4("00000020667479706D70"), // MPEG-4 (mp4) MPG("000001BA"), // MPEG (mpg) WMV("3026B2758E66CF11"), // Windows Media Video (wmv) MID("4D546864"); // MIDI (mid) private String value = ""; private FileType(String value) { this.value = value; } public String getValue() { return value; } } ``` 此方法可以通过读取文件的前几个字节来判断文件类型,比较可靠,但需要注意的是,不同类型的文件前几个字节可能是相同的,因此有一定的误判率。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值