我的日常

登录/注册
您现在的位置:论坛 盖世程序员(我猜到了开头 却没有猜到结局) 盖世程序员 > JAVA对图片进行格式检查以及安全检查处理
总共48086条微博

动态微博

查看: 1426|回复: 0

JAVA对图片进行格式检查以及安全检查处理

[复制链接]
admin    

1244

主题

544

听众

1万

金钱

管理员

  • TA的每日心情

    2021-2-2 11:21
  • 签到天数: 36 天

    [LV.5]常住居民I

    管理员

    跳转到指定楼层
    楼主
    发表于 2015-04-03 10:58:17 |只看该作者 |倒序浏览
    一、通常情况下,验证一个文件是否图片,可以通过以下三种方式:
    6 L1 l, @9 n4 w( B1)、判断文件的扩展名是否是要求的图片扩展名; b) ?0 M( [* n- m# ^3 \
    这种判断是用得比较多的一种方式,不过这种方式非常的不妥,别人稍微的把一个不是图片的文件的扩展名修改为图片的扩展名,就绕开了你的这种校验,如果这上传的文件是shell、php或者jsp,那你的网站基本上可以说就在别人的手里面了。9 P  ~8 x4 k. {: i$ U2 m4 e
    不过这种判断方式也不是完全没有用,我们可以把它放在判断图片的最外层,如果一个文件连扩展名都不是我们所要求的图片扩展名,那就根本不用后面的内容格式检查了,从一定程度上说,对减少服务器的压力还是有一定的帮助,否则所有的文件都等上传完后成后再通过服务器去判断,那会在一定程度上浪费器资源的。0 V, L. ~) ?9 n, l8 q8 k
    2)、根据文件的前面几个字节,即常说的魔术数字进行判断,不同文件类型的开头几个字节,可以查看我的另外一篇专站介绍:表示不同文件类型的魔术数字。3 b  z8 ~5 O3 K) y* M
    但是这种判断方式也是非常不靠谱的,因为他只能够验证文件的前面几个字节,如此时有人把一个可执行的PHP文件的扩展名修改为PNG,然后再在前面补上”89 50″两个字节,就又绕开了这种验证方式。7 O/ ^, }& h3 l
    以下是一段通过java代码获取文件前面两个字节的示例程序
      Q3 y) s# s* j, @/ M
    1. import java.io.File;7 K0 F$ M2 }9 a7 {7 A1 ~* u
    2. import java.io.FileInputStream;
        F+ x7 I8 G; _' n
    3. import java.io.IOException;- Z! k3 W% @2 e2 H( J
    4. import java.io.InputStream;% ?6 J  [, c# `7 K) K) e

    5. , _, }2 c$ f$ B7 L, l* b2 l8 K
    6. public class ImageTypeCheck {8 \" P# k  d. i4 a4 h% [

    7. 3 J9 b9 s8 p; `
    8.     public static String bytesToHexString(byte[] src) {4 ~0 L$ j3 j- I/ r! u( c
    9.         StringBuilder stringBuilder = new StringBuilder();
      9 A2 X# x) `9 B( q+ M
    10.         if (src == null || src.length <= 0) {% b! Z! _! z8 Q' F3 Q- j. g6 u
    11.             return null;
      & P6 V/ b0 \, R+ J( D1 n
    12.         }
      6 x& J+ L# B' }# `) D
    13.         for (int i = 0; i < src.length; i++) {
      9 X2 u: d0 q  r4 \
    14.             int v = src[i] & 0xFF;* E  A5 ?) n& B4 u' L
    15.             String hv = Integer.toHexString(v);
      ; C+ u3 ]& L1 i( g. `3 O9 S
    16.             if (hv.length() < 2) {: L; X: @- m& j0 H+ b% R3 E5 J
    17.                 stringBuilder.append(0);# w8 j8 Q1 H: K$ J; y# B4 p
    18.             }
      + l0 i7 Z& @* J" ^
    19.             stringBuilder.append(hv);
      $ A3 t4 t4 _' ~) O+ y. k
    20.         }
      & I4 U- J# V  `* _
    21.         return stringBuilder.toString();2 b1 B) G4 H0 c$ [0 `' ?
    22.     }
      : ~9 }; G5 Z: j8 R" q
    23.     public static void main(String[] args) throws IOException {  J* P2 `0 l9 S; y" Z. Z, v
    24.         String imagePath = "c:/favicon.png";: E2 f' t- J, s5 Y
    25.         File image = new File(imagePath);: }3 j# o+ s3 B* Z% J
    26.         InputStream is = new FileInputStream(image);
      # ~2 M, `. i5 k  o/ G3 ^
    27.         byte[] bt = new byte[2];
      1 h% E7 x$ B6 o' k( H+ Y
    28.         is.read(bt);
      5 M$ ^- i% H. r7 @" T/ [
    29.         System.out.println(bytesToHexString(bt));
      & [+ W1 l: g$ V1 Y: A( T( W
    30.     }
      ; E; C; r) b1 a1 w! s
    31. }
    复制代码
    + L! a5 t3 ~8 f
    不过这种判断方式和判断扩展名一样,也不是完全没有用,至少可以在前期在简单的检查,为进入下一步检查做铺垫。) x1 m1 i7 a' M/ [( `

    8 D6 g2 k( y: L( C3 r5 t. d& ~3)、获取图片的宽高属性
    # [% U1 ^9 S6 G. O$ E( u# m/ d 如果能够正常的获取到一张图片的宽高属性,那肯定这是一张图片,因为非图片文件我们是获取不到它的宽高属性的,以下是用于获取根据是否可以获取到图片宽高属性来判断这是否一张图片的JAVA代码:
    0 [' m+ c' t5 }* k
    1.     /**
      " N2 @; i) d  M/ y1 J
    2.      * 通过读取文件并获取其width及height的方式,来判断判断当前文件是否图片,这是一种非常简单的方式。8 _3 ?1 q! O+ W6 x) A# ^' G; n/ [
    3.      *
      + h9 n' d* S0 {$ j
    4.      * @param imageFile  a* r* Q4 C: w1 v6 `7 Q1 t
    5.      * @return
      ; Z. o3 x9 K) V: L9 Z
    6.      */; _8 S1 |2 x8 S" B; X
    7.     public static boolean isImage(File imageFile) {
      1 ?* ^6 ^, g+ E% U+ Y( G
    8.         if (!imageFile.exists()) {% X# N  b8 c2 i$ x" Q) ?* \0 i
    9.             return false;/ K1 U! y8 [/ F1 G( j$ `/ g( o
    10.         }5 g- ~& J9 S5 Z4 Q$ O" H
    11.         Image img = null;$ _+ j! a- I# a% }) z% h
    12.         try {
        W7 Q% z( c/ V  m) ^% K& K
    13.             img = ImageIO.read(imageFile);
      $ L5 m/ a' H) X
    14.             if (img == null || img.getWidth(null) <= 0 || img.getHeight(null) <= 0) {  ^% ~+ u' t) G7 j' A) `( ^
    15.                 return false;" M8 k6 F/ a3 u5 N& G, N7 Q7 S
    16.             }
      4 [/ g$ C( b* i
    17.             return true;
      # L; Z/ v! K. v* o) m4 s
    18.         } catch (Exception e) {
      0 x5 Q' M- N* o' K* q; H, _
    19.             return false;
      5 q' F8 Q, q/ g' A, ~
    20.         } finally {
      % f1 W% ^7 D8 L
    21.             img = null;
      # @% ^! z$ B  F: q  S
    22.         }
        a, P4 |( l7 ]6 p: Z& a
    23.     }
    复制代码
    . r/ d3 v+ K( Z2 O* m3 J' D, _& ?
    二、图片文件的安全检查处理7 u; i7 m* u5 s
    好了,我们终于判断出一个文件是否图片了,可是如果是在一个可以正常浏览的图片文件中加入一些非法的代码呢:
    , I8 Y: b" u' N! \1 m8 Q
    / {5 w& B5 f6 c) K5 p, c9 O2 C/ m ; P; b& ^- d3 c* [6 A
    9 L9 h3 }: X  z* B( j) d
    这就是在一张正常的图片末尾增加的一些iframe代码,我曾经尝试过单独打开这张图片,也将这张图片放于网页上打开,虽然这样都不会被执行,但并不代表插入其它的代码也并不会执行,杀毒软件(如AVAST)对这种修改是会报为病毒的。
    & u& @* g. K1 i8 O8 J" e 那我们要如何预防这种东西,即可以正常打开,又具有正确的图片文件扩展名,还可以获取到它的宽高属性?呵,我们这个时候可以对这个图片进地重写,给它增加水印或者对它进行resize操作,这样新生成的图片就不会再包含这样的恶意代码了,以下是一个增加水印的JAVA实现:
    9 |( X: U2 B! `  Q" c( y
    1. /**9 B2 m6 G* A* f2 R5 h" Y. @
    2.      * 添加图片水印, `4 d( d, p" C* `& p$ Y2 I
    3.      *
      9 [  s" z: ^. }3 N( g  W
    4.      * @param srcImg 目标图片路径,如:C:\\kutuku.jpg
      8 d) ~& T0 W8 `  @. _1 O% A
    5.      * @param waterImg 水印图片路径,如:C:\\kutuku.png
      7 I- ?8 v$ d; n: d9 G" E
    6.      * @param x 水印图片距离目标图片左侧的偏移量,如果x<0, 则在正中间" F3 g/ a2 ]  u' V, Z
    7.      * @param y 水印图片距离目标图片上侧的偏移量,如果y<0, 则在正中间
      , m+ Z+ I3 y. \/ m  j( E1 d1 D
    8.      * @param alpha 透明度(0.0 -- 1.0, 0.0为完全透明,1.0为完全不透明); q) |: `* Y9 _6 ]7 ?
    9.      * @throws IOException
      + k+ w3 L' N8 ]
    10.      */! j* O+ G4 `; ?& @$ S8 z
    11.     public final static void addWaterMark(String srcImg, String waterImg, int x, int y, float alpha) throws IOException {; {6 D. K9 k" ~8 D
    12.         // 加载目标图片4 f) |3 J% w" T0 q! o' R
    13.         File file = new File(srcImg);' }- {) J! g+ l: B: Z
    14.         String ext = srcImg.substring(srcImg.lastIndexOf(".") + 1);
      + V1 G5 g: `$ t6 K
    15.         Image image = ImageIO.read(file);+ `# {( S  S" z2 n* W' N5 X0 W$ ^
    16.         int width = image.getWidth(null);
      / [0 ^& A+ p! C0 i+ P
    17.         int height = image.getHeight(null);
        M  w+ {" L1 `5 X. u2 W, Z
    18. " ?$ r% s6 U% t) M# g' o6 @
    19.         // 将目标图片加载到内存。3 L' u! r. z/ c- R
    20.         BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
      0 ]! k# B" E5 V5 A# x. @( z5 c
    21.         Graphics2D g = bufferedImage.createGraphics();  K6 P  f& a) G$ K: W% D
    22.         g.drawImage(image, 0, 0, width, height, null);* _0 |5 |2 }3 k' k) A
    23. 5 V2 Y8 ^2 \% \0 r) x! C
    24.         // 加载水印图片。
      # Y( [2 ~$ O4 e
    25.         Image waterImage = ImageIO.read(new File(waterImg));
      3 d2 z/ r" f5 H. Y  Q! z, T$ C/ ^+ B
    26.         int width_1 = waterImage.getWidth(null);
      7 N/ d- e4 i9 e7 @
    27.         int height_1 = waterImage.getHeight(null);
      $ s9 t) e  H* i" t$ \0 G; U
    28.         // 设置水印图片的透明度。
      - N" y6 m/ Q' U
    29.         g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));, c: [  s& x# X) J6 V7 v9 _& j

    30. % {' q6 X- \) ^0 C
    31.         // 设置水印图片的位置。  j; v+ ^/ o, J0 I) z' p
    32.         int widthDiff = width - width_1;2 G; {! u1 o! h
    33.         int heightDiff = height - height_1;/ T/ b# ]' j! z" D
    34.         if (x < 0) {
      : ?- ^6 l1 p. y2 h3 x+ W+ T
    35.             x = widthDiff / 2;
      ! ?8 Y' E4 G3 `, _' H' x- w5 f
    36.         } else if (x > widthDiff) {) x1 {/ A/ ]( l- N6 G" y
    37.             x = widthDiff;5 H9 f1 ]. }5 U# d
    38.         }
      ) D- ~7 G( K! B6 O7 n9 |
    39.         if (y < 0) {
        ?0 B: V5 c2 h7 W; D
    40.             y = heightDiff / 2;1 K+ d: m+ s$ N0 k+ h6 m( o( S
    41.         } else if (y > heightDiff) {
      1 r+ r. ]( b; B& F/ W
    42.             y = heightDiff;+ s+ T8 C" H: `0 T1 k
    43.         }+ [# i  Q" F- `3 t

    44. , V, y9 N$ t: ~/ V
    45.         // 将水印图片“画”在原有的图片的制定位置。2 a  [3 b7 ~( J
    46.         g.drawImage(waterImage, x, y, width_1, height_1, null);
      ) H* a& R" S) K0 N+ {
    47.         // 关闭画笔。6 B8 r) L+ k3 e' x  n0 R
    48.         g.dispose();
      : h, Y  g3 T. {; |
    49.   f1 a+ s% V& ^1 _
    50.         // 保存目标图片。: y# ~+ m* |" h) @) Y0 X9 _
    51.         ImageIO.write(bufferedImage, ext, file);% D" f* Y6 Q6 V3 x, g: W
    52.     }
    复制代码
    通过以上几种方式,应该可以避免绝大部份图片中带恶意代码的安全问题、但是我们没必要自己再造轮子了、并且自己造的不一定好。这里采用fckeditor 插件校验图片的方式。
    8 |) j$ l8 I% c8 t! F- I& Y+ g找到ImageInfo.java 类即可。
    7 Y% J2 _( d* {8 l
    1. /**2 e! }) o/ v0 J. ?; ^# ~
    2.          * 校验是否是图片
      . y6 \$ H6 O1 V/ c, I7 P6 D
    3.          * @Author        张志朋
      ( G  W. I' P; ~' q8 T& g( L
    4.          * @param in, }4 Y3 N. R9 ^+ V5 g- x' b" A3 C
    5.          * @return  boolean) e" n2 H# r, X: M" p2 L  J" f7 \( Q
    6.          * @Date        2015年4月2日  Z/ e4 ~) ?, T; \
    7.          * 更新日志/ U7 h) Y9 B* c% ~# g) Q! F
    8.          * 2015年4月2日 张志朋  首次创建
      0 m' N6 g2 d. c7 h+ |  v
    9.          *; E7 U- E4 e2 D& Y2 ~
    10.          */" a7 x& z; ~' K, i
    11.         public static boolean isImage(final InputStream in) {# D4 n/ B! B$ z7 e$ K! ^; s
    12.             ImageInfo ii = new ImageInfo();* O3 U! V3 S& K9 @' D
    13.             ii.setInput(in);0 f0 X3 ]& _- Z
    14.             return ii.check();
      + y+ P% x- _3 V  K1 W9 i
    15.     }
    复制代码

    : A2 i  c0 j$ G* _+ q1 ~ ImageInfo.zip (7.11 KB, 下载次数: 2) 3 V/ i/ i. T0 y! u
    + x. ?& q3 _5 S3 G3 q' c: V7 H3 E6 j! i
    , B1 x1 |8 j/ D1 O8 l1 @
    4 {9 M8 T2 t. F0 v2 `3 v

    科帮网 1、本主题所有言论和图片纯属会员个人意见,与本社区立场无关
    2、本站所有主题由该帖子作者发表,该帖子作者与科帮网享有帖子相关版权
    3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和科帮网的同意
    4、帖子作者须承担一切因本文发表而直接或间接导致的民事或刑事法律责任
    5、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责
    6、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除并致以最深的歉意
    7、科帮网管理员和版主有权不事先通知发贴者而删除本文


    JAVA爱好者①群:JAVA爱好者① JAVA爱好者②群:JAVA爱好者② JAVA爱好者③ : JAVA爱好者③

    快速回复
    您需要登录后才可以回帖 登录 | 立即注册

       

    关闭

    站长推荐上一条 /1 下一条

    发布主题 快速回复 返回列表 联系我们 官方QQ群 科帮网手机客户端
    快速回复 返回顶部 返回列表