本文出自冯立彬的博客,原地址:http://www.fenglibin.com/use_java_to_check_images_type_and_security.html
一、通常情况下,验证一个文件是否图片,可以通过以下三种方式:
1)、判断文件的扩展名是否是要求的图片扩展名
这种判断是用得比较多的一种方式,不过这种方式非常的不妥,别人稍微的把一个不是图片的文件的扩展名修改为图片的扩展名,就绕开了你的这种校验,如果这上传的文件是shell、php或者jsp,那你的网站基本上可以说就在别人的手里面了。
不过这种判断方式也不是完全没有用,我们可以把它放在判断图片的最外层,如果一个文件连扩展名都不是我们所要求的图片扩展名,那就根本不用后面的内容格式检查了,从一定程度上说,对减少服务器的压力还是有一定的帮助,否则所有的文件都等上传完后成后再通过服务器去判断,那会在一定程度上浪费器资源的。
2)、根据文件的前面几个字节,即常说的魔术数字进行判断,不同文件类型的开头几个字节,可以查看我的另外一篇专站介绍:表示不同文件类型的魔术数字。
但是这种判断方式也是非常不靠谱的,因为他只能够验证文件的前面几个字节,如此时有人把一个可执行的PHP文件的扩展名修改为PNG,然后再在前面补上”89 50″两个字节,就又绕开了这种验证方式。
以下是一段通过JAVA代码获取文件前面两个字节的示例程序:
1 import java.io.File; 2 import java.io.FileInputStream; 3 import java.io.IOException; 4 import java.io.InputStream; 5 6 public class ImageTypeCheck { 7 8 public static String bytesToHexString(byte[] src) { 9 StringBuilder stringBuilder = new StringBuilder(); 10 if (src == null || src.length <= 0) { 11 return null; 12 } 13 for (int i = 0; i < src.length; i++) { 14 int v = src[i] & 0xFF; 15 String hv = Integer.toHexString(v); 16 if (hv.length() < 2) { 17 stringBuilder.append(0); 18 } 19 stringBuilder.append(hv); 20 } 21 return stringBuilder.toString(); 22 } 23 public static void main(String[] args) throws IOException { 24 String imagePath = "c:/favicon.png"; 25 File image = new File(imagePath); 26 InputStream is = new FileInputStream(image); 27 byte[] bt = new byte[2]; 28 is.read(bt); 29 System.out.println(bytesToHexString(bt)); 30 } 31 }
不过这种判断方式和判断扩展名一样,也不是完全没有用,至少可以在前期在简单的检查,为进入下一步检查做铺垫。
3)、获取图片的宽高属性
如果能够正常的获取到一张图片的宽高属性,那肯定这是一张图片,因为非图片文件我们是获取不到它的宽高属性的,以下是用于获取根据是否可以获取到图片宽高属性来判断这是否一张图片的JAVA代码:
1 /** 2 * 通过读取文件并获取其width及height的方式,来判断判断当前文件是否图片,这是一种非常简单的方式。 3 * 4 * @param imageFile 5 * @return 6 */ 7 public static boolean isImage(File imageFile) { 8 if (!imageFile.exists()) { 9 return false; 10 } 11 Image img = null; 12 try { 13 img = ImageIO.read(imageFile); 14 if (img == null || img.getWidth(null) <= 0 || img.getHeight(null) <= 0) { 15 return false; 16 } 17 return true; 18 } catch (Exception e) { 19 return false; 20 } finally { 21 img = null; 22 } 23 }
二、图片文件的安全检查处理
好了,我们终于判断出一个文件是否图片了,可是如果是在一个可以正常浏览的图片文件中加入一些非法的代码呢:
这就是在一张正常的图片末尾增加的一些iframe代码,我曾经尝试过单独打开这张图片,也将这张图片放于网页上打开,虽然这样都不会被执行,但并不代表插入其它的代码也并不会执行,杀毒软件(如AVAST)对这种修改是会报为病毒的。
那我们要如何预防这种东西,即可以正常打开,又具有正确的图片文件扩展名,还可以获取到它的宽高属性?呵,我们这个时候可以对这个图片进地重写,给它增加水印或者对它进行resize操作,这样新生成的图片就不会再包含这样的恶意代码了,以下是一个增加水印的JAVA实现:
1 /** 2 * 添加图片水印 3 * 4 * @param srcImg 目标图片路径,如:C:\\kutuku.jpg 5 * @param waterImg 水印图片路径,如:C:\\kutuku.png 6 * @param x 水印图片距离目标图片左侧的偏移量,如果x<0, 则在正中间 7 * @param y 水印图片距离目标图片上侧的偏移量,如果y<0, 则在正中间 8 * @param alpha 透明度(0.0 -- 1.0, 0.0为完全透明,1.0为完全不透明) 9 * @throws IOException 10 */ 11 public final static void addWaterMark(String srcImg, String waterImg, int x, int y, float alpha) throws IOException { 12 // 加载目标图片 13 File file = new File(srcImg); 14 String ext = srcImg.substring(srcImg.lastIndexOf(".") + 1); 15 Image image = ImageIO.read(file); 16 int width = image.getWidth(null); 17 int height = image.getHeight(null); 18 19 // 将目标图片加载到内存。 20 BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 21 Graphics2D g = bufferedImage.createGraphics(); 22 g.drawImage(image, 0, 0, width, height, null); 23 24 // 加载水印图片。 25 Image waterImage = ImageIO.read(new File(waterImg)); 26 int width_1 = waterImage.getWidth(null); 27 int height_1 = waterImage.getHeight(null); 28 // 设置水印图片的透明度。 29 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)); 30 31 // 设置水印图片的位置。 32 int widthDiff = width - width_1; 33 int heightDiff = height - height_1; 34 if (x < 0) { 35 x = widthDiff / 2; 36 } else if (x > widthDiff) { 37 x = widthDiff; 38 } 39 if (y < 0) { 40 y = heightDiff / 2; 41 } else if (y > heightDiff) { 42 y = heightDiff; 43 } 44 45 // 将水印图片“画”在原有的图片的制定位置。 46 g.drawImage(waterImage, x, y, width_1, height_1, null); 47 // 关闭画笔。 48 g.dispose(); 49 50 // 保存目标图片。 51 ImageIO.write(bufferedImage, ext, file); 52 }
通过以上几种方式,应该可以避免绝大部份图片中带恶意代码的安全问题,不过由于我个人的才疏学浅,可能有没有考虑周全的地方,还请各位不吝指教了。