java 通过 MagicNumber获取文件类型,并校验

java 通过 MagicNumber 获取文件类型

一、问题产生背景

1、用户上传文件,前端限制了文件类型,后端没做限制
2、用户通过改变文件的后缀,伪造符合要求的文件
3、用户通过上传文件,来攻击服务,提高服务的安全性

二、解决方案

1、后端增加后缀的校验
2、后端通过文件流读取成二进制流,并转成MagicNumber(魔数)来获取文件类型

三、解决方案1(后缀校验)
1、获取文件后缀类型

含义:通过文件路径解析文件后缀,采用最平常的方式,后缀截取

    /**
     * 获取文件名后缀
     *
     * @param filePath 文件名
     * @return 后缀名
     */
    public static String getFileExtension(String filePath) {
        // 使用lastIndexOf方法找到"."的位置
        int lastDotIndex = filePath.lastIndexOf(".");
        // 如果"."存在并且不在字符串的开始位置(即文件名包含点)
        if (lastDotIndex > 0 && lastDotIndex < filePath.length() - 1) {
            // 获取从"."开始到字符串结束的部分,即文件后缀
            return filePath.substring(lastDotIndex + 1);
        } else {
            // 如果没有找到"."或者"."在字符串的开始位置,说明没有文件后缀
            return "";
        }
    }
2、获取所有可接受的文件类型
    public static List<String> allowFileTypeList = Arrays.asList("jpg", "jpeg", "png", "gif", "bmp", "tiff", "tif", "pdf", "doc", "docx", "xls", "xlsx", "xlsm", "apk");
3、判断该文件后缀是否在文件类型列表中
String fileName = "文件名称.pdf";
String fileExtension = MagicNumberIdentifier.getFileExtension(fileName);
            List<String> allowHtmlTypeList = MagicNumberIdentifier.allowHtmlTypeList;
            if (!allowHtmlTypeList.contains(fileExtension)) {
                log.info("feign上传HTML,该文件后缀的文件格式,不符合要求...");
            }
4、根据返回结果判断是否进行下一步
三、解决方案2(MagicNumber校验)
1、通过Byte数组获取文件头
    /**
     * 得到上传文件的文件头
     *
     * @param src 文件
     * @return 字符串
     */
    public static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (byte b : src) {
            int v = b & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }
