本文出自馮立彬的部落格,原地址:http://www.fenglibin.com/use_java_to_check_images_type_and_security.html
一、通常情況下,驗證一個檔案是否圖片,可以通過以下三種方式:
1)、判斷檔案的副檔名是否是要求的圖片副檔名
這種判斷是用得比較多的一種方式,不過這種方式非常的不妥,別人稍微的把一個不是圖片的檔案的副檔名修改為圖片的副檔名,就繞開了你的這種校正,如果這上傳的檔案是shell、php或者jsp,那你的網站基本上可以說就在別人的手裡面了。
不過這種判斷方式也不是完全沒有用,我們可以把它放在判斷圖片的最外層,如果一個檔案連副檔名都不是我們所要求的圖片副檔名,那就根本不用後面的內容格式檢查了,從一定程度上說,對減少伺服器的壓力還是有一定的協助,否則所有的檔案都等上傳完後成後再通過伺服器去判斷,那會在一定程度上浪費器資源的。
2)、根據檔案的前面幾個位元組,即常說的魔術數字進行判斷,不同檔案類型的開頭幾個位元組,可以查看我的另外一篇專站介紹:表示不同檔案類型的魔術數字。
但是這種判斷方式也是非常不靠譜的,因為他只能夠驗證檔案的前面幾個位元組,如此時有人把一個可執行檔PHP檔案的副檔名修改為PNG,然後再在前面補上”89 50″兩個位元組,就又繞開了這種驗證方式。
以下是一段通過JAVA代碼擷取檔案前面兩個位元組的樣本程式:
[java]
view plaincopyprint?
- 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));
- }
- }
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代碼:
[java]
view plaincopyprint?
- /**
- * 通過讀取檔案並擷取其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;
- }
- }
/** * 通過讀取檔案並擷取其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實現:
[java]
view plaincopyprint?
- /**
- * 添加圖片浮水印
- *
- * @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);
- }
/** * 添加圖片浮水印 * * @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); }
通過以上幾種方式,應該可以避免絕大部份圖片中帶惡意代碼的安全問題,不過由於我個人的才疏學淺,可能有沒有考慮周全的地方,還請各位不吝指教了。