问题描述:
话说有一上传功能,使用struts FileUpload拦截器对上传的文件类型、容量大小进行限制,这里略去实现细节。文件类型限制,允许MS Office下的doc、docx、xls、xlsx、ppt、pptx文件上传,经过一段时间测试后,发现某些客户端(事后总结:某些操作系统(发现多为windows XP的某些ghost精简版)会报出错误信息“上传文件类型不对”。
拦截器配置代码:
<action name="savedatumforadd" class="classRoomAction" method="saveDatumForAdd"> <interceptor-ref name="paramsPrepareParamsStack" /> <interceptor-ref name="fileUpload"> <param name="allowedTypes">application/vnd.ms-excel,application/msword, application/vnd.ms-powerpoint,text/plain,application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.openxmlformats-officedocument.presentationml.presentation </param> <param name="maximumSize">102400000</param> </interceptor-ref> <interceptor-ref name="defaultStack" /> <result name="input">/WEB-INF/templates/input.ftl</result> <result name="success">/WEB-INF/templates/success.ftl</result> </action>
跟踪问题:既然类型不对,在Action里查看uploadContentType
private String uploadContentType; public String getUploadContentType () { return addflag; } public void setUploadContentType (String uploadContentType ) { this. uploadContentType = uploadContentType ; }
在调试日志里输出uploadContentType,发现doc、xls、ppt也即office 2003以下版本的文件,在有异常客户端时被输出为application/octet-stream;2007的docx、xlsx、ppts则被输出为application/x-zip-compressed。
进一步查阅FileUploadInterceptor.java源码,可以了解到文件类型的数据来源直接来自客户端,那客户端有没有一个地方像tomcat下web.xml配置mime-mapping的地方呢。网上有网友说起windows注册表,并说到
HKEY_LOCAL_MACHINE->SOFTWARE->Classes->MIME-Database->Content Type
去查看异常的客户端的注册表,发现没有该项。从正常客户机导入此项,还是报同样的错误,发现在HKEY_CLASSES_ROOT下还缺少键.doc .docx等相关键值,从正常客户机导入.doc的相关键值,新开浏览器,上传一个doc文件,不再报错误信息了。
问题的本质:所有浏览器都能根据本操作对文件类型做出判断,但这依赖于当前的操作,就像windows对文件类型的判断依赖于相应的注册表。其他如IOS操作系统也有可能出类似错误。如果客户端的操作系统相关mime-mapping故意置错或是没有数据,此时服务端就会从客户端得到错误的文件类型信息。从本质上来说,像这样从客户端获取文件类型信息来判断文件类型是否正确是比较粗糙的,并有可能被客户端恶意欺骗。
解决问题方案:
1、就上传不了office这几种文件类型,可以找一台正确的mime-mapping相关注册表信息导出。如果有windows下不能上传office这几种文件类型的用户,下载正确的注册表信息文件,并导入。该方案的好处是对于服务端来说相对安全,但这样对用户来说操作上比较麻烦,并且该解决方案且限于mime-mapping,并且不能解决恶意修改mime-mapping的客户端。
2、判断文件类型,放宽判断条件application/octet-stream,application/x-zip-compressed,这样异常的客户端就能上传office文件了,但application/octet-stream实际上对应的是.dll,.a,.bin,.exe,.so等文件类型,application/x-zip-compressed对应.zip文件,放宽了这两个类别,等于这些文件都能上传了。这时可以进一步通过文件扩展名来限制上传的类别。这样的解决方案对于一般用户的需求是足够了,但还是不能解决恶意修改mime-mapping的客户端的情况,另外还存在一种情况就是用户故意把.dll改成.doc来上传,也是无能为力。
3、上传之后的文件类型判断,在第2种办法方案的基础上,在用户成功上传文件后,在服务端的操作层面再次判断其文件类别,因为这个判断是依赖于服务端本身,因此可以确保文件类型检查结果的正确性。这种方案最为安全,但是增加了工作量而且占用服务端更多的资源,如果没有很高的安全要求,可以选择第2种方案,如果需求高级别的安全可以再考虑服务端再做一层。