一、通常情況下,驗證一個文件是否圖片,可以通過以下三種方式:
1)、判斷文件的擴展名是否是要求的圖片擴展名
這種判斷是用得比較多的一種方式,不過這種方式非常的不妥,別人稍微的把一個不是圖片的文件的擴展名修改為圖片的擴展名,就繞開了你的這種校驗,如果這上傳的文件是shell、php或者jsp,那你的網站基本上可以說就在別人的手里面了。
不過這種判斷方式也不是完全沒有用,我們可以把它放在判斷圖片的最外層,如果一個文件連擴展名都不是我們所要求的圖片擴展名,那就根本不用后面的內容格式檢查了,從一定程度上說,對減少服務器的壓力還是有一定的幫助,否則所有的文件都等上傳完后成后再通過服務器去判斷,那會在一定程度上浪費器資源的。
2)、根據文件的前面幾個字節,即常說的魔術數字進行判斷,不同文件類型的開頭幾個字節,可以查看我的另外一篇專站介紹:表示不同文件類型的魔術數字。
但是這種判斷方式也是非常不靠譜的,因為他只能夠驗證文件的前面幾個字節,如此時有人把一個可執行的PHP文件的擴展名修改為PNG,然后再在前面補上”89 50″兩個字節,就又繞開了這種驗證方式。
以下是一段通過JAVA代碼獲取文件前面兩個字節的示例程序:
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; public class ImageTypeCheck { public static String bytesToHexString(byte[] src) { StringBuilder stringBuilder = new StringBuilder(); if (src == null || src.length <= 0) { return null; } for (int i = 0; i < src.length; i++) { int v = src[i] & 0xFF; String hv = Integer.toHexString(v); if (hv.length() < 2) { stringBuilder.append(0); } stringBuilder.append(hv); } return stringBuilder.toString(); } public static void main(String[] args) throws IOException { String imagePath = "c:/favicon.png"; File image = new File(imagePath); InputStream is = new FileInputStream(image); byte[] bt = new byte[2]; is.read(bt); System.out.println(bytesToHexString(bt)); } }
不過這種判斷方式和判斷擴展名一樣,也不是完全沒有用,至少可以在前期在簡單的檢查,為進入下一步檢查做鋪墊。
3)、獲取圖片的寬高屬性
如果能夠正常的獲取到一張圖片的寬高屬性,那肯定這是一張圖片,因為非圖片文件我們是獲取不到它的寬高屬性的,以下是用於獲取根據是否可以獲取到圖片寬高屬性來判斷這是否一張圖片的JAVA代碼:
/** * 通過讀取文件並獲取其width及height的方式,來判斷判斷當前文件是否圖片,這是一種非常簡單的方式。 * * @param imageFile * @return */ public static boolean isImage(File imageFile) { if (!imageFile.exists()) { return false; } Image img = null; try { img = ImageIO.read(imageFile); if (img == null || img.getWidth(null) <= 0 || img.getHeight(null) <= 0) { return false; } return true; } catch (Exception e) { return false; } finally { img = null; } }
二、圖片文件的安全檢查處理
好了,我們終於判斷出一個文件是否圖片了,可是如果是在一個可以正常瀏覽的圖片文件中加入一些非法的代碼呢:
這就是在一張正常的圖片末尾增加的一些iframe代碼,我曾經嘗試過單獨打開這張圖片,也將這張圖片放於網頁上打開,雖然這樣都不會被執行,但並不代表插入其它的代碼也並不會執行,殺毒軟件(如AVAST)對這種修改是會報為病毒的。
那我們要如何預防這種東西,即可以正常打開,又具有正確的圖片文件擴展名,還可以獲取到它的寬高屬性?呵,我們這個時候可以對這個圖片進地重寫,給它增加水印或者對它進行resize操作,這樣新生成的圖片就不會再包含這樣的惡意代碼了,以下是一個增加水印的JAVA實現:
/** * 添加圖片水印 * * @param srcImg 目標圖片路徑,如:C:\\kutuku.jpg * @param waterImg 水印圖片路徑,如:C:\\kutuku.png * @param x 水印圖片距離目標圖片左側的偏移量,如果x<0, 則在正中間 * @param y 水印圖片距離目標圖片上側的偏移量,如果y<0, 則在正中間 * @param alpha 透明度(0.0 -- 1.0, 0.0為完全透明,1.0為完全不透明) * @throws IOException */ public final static void addWaterMark(String srcImg, String waterImg, int x, int y, float alpha) throws IOException { // 加載目標圖片 File file = new File(srcImg); String ext = srcImg.substring(srcImg.lastIndexOf(".") + 1); Image image = ImageIO.read(file); int width = image.getWidth(null); int height = image.getHeight(null); // 將目標圖片加載到內存。 BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g = bufferedImage.createGraphics(); g.drawImage(image, 0, 0, width, height, null); // 加載水印圖片。 Image waterImage = ImageIO.read(new File(waterImg)); int width_1 = waterImage.getWidth(null); int height_1 = waterImage.getHeight(null); // 設置水印圖片的透明度。 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)); // 設置水印圖片的位置。 int widthDiff = width - width_1; int heightDiff = height - height_1; if (x < 0) { x = widthDiff / 2; } else if (x > widthDiff) { x = widthDiff; } if (y < 0) { y = heightDiff / 2; } else if (y > heightDiff) { y = heightDiff; } // 將水印圖片“畫”在原有的圖片的制定位置。 g.drawImage(waterImage, x, y, width_1, height_1, null); // 關閉畫筆。 g.dispose(); // 保存目標圖片。 ImageIO.write(bufferedImage, ext, file); }
通過以上幾種方式,應該可以避免絕大部份圖片中帶惡意代碼的安全問題,如有其他方式,望告知。