文件上传大部分通过web前端判断后尾名或者service后端判断后尾名,这种操作具有一定的风险,比如:我可以将一个jsp页面,修改后尾名改成jpg文件进行上传,由于图片预览功能,这个文件会被执行,这时就可以发送用户数据到指定的服务下,窃取用户信息。
本文通过文件流头部判断文件类型
不同的文件具有不同的头部,比如:
不同的文件具有不同的头部信息,以SpringBoot为例,通过拦截器拦截文件流进行判断:
1、添加配置文件checkFileHeader.properties
在src/main/resources中增加配置文件checkFileHeader.properties,文件内容:
1
2
3
4
5
6
7
8
9
10
|
JPEG=FFD8FF PNG=89504E47 GIF=47494638 TXT=75736167 PDF=255044462D312E DOC=D0CF11E0 XML=3C3F786D6C DOCX=504B0304 APK=504B030414000808 IPA=504B03040A000000 |
2、编写读取properties文件类
读取checkFileHeader.properties文件内容,用于拦截器判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
/** * 读取文件流头信息 * @author hanjie * */ public class FileHeaderHelper { private static FileHeaderHelper me ; private static List<String> headerList ; private FileHeaderHelper(){} public static FileHeaderHelper getInstance(){ if (me == null ){ me = new FileHeaderHelper() ; } return me ; } public List<String> getHeaderList(){ if (headerList == null ){ headerList = new ArrayList<String>() ; PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); String classpathResource = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "/fileheader.properties" ; Properties p = new Properties(); try { Resource[] res = resolver.getResources(classpathResource) ; for (Resource re : res) { p.load(re.getInputStream()); break ; } } catch (IOException e) { e.printStackTrace(); } for (Map.Entry<Object, Object> item : p.entrySet()) { headerList.add(item.getValue().toString()) ; } } return headerList ; } } |
3、编写拦截器
拦截去中,获取文件流,读取文件流前8个字节,根据需要可以读取更多字节判读,8个字节转成16进制为16个字符串,我这里最长的APK/IPA文件也就16个字节,所以读取8个字节,读取字节后判断是否checkFileHeader.properties文件中字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
/** * 文件上传拦截器 * @author hanjie * */ public class FileHeaderCheckInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 判断是否为文件上传请求 if (request != null && request instanceof MultipartHttpServletRequest) { MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; Map<String, MultipartFile> files = multipartRequest.getFileMap(); Iterator<String> iterator = files.keySet().iterator(); while (iterator.hasNext()) { String formKey = (String) iterator.next(); MultipartFile multipartFile = multipartRequest.getFile(formKey); //String filename = multipartFile.getOriginalFilename(); byte [] file = multipartFile.getBytes() ; 获取字节流前 8 字节,差不多够了,不行再加 int HEADER_LENGTH = 8 ; if (file.length>HEADER_LENGTH){ //转成16进制 StringBuilder sb = new StringBuilder(); for ( int i= 0 ;i<HEADER_LENGTH;i++){ int v = file[i] & 0xFF ; String hv = Integer.toHexString(v); if (hv.length() < 2 ) { sb.append( 0 ); } sb.append(hv); } boolean isFound = false ; String fileHead = sb.toString().toUpperCase() ; List<String> headerList = FileHeaderHelper.getInstance().getHeaderList() ; for (String header : headerList){ if (fileHead.startsWith(header)){ isFound = true ; break ; } } if (!isFound){ // throw new BaseRunException("上传文件有异常,已被系统禁止!") ; System.out.println( "----------上传文件有异常,已被系统禁止!头部信息:" +fileHead); response.setCharacterEncoding( "UTF-8" ); response.setContentType( "application/json;charset=utf-8" ); PrintWriter printWriter = response.getWriter(); printWriter.write( "上传文件有异常,已被系统禁止!" ); return false ; } } } } return true ; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // TODO Auto-generated method stub } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // TODO Auto-generated method stub } } |
4、配置拦截文件
拦截器写完了,配置下让它生效,在Configuration中配置拦截器,拦截文件流进行判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
@Configuration public class MyfWebAppConfiguration extends WebMvcConfigurerAdapter { //拦截器,拦截文件流 public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor( new FileHeaderCheckInterceptor()) .addPathPatterns( "/**" ); } // //注册过滤 // @Bean // public FilterRegistrationBean myFilterRegistration() { // // FilterRegistrationBean registration = new FilterRegistrationBean(); // registration.setFilter(new LoginFilter()); // registration.addUrlPatterns("/serviceInvoke"); // //registration.addInitParameter("paramName", "paramValue"); // registration.setName("loginFilter"); // registration.setOrder(1); // return registration; // } // // // //注册servlet // @Bean // public ServletRegistrationBean myServletRegistration() { // ServletRegistrationBean registration = new ServletRegistrationBean(new DownloadServlet()); // registration.addUrlMappings("/download"); // return registration; // } } |
页面消息提醒已经在printWriter中输出了,根据自己的页面编写显示吧。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://muyunfei.blog.csdn.net/article/details/90230481