一般,在WEB系統中,上傳文件時都須要作文件的類型校驗,大體有以下幾種方法:html
1. 經過後綴名,如exe,jpg,bmp,rar,zip等等。java
2. 經過讀取文件,獲取文件的Content-type來判斷。api
3. 經過讀取文件流,根據文件流中特定的一些字節標識來區分不一樣類型的文件。安全
4. 如果圖片,則經過縮放來判斷,能夠縮放的爲圖片,不能夠的則不是。app
然而,在安全性較高的業務場景中,1,2兩種方法的校驗會被輕易繞過。性能
1. 僞造後綴名,如圖片的,很是容易修改。ui
2. 僞造文件的Content-type,這個稍微複雜點,爲了直觀,截圖以下:spa
![](http://static.javashuo.com/static/loading.gif)
3.較安全,可是要讀取文件,並有16進制轉換等操做,性能稍差,但能知足必定條件下對安全的要求,因此建議使用。.net
可是文件頭的信息也能夠僞造,截圖以下,對於圖片能夠採用圖片縮放或者獲取圖片寬高的方法避免僞造頭信息漏洞。excel
![](http://static.javashuo.com/static/loading.gif)
被假裝成gif的惡意圖片文件
對應的Java代碼以下:
[java] view plain copy
print?
- package apistudy;
-
- 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.HashMap;
- import java.util.Iterator;
- import java.util.Map;
- import java.util.Map.Entry;
- import javax.imageio.ImageIO;
- import javax.imageio.ImageReader;
- import javax.imageio.stream.ImageInputStream;
-
- public class FileTypeTest
- {
- public final static Map<String, String> FILE_TYPE_MAP = new HashMap<String, String>();
-
- private FileTypeTest(){}
- static{
- getAllFileType(); //初始化文件類型信息
- }
-
- /**
- * Created on 2010-7-1
- * <p>Discription:[getAllFileType,常見文件頭信息]</p>
- * @author:[shixing_11@sina.com]
- */
- private static void getAllFileType()
- {
- FILE_TYPE_MAP.put("jpg", "FFD8FF"); //JPEG (jpg)
- FILE_TYPE_MAP.put("png", "89504E47"); //PNG (png)
- FILE_TYPE_MAP.put("gif", "47494638"); //GIF (gif)
- FILE_TYPE_MAP.put("tif", "49492A00"); //TIFF (tif)
- FILE_TYPE_MAP.put("bmp", "424D"); //Windows Bitmap (bmp)
- FILE_TYPE_MAP.put("dwg", "41433130"); //CAD (dwg)
- FILE_TYPE_MAP.put("html", "68746D6C3E"); //HTML (html)
- FILE_TYPE_MAP.put("rtf", "7B5C727466"); //Rich Text Format (rtf)
- FILE_TYPE_MAP.put("xml", "3C3F786D6C");
- FILE_TYPE_MAP.put("zip", "504B0304");
- FILE_TYPE_MAP.put("rar", "52617221");
- FILE_TYPE_MAP.put("psd", "38425053"); //Photoshop (psd)
- FILE_TYPE_MAP.put("eml", "44656C69766572792D646174653A"); //Email [thorough only] (eml)
- FILE_TYPE_MAP.put("dbx", "CFAD12FEC5FD746F"); //Outlook Express (dbx)
- FILE_TYPE_MAP.put("pst", "2142444E"); //Outlook (pst)
- FILE_TYPE_MAP.put("xls", "D0CF11E0"); //MS Word
- FILE_TYPE_MAP.put("doc", "D0CF11E0"); //MS Excel 注意:word 和 excel的文件頭同樣
- FILE_TYPE_MAP.put("mdb", "5374616E64617264204A"); //MS Access (mdb)
- FILE_TYPE_MAP.put("wpd", "FF575043"); //WordPerfect (wpd)
- FILE_TYPE_MAP.put("eps", "252150532D41646F6265");
- FILE_TYPE_MAP.put("ps", "252150532D41646F6265");
- FILE_TYPE_MAP.put("pdf", "255044462D312E"); //Adobe Acrobat (pdf)
- FILE_TYPE_MAP.put("qdf", "AC9EBD8F"); //Quicken (qdf)
- FILE_TYPE_MAP.put("pwl", "E3828596"); //Windows Password (pwl)
- FILE_TYPE_MAP.put("wav", "57415645"); //Wave (wav)
- FILE_TYPE_MAP.put("avi", "41564920");
- FILE_TYPE_MAP.put("ram", "2E7261FD"); //Real Audio (ram)
- FILE_TYPE_MAP.put("rm", "2E524D46"); //Real Media (rm)
- FILE_TYPE_MAP.put("mpg", "000001BA"); //
- FILE_TYPE_MAP.put("mov", "6D6F6F76"); //Quicktime (mov)
- FILE_TYPE_MAP.put("asf", "3026B2758E66CF11"); //Windows Media (asf)
- FILE_TYPE_MAP.put("mid", "4D546864"); //MIDI (mid)
- }
-
- public static void main(String[] args) throws Exception
- {
- File f = new File("c://aaa.gif");
- if (f.exists())
- {
- String filetype1 = getImageFileType(f);
- System.out.println(filetype1);
- String filetype2 = getFileByFile(f);
- System.out.println(filetype2);
- }
- }
-
- /**
- * Created on 2010-7-1
- * <p>Discription:[getImageFileType,獲取圖片文件實際類型,若不是圖片則返回null]</p>
- * @param File
- * @return fileType
- * @author:[shixing_11@sina.com]
- */
- public final static String getImageFileType(File f)
- {
- if (isImage(f))
- {
- try
- {
- ImageInputStream iis = ImageIO.createImageInputStream(f);
- 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;
- }
-
- /**
- * Created on 2010-7-1
- * <p>Discription:[getFileByFile,獲取文件類型,包括圖片,若格式不是已配置的,則返回null]</p>
- * @param file
- * @return fileType
- * @author:[shixing_11@sina.com]
- */
- 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;
- }
-
- /**
- * Created on 2010-7-1
- * <p>Discription:[getFileTypeByStream]</p>
- * @param b
- * @return fileType
- * @author:[shixing_11@sina.com]
- */
- public final static String getFileTypeByStream(byte[] b)
- {
- String filetypeHex = String.valueOf(getFileHexString(b));
- Iterator<Entry<String, String>> entryiterator = FILE_TYPE_MAP.entrySet().iterator();
- while (entryiterator.hasNext()) {
- Entry<String,String> entry = entryiterator.next();
- String fileTypeHexValue = entry.getValue();
- if (filetypeHex.toUpperCase().startsWith(fileTypeHexValue)) {
- return entry.getKey();
- }
- }
- return null;
- }
-
- /**
- * Created on 2010-7-2
- * <p>Discription:[isImage,判斷文件是否爲圖片]</p>
- * @param file
- * @return true 是 | false 否
- * @author:[shixing_11@sina.com]
- */
- 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;
- }
-
- /**
- * Created on 2010-7-1
- * <p>Discription:[getFileHexString]</p>
- * @param b
- * @return fileTypeHex
- * @author:[shixing_11@sina.com]
- */
- public final static String getFileHexString(byte[] b)
- {
- StringBuilder stringBuilder = 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)
- {
- stringBuilder.append(0);
- }
- stringBuilder.append(hv);
- }
- return stringBuilder.toString();
- }
- }
這樣,無論是傳入的文件有後綴名,仍是無後綴名,或者修改了後綴名,真正獲取到的纔是該文件的實際類型,這樣避免了一些想經過修改後綴名或者Content-type信息來攻擊的因素。可是性能與安全永遠是沒法同時完美的,安全的同時付出了讀取文件的代價。本人建議可採用後綴名與讀取文件的方式結合校驗,畢竟攻擊是少數,後綴名的校驗能排除大多數用戶,在後綴名獲取不到時再經過獲取文件真實類型校驗,這樣來適當提升性能。