2、根据头判断属于哪种文件类型
    /**
     * 根据制定文件的文件头判断其文件类型
     *
     * @param byteArray 文件转换后的byte数组
     * @return 文件类型
     */
    public static String getCommonFileType(byte[] byteArray) {
        String res = null;
        String fileCode = bytesToHexString(byteArray);
        if (StringUtils.isBlank(fileCode)) {
            return null;
        }
        Map<String, String> allFileType = getAllFileType();
        for (String key : allFileType.keySet()) {
            // 验证前5个字符比较
            if (key.toLowerCase().startsWith(fileCode.toLowerCase().substring(0, 5))
                    || fileCode.toLowerCase().substring(0, 5).startsWith(key.toLowerCase())) {
                res = allFileType.get(key);
                break;
            }
        }
        return res;
    }
    /**
     * 常用文件格式
     */
    public static Map<String, String> getAllFileType() {
        Map<String, String> fileTypeMap = new HashMap<>();
        fileTypeMap.put("ffd8ffe000104a464946", "jpg"); // JPEG (jpg)
        fileTypeMap.put("89504e470d0a1a0a0000", "png"); // PNG (png)
        fileTypeMap.put("47494638396126026f01", "gif"); // GIF (gif)
        fileTypeMap.put("49492a00227105008037", "tif"); // TIFF (tif)
        fileTypeMap.put("424d228c010000000000", "bmp"); // 16色位图(bmp)
        fileTypeMap.put("424d8240090000000000", "bmp"); // 24位位图(bmp)
        fileTypeMap.put("424d8e1b030000000000", "bmp"); // 256色位图(bmp)
        fileTypeMap.put("41433130313500000000", "dwg"); // CAD (dwg)

        fileTypeMap.put("7b5c727466315c616e73", "rtf"); // Rich Text Format (rtf)
        fileTypeMap.put("38425053000100000000", "psd"); // Photoshop (psd)
        fileTypeMap.put("46726f6d3a203d3f6762", "eml"); // Email [Outlook Express 6] (eml)
        fileTypeMap.put("d0cf11e0a1b11ae10000", "doc"); // MS Excel 注意:word、msi 和 excel的文件头一样
        fileTypeMap.put("d0cf11e0a1b11ae10000", "vsd"); // Visio 绘图
        fileTypeMap.put("5374616E64617264204A", "mdb"); // MS Access (mdb)
        fileTypeMap.put("252150532D41646F6265", "ps");
        fileTypeMap.put("255044462d312e350d0a", "pdf"); // Adobe Acrobat (pdf)
        fileTypeMap.put("2e524d46000000120001", "rmvb"); // rmvb/rm相同
        fileTypeMap.put("464c5601050000000900", "flv"); // flv与f4v相同
        fileTypeMap.put("00000020667479706d70", "mp4");
        fileTypeMap.put("49443303000000002176", "mp3");
        fileTypeMap.put("000001ba210001000180", "mpg"); //
        fileTypeMap.put("3026b2758e66cf11a6d9", "wmv"); // wmv与asf相同
        fileTypeMap.put("52494646e27807005741", "wav"); // Wave (wav)
        fileTypeMap.put("52494646d07d60074156", "avi");
        fileTypeMap.put("4d546864000000060001", "mid"); // MIDI (mid)
        fileTypeMap.put("504b0304140000000800", "zip");
        fileTypeMap.put("526172211a0700cf9073", "rar");
        fileTypeMap.put("235468697320636f6e66", "ini");
        fileTypeMap.put("4d616e69666573742d56", "mf");// MF文件
        fileTypeMap.put("3c3f786d6c2076657273", "xml");// xml文件
        fileTypeMap.put("1f8b0800000000000000", "gz");// gz文件
        fileTypeMap.put("504b0304140006000800", "docx");// docx文件
        fileTypeMap.put("d0cf11e0a1b11ae10000", "wps");// WPS文字wps、表格et、演示dps都是一样的

        fileTypeMap.put("6D6F6F76", "mov"); // Quicktime (mov)
        fileTypeMap.put("FF575043", "wpd"); // WordPerfect (wpd)
        fileTypeMap.put("CFAD12FEC5FD746F", "dbx"); // Outlook Express (dbx)
        fileTypeMap.put("2142444E", "pst"); // Outlook (pst)
        fileTypeMap.put("AC9EBD8F", "qdf"); // Quicken (qdf)
        fileTypeMap.put("E3828596", "pwl"); // Windows Password (pwl)
        fileTypeMap.put("2E7261FD", "ram"); // Real Audio (ram)
        fileTypeMap.put("null", null); // null
        return fileTypeMap;
    }
3、根据返回的类型决定是否符合要求
          //增加 magic number校验
            String htmlFileType = MagicNumberIdentifier.getHtmlFileType(uploadfile.getBytes());
            if (Objects.isNull(htmlFileType)) {
                log.info("feign上传HTML,该文件内容中的文件格式为空,不符合要求...");
            }
4、根据结果决定是否执行下一步,至此,校验的内容结束,完整代码如下:
package common.tool;


import org.apache.commons.lang.StringUtils;

import java.util.*;

/**
 * 识别文件类型工具类
 */
public class MagicNumberIdentifier {
    //常用允许上传的类型
    public static List<String> allowFileTypeList = Arrays.asList("jpg", "jpeg", "png", "gif", "bmp", "tiff", "tif", "pdf", "doc", "docx", "xls", "xlsx", "xlsm","apk");
    //只有HTML上传的类型
    public static List<String> allowHtmlTypeList = Arrays.asList("html", "htm");

