• Java根据字节数据判断文件类型


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

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

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

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

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

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

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

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

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

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

     

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

    对应的Java代码如下:

    1. package apistudy;    
    2.     
    3. import java.awt.image.BufferedImage;  
    4. import java.io.File;  
    5. import java.io.FileInputStream;  
    6. import java.io.FileNotFoundException;  
    7. import java.io.IOException;  
    8. import java.io.InputStream;  
    9. import java.util.HashMap;  
    10. import java.util.Iterator;  
    11. import java.util.Map;  
    12. import java.util.Map.Entry;  
    13. import javax.imageio.ImageIO;  
    14. import javax.imageio.ImageReader;  
    15. import javax.imageio.stream.ImageInputStream;  
    16.     
    17. public class FileTypeTest    
    18. {    
    19.     public final static Map<String, String> FILE_TYPE_MAP = new HashMap<String, String>();    
    20.         
    21.     private FileTypeTest(){}    
    22.     static{    
    23.         getAllFileType();  //初始化文件类型信息    
    24.     }    
    25.         
    26.     /**  
    27.      * Created on 2010-7-1   
    28.      * <p>Discription:[getAllFileType,常见文件头信息]</p>  
    29.      * @author:[shixing_11@sina.com]  
    30.      */    
    31.     private static void getAllFileType()    
    32.     {    
    33.         FILE_TYPE_MAP.put("jpg", "FFD8FF"); //JPEG (jpg)    
    34.         FILE_TYPE_MAP.put("png", "89504E47");  //PNG (png)    
    35.         FILE_TYPE_MAP.put("gif", "47494638");  //GIF (gif)    
    36.         FILE_TYPE_MAP.put("tif", "49492A00");  //TIFF (tif)    
    37.         FILE_TYPE_MAP.put("bmp", "424D"); //Windows Bitmap (bmp)    
    38.         FILE_TYPE_MAP.put("dwg", "41433130"); //CAD (dwg)    
    39.         FILE_TYPE_MAP.put("html", "68746D6C3E");  //HTML (html)    
    40.         FILE_TYPE_MAP.put("rtf", "7B5C727466");  //Rich Text Format (rtf)    
    41.         FILE_TYPE_MAP.put("xml", "3C3F786D6C");    
    42.         FILE_TYPE_MAP.put("zip", "504B0304");    
    43.         FILE_TYPE_MAP.put("rar", "52617221");    
    44.         FILE_TYPE_MAP.put("psd", "38425053");  //Photoshop (psd)    
    45.         FILE_TYPE_MAP.put("eml", "44656C69766572792D646174653A");  //Email [thorough only] (eml)    
    46.         FILE_TYPE_MAP.put("dbx", "CFAD12FEC5FD746F");  //Outlook Express (dbx)    
    47.         FILE_TYPE_MAP.put("pst", "2142444E");  //Outlook (pst)    
    48.         FILE_TYPE_MAP.put("xls", "D0CF11E0");  //MS Word    
    49.         FILE_TYPE_MAP.put("doc", "D0CF11E0");  //MS Excel 注意:word 和 excel的文件头一样    
    50.         FILE_TYPE_MAP.put("mdb", "5374616E64617264204A");  //MS Access (mdb)    
    51.         FILE_TYPE_MAP.put("wpd", "FF575043"); //WordPerfect (wpd)     
    52.         FILE_TYPE_MAP.put("eps", "252150532D41646F6265");    
    53.         FILE_TYPE_MAP.put("ps", "252150532D41646F6265");    
    54.         FILE_TYPE_MAP.put("pdf", "255044462D312E");  //Adobe Acrobat (pdf)    
    55.         FILE_TYPE_MAP.put("qdf", "AC9EBD8F");  //Quicken (qdf)    
    56.         FILE_TYPE_MAP.put("pwl", "E3828596");  //Windows Password (pwl)    
    57.         FILE_TYPE_MAP.put("wav", "57415645");  //Wave (wav)    
    58.         FILE_TYPE_MAP.put("avi", "41564920");    
    59.         FILE_TYPE_MAP.put("ram", "2E7261FD");  //Real Audio (ram)    
    60.         FILE_TYPE_MAP.put("rm", "2E524D46");  //Real Media (rm)    
    61.         FILE_TYPE_MAP.put("mpg", "000001BA");  //    
    62.         FILE_TYPE_MAP.put("mov", "6D6F6F76");  //Quicktime (mov)    
    63.         FILE_TYPE_MAP.put("asf", "3026B2758E66CF11"); //Windows Media (asf)    
    64.         FILE_TYPE_MAP.put("mid", "4D546864");  //MIDI (mid)    
    65.     }    
    66.     
    67.     public static void main(String[] args) throws Exception    
    68.     {    
    69.         File f = new File("c://aaa.gif");    
    70.         if (f.exists())    
    71.         {    
    72.             String filetype1 = getImageFileType(f);    
    73.             System.out.println(filetype1);    
    74.             String filetype2 = getFileByFile(f);    
    75.             System.out.println(filetype2);    
    76.         }    
    77.     }    
    78.     
    79.     /**  
    80.      * Created on 2010-7-1   
    81.      * <p>Discription:[getImageFileType,获取图片文件实际类型,若不是图片则返回null]</p>  
    82.      * @param File  
    83.      * @return fileType  
    84.      * @author:[shixing_11@sina.com]  
    85.      */    
    86.     public final static String getImageFileType(File f)    
    87.     {    
    88.         if (isImage(f))  
    89.         {  
    90.             try  
    91.             {  
    92.                 ImageInputStream iis = ImageIO.createImageInputStream(f);  
    93.                 Iterator<ImageReader> iter = ImageIO.getImageReaders(iis);  
    94.                 if (!iter.hasNext())  
    95.                 {  
    96.                     return null;  
    97.                 }  
    98.                 ImageReader reader = iter.next();  
    99.                 iis.close();  
    100.                 return reader.getFormatName();  
    101.             }  
    102.             catch (IOException e)  
    103.             {  
    104.                 return null;  
    105.             }  
    106.             catch (Exception e)  
    107.             {  
    108.                 return null;  
    109.             }  
    110.         }  
    111.         return null;  
    112.     }    
    113.     
    114.     /**  
    115.      * Created on 2010-7-1   
    116.      * <p>Discription:[getFileByFile,获取文件类型,包括图片,若格式不是已配置的,则返回null]</p>  
    117.      * @param file  
    118.      * @return fileType  
    119.      * @author:[shixing_11@sina.com]  
    120.      */    
    121.     public final static String getFileByFile(File file)    
    122.     {    
    123.         String filetype = null;    
    124.         byte[] b = new byte[50];    
    125.         try    
    126.         {    
    127.             InputStream is = new FileInputStream(file);    
    128.             is.read(b);    
    129.             filetype = getFileTypeByStream(b);    
    130.             is.close();    
    131.         }    
    132.         catch (FileNotFoundException e)    
    133.         {    
    134.             e.printStackTrace();    
    135.         }    
    136.         catch (IOException e)    
    137.         {    
    138.             e.printStackTrace();    
    139.         }    
    140.         return filetype;    
    141.     }    
    142.         
    143.     /**  
    144.      * Created on 2010-7-1   
    145.      * <p>Discription:[getFileTypeByStream]</p>  
    146.      * @param b  
    147.      * @return fileType  
    148.      * @author:[shixing_11@sina.com]  
    149.      */    
    150.     public final static String getFileTypeByStream(byte[] b)    
    151.     {    
    152.         String filetypeHex = String.valueOf(getFileHexString(b));    
    153.         Iterator<Entry<String, String>> entryiterator = FILE_TYPE_MAP.entrySet().iterator();    
    154.         while (entryiterator.hasNext()) {    
    155.             Entry<String,String> entry =  entryiterator.next();    
    156.             String fileTypeHexValue = entry.getValue();    
    157.             if (filetypeHex.toUpperCase().startsWith(fileTypeHexValue)) {    
    158.                 return entry.getKey();    
    159.             }    
    160.         }    
    161.         return null;    
    162.     }    
    163.         
    164.     /** 
    165.      * Created on 2010-7-2  
    166.      * <p>Discription:[isImage,判断文件是否为图片]</p> 
    167.      * @param file 
    168.      * @return true 是 | false 否 
    169.      * @author:[shixing_11@sina.com] 
    170.      */  
    171.     public static final boolean isImage(File file){  
    172.         boolean flag = false;  
    173.         try  
    174.         {  
    175.             BufferedImage bufreader = ImageIO.read(file);  
    176.             int width = bufreader.getWidth();  
    177.             int height = bufreader.getHeight();  
    178.             if(width==0 || height==0){  
    179.                 flag = false;  
    180.             }else {  
    181.                 flag = true;  
    182.             }  
    183.         }  
    184.         catch (IOException e)  
    185.         {  
    186.             flag = false;  
    187.         }catch (Exception e) {  
    188.             flag = false;  
    189.         }  
    190.         return flag;  
    191.     }  
    192.       
    193.     /**  
    194.      * Created on 2010-7-1   
    195.      * <p>Discription:[getFileHexString]</p>  
    196.      * @param b  
    197.      * @return fileTypeHex  
    198.      * @author:[shixing_11@sina.com]  
    199.      */    
    200.     public final static String getFileHexString(byte[] b)    
    201.     {    
    202.         StringBuilder stringBuilder = new StringBuilder();    
    203.         if (b == null || b.length <= 0)    
    204.         {    
    205.             return null;    
    206.         }    
    207.         for (int i = 0; i < b.length; i++)    
    208.         {    
    209.             int v = b[i] & 0xFF;    
    210.             String hv = Integer.toHexString(v);    
    211.             if (hv.length() < 2)    
    212.             {    
    213.                 stringBuilder.append(0);    
    214.             }    
    215.             stringBuilder.append(hv);    
    216.         }    
    217.         return stringBuilder.toString();    
    218.     }    
    219. }  

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

  • 相关阅读:
    re模块
    Docker的使用
    flask中请求勾子
    flask中的蓝图实现模块化的应用
    HTTP中常见的各种状态码详解及解决方案
    git快速入门
    2st week blog 1
    web个人介绍
    CentOS7下shell脚本实现限定范围类的随机数
    CentOS7下shell脚本大小比较
  • 原文地址:https://www.cnblogs.com/jiangyang/p/4881586.html
Copyright © 2020-2023  润新知