1. 程式人生 > >如何更好的判斷系統上傳檔案是指定檔案型別--檔案魔術數字

如何更好的判斷系統上傳檔案是指定檔案型別--檔案魔術數字

理論介紹

這裡所說的表示不同檔案型別的魔術數字,指定是檔案的最開頭的幾個用於唯一區別其它檔案型別的位元組,有了這些魔術數字,我們就可以很方便的區別不同的檔案,這也使得程式設計變得更加容易,因為我減少了我們用於區別一個檔案的檔案型別所要花費的時間。
比如,一個JPEG檔案,它開頭的一些位元組可能是類似這樣的”ffd8 ffe0 0010 4a46 4946 0001 0101 0047 ……JFIF…..G“,這裡”ffd8“就表示了這個檔案是一個JPEG型別的檔案,”ffe0“表示這是JFIF型別結構。
以下例出的是一些我們常見的檔案型別,以及它用於判斷這種檔案的型別的幾個開始位元組及所對尖的ASCII數字:

圖片檔案

檔案型別 副檔名 16進位制數字,xx這裡表示變數 Ascii數字 . = 不是Ascii字元
Bitmap format .bmp 42 4d BM
FITS format .fits 53 49 4d 50 4c 45 SIMPLE
GIF format .gif 47 49 46 38 GIF8
Graphics Kernel System .gks 47 4b 53 4d GKSM
IRIS rgb format .rgb 01 da ..
ITC (CMU WM) format .itc f1 00 40 bb ….
JPEG File Interchange Format .jpg ff d8 ff e0 ….
NIFF (Navy TIFF) .nif 49 49 4e 31 IIN1
PM format .pm 56 49 45 57 VIEW
PNG format .png 89 50 4e 47 .PNG
Postscript format .[e]ps 25 21 %!
Sun Rasterfile .ras 59 a6 6a 95 Y.j.
Targa format .tga xx xx xx
TIFF format (Motorola – big endian) .tif 4d 4d 00 2a MM.*
TIFF format (Intel – little endian) .tif 49 49 2a 00 II*.
X11 Bitmap format .xbm xx xx
XCF Gimp file structure .xcf 67 69 6d 70 20 78 63 66 20 76 gimp xcf
Xfig format .fig 23 46 49 47 #FIG
XPM format .xpm 2f 2a 20 58 50 4d 20 2a 2f /* XPM */

壓縮檔案

檔案型別 副檔名 16進位制數字 xx這裡表示變數 Ascii數字. = 不是Ascii字元
Bzip .bz 42 5a BZ
Compress .Z 1f 9d ..
gzip format .gz 1f 8b ..
pkzip format .zip 50 4b 03 04 PK..

存檔檔案

檔案型別 副檔名 16進位制數字xx這裡表示變數 Ascii數字. = 不是Ascii字元
TAR (pre-POSIX) .tar xx xx (a filename)
TAR (POSIX) .tar 75 73 74 61 72 ustar (offset by 257 bytes)

可執行檔案

檔案型別 副檔名 16進位制數字xx這裡表示變數 Ascii數字. = 不是Ascii字元
MS-DOS, OS/2 or MS Windows 4d 5a MZ
Unix elf 7f 45 4c 46 .ELF

其它檔案

檔案型別 副檔名 16進位制數字xx這裡表示變數 Ascii數字. = 不是Ascii字元
pgp public ring 99 00 ..
pgp security ring 95 01 ..
pgp security ring 95 00 ..
pgp encrypted data a6 00 ¦.

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++) {  
            // byte轉int
            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));  
    }  
}

使用圖片長寬判斷

如果能夠正常的獲取到一張圖片的寬高屬性,那肯定這是一張圖片,因為非圖片檔案我們是獲取不到它的寬高屬性的,以下是用於獲取根據是否可以獲取到圖片寬高屬性來判斷這是否一張圖片的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實現:

增加水印的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);  
    }