    /**
     * 常用文件格式
     */
    public static Map<String, String> getAllFileType() {
        Map<String, String> fileTypeMap = new HashMap<>();
        fileTypeMap.put("ffd8ffe000104a464946", "jpg"); // JPEG (jpg)
        fileTypeMap.put("89504e470d0a1a0a0000", "png"); // PNG (png)
        fileTypeMap.put("47494638396126026f01", "gif"); // GIF (gif)
        fileTypeMap.put("49492a00227105008037", "tif"); // TIFF (tif)
        fileTypeMap.put("424d228c010000000000", "bmp"); // 16色位图(bmp)
        fileTypeMap.put("424d8240090000000000", "bmp"); // 24位位图(bmp)
        fileTypeMap.put("424d8e1b030000000000", "bmp"); // 256色位图(bmp)
        fileTypeMap.put("41433130313500000000", "dwg"); // CAD (dwg)

        fileTypeMap.put("7b5c727466315c616e73", "rtf"); // Rich Text Format (rtf)
        fileTypeMap.put("38425053000100000000", "psd"); // Photoshop (psd)
        fileTypeMap.put("46726f6d3a203d3f6762", "eml"); // Email [Outlook Express 6] (eml)
        fileTypeMap.put("d0cf11e0a1b11ae10000", "doc"); // MS Excel 注意:word、msi 和 excel的文件头一样
        fileTypeMap.put("d0cf11e0a1b11ae10000", "vsd"); // Visio 绘图
        fileTypeMap.put("5374616E64617264204A", "mdb"); // MS Access (mdb)
        fileTypeMap.put("252150532D41646F6265", "ps");
        fileTypeMap.put("255044462d312e350d0a", "pdf"); // Adobe Acrobat (pdf)
        fileTypeMap.put("2e524d46000000120001", "rmvb"); // rmvb/rm相同
        fileTypeMap.put("464c5601050000000900", "flv"); // flv与f4v相同
        fileTypeMap.put("00000020667479706d70", "mp4");
        fileTypeMap.put("49443303000000002176", "mp3");
        fileTypeMap.put("000001ba210001000180", "mpg"); //
        fileTypeMap.put("3026b2758e66cf11a6d9", "wmv"); // wmv与asf相同
        fileTypeMap.put("52494646e27807005741", "wav"); // Wave (wav)
        fileTypeMap.put("52494646d07d60074156", "avi");
        fileTypeMap.put("4d546864000000060001", "mid"); // MIDI (mid)
        fileTypeMap.put("504b0304140000000800", "zip");
        fileTypeMap.put("526172211a0700cf9073", "rar");
        fileTypeMap.put("235468697320636f6e66", "ini");
        fileTypeMap.put("4d616e69666573742d56", "mf");// MF文件
        fileTypeMap.put("3c3f786d6c2076657273", "xml");// xml文件
        fileTypeMap.put("1f8b0800000000000000", "gz");// gz文件
        fileTypeMap.put("504b0304140006000800", "docx");// docx文件
        fileTypeMap.put("d0cf11e0a1b11ae10000", "wps");// WPS文字wps、表格et、演示dps都是一样的

        fileTypeMap.put("6D6F6F76", "mov"); // Quicktime (mov)
        fileTypeMap.put("FF575043", "wpd"); // WordPerfect (wpd)
        fileTypeMap.put("CFAD12FEC5FD746F", "dbx"); // Outlook Express (dbx)
        fileTypeMap.put("2142444E", "pst"); // Outlook (pst)
        fileTypeMap.put("AC9EBD8F", "qdf"); // Quicken (qdf)
        fileTypeMap.put("E3828596", "pwl"); // Windows Password (pwl)
        fileTypeMap.put("2E7261FD", "ram"); // Real Audio (ram)
        fileTypeMap.put("null", null); // null
        return fileTypeMap;
    }

    /**
     * 获取HTML常见的文件类型
     *
     * @return HTML常见的文件类型
     */
    public static Map<String, String> getHtmlFileType() {
        Map<String, String> fileTypeMap = new HashMap<>();
        fileTypeMap.put("3c21444f435459504520", "html"); // HTML (html)
        fileTypeMap.put("3c21646f637479706520", "htm"); // HTM (htm)
        fileTypeMap.put("null", null); // null
        return fileTypeMap;
    }

    /**
     * 得到上传文件的文件头
     *
     * @param src 文件
     * @return 字符串
     */
    public static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (byte b : src) {
            int v = b & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

    /**
     * 根据制定文件的文件头判断其文件类型
     *
     * @param byteArray 文件转换后的byte数组
     * @return 文件类型
     */
    public static String getCommonFileType(byte[] byteArray) {
        String res = null;
        String fileCode = bytesToHexString(byteArray);
        if (StringUtils.isBlank(fileCode)) {
            return null;
        }
        Map<String, String> allFileType = getAllFileType();
        for (String key : allFileType.keySet()) {
            // 验证前5个字符比较
            if (key.toLowerCase().startsWith(fileCode.toLowerCase().substring(0, 5))
                    || fileCode.toLowerCase().substring(0, 5).startsWith(key.toLowerCase())) {
                res = allFileType.get(key);
                break;
            }
        }
        return res;
    }

    /**
     * 获取html的文件类型
     *
     * @param byteArray html文件转换后的byte数组
     * @return 文件类型
     */
    public static String getHtmlFileType(byte[] byteArray) {
        String res = null;
        String fileCode = bytesToHexString(byteArray);
        if (StringUtils.isBlank(fileCode)) {
            return null;
        }
        Map<String, String> htmlFileType = getHtmlFileType();
        for (String key : htmlFileType.keySet()) {
            // 验证前5个字符比较
            if (key.toLowerCase().startsWith(fileCode.toLowerCase().substring(0, 5))
                    || fileCode.toLowerCase().substring(0, 5).startsWith(key.toLowerCase())) {
                res = htmlFileType.get(key);
                break;
            }
        }
        return res;
    }


    /**
     * 获取文件名后缀
     *
     * @param filePath 文件名
     * @return 后缀名
     */
    public static String getFileExtension(String filePath) {
        // 使用lastIndexOf方法找到"."的位置
        int lastDotIndex = filePath.lastIndexOf(".");
        // 如果"."存在并且不在字符串的开始位置(即文件名包含点)
        if (lastDotIndex > 0 && lastDotIndex < filePath.length() - 1) {
            // 获取从"."开始到字符串结束的部分,即文件后缀
            return filePath.substring(lastDotIndex + 1);
        } else {
            // 如果没有找到"."或者"."在字符串的开始位置,说明没有文件后缀
            return "";
        }
    }
}

public class Main {
    public static void main(String[] args) throws IOException, MagicMatchNotFoundException, MagicException, MagicParseException {
        String fileExtension = MagicNumberIdentifier.getFileExtension(fileName);
        List<String> allowHtmlTypeList = MagicNumberIdentifier.allowHtmlTypeList;
        if (!allowHtmlTypeList.contains(fileExtension)) {
            log.info("feign上传HTML,该文件后缀的文件格式,不符合要求...");
            return;
        }
        //增加 magic number校验
        String htmlFileType = MagicNumberIdentifier.getHtmlFileType(uploadfile.getBytes());
        if (Objects.isNull(htmlFileType)) {
            log.info("feign上传HTML,该文件内容中的文件格式为空,不符合要求...");
            return;
        }
    }
}
5、更全的类型,见以下连接

之后补充一下,找不见了…,下次见

  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用Java IO中的FileInputStream类读取文件的前几个字节,根据这些字节判断文件类型。常见的文件类型判断方法如下: 1. 判断文件类型的魔数(Magic Number):魔数是一种特殊的标识符,通常在文件的开头几个字节中存储。不同类型的文件有不同的魔数,可以通过比较文件的前几个字节来判断文件类型。 2. 判断文件类型的扩展名:根据文件名的后缀来判断文件类型,但是这种方法并不可靠,因为文件名可能被修改或者没有扩展名。 以下是一个简单的示例代码,可以根据文件的前几个字节判断文件类型: ```java import java.io.*; public class FileTypeDetector { public static String detectFileType(InputStream inputStream) throws IOException { byte[] header = new byte[8]; inputStream.read(header, 0, 8); String fileType = null; if (isJPEG(header)) { fileType = "JPEG"; } else if (isPNG(header)) { fileType = "PNG"; } else if (isGIF(header)) { fileType = "GIF"; } else if (isBMP(header)) { fileType = "BMP"; } else { fileType = "Unknown"; } return fileType; } private static boolean isJPEG(byte[] header) { if (header[0] == (byte)0xFF && header[1] == (byte)0xD8) { return true; } return false; } private static boolean isPNG(byte[] header) { if (header[0] == (byte)0x89 && header[1] == (byte)0x50 && header[2] == (byte)0x4E && header[3] == (byte)0x47 && header[4] == (byte)0x0D && header[5] == (byte)0x0A && header[6] == (byte)0x1A && header[7] == (byte)0x0A) { return true; } return false; } private static boolean isGIF(byte[] header) { if (header[0] == (byte)0x47 && header[1] == (byte)0x49 && header[2] == (byte)0x46 && header[3] == (byte)0x38 && (header[4] == (byte)0x37 || header[4] == (byte)0x39) && header[5] == (byte)0x61) { return true; } return false; } private static boolean isBMP(byte[] header) { if (header[0] == (byte)0x42 && header[1] == (byte)0x4D) { return true; } return false; } } ``` 这个示例代码实现了根据文件的前几个字节判断JPEG、PNG、GIF和BMP文件类型。在使用时,只需要将文件的InputStream传入detectFileType方法即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值