通常,在文件上传时,我们只会根据文件的扩展名来识别并限制所上传的文件类型。
比如只允许上传 Excel,那么我们将会查看文件的扩展名是不是.xls
/.xlsx
。
但我们知道,在windows系统中,我们可以任意更改文件的扩展名。 那么如果某些用户,将一个zip的压缩文件,改为 .xls
,在上传时,单单通过扩展名来识别文件,那么它一定是可以通过的。
很多时候,用户都会利用这个漏洞,上传一些看似合法的文件,就如上述场景,将盗版的视频文件改为.pdf
,跳过审查(某些论坛就是这么干的)。
需求场景
小A: “呀,网盘不能上传视频了,咋办?”
大B:”什么情况?”
小A:”我一上传mp4的文件,它就提示文件名不合法…”
大B:”把扩展名改成pdf试下,下载看的时候,再改过来。”
…
小A:”真的可以了诶! 膜拜大神!”
那么我们是否可以通过某些方法,来识别出这类“非法”文件呢?
如果文件扩展名改了,原本的文件内容会变吗?
当然不会…
我们可以做个实验,比如原本就是一个视频文件,我们改掉扩展名后,再用播放器打开。 测试会发现,播放器一样可以播放该视频。
那么就是说,我们可以通过读取文件内容,来具体识别出文件类型。
原理在此简单介绍下,其实在每个文件生成时,都会有个File Signature
(亦称magic number
)。 一般都会在文件头处(即前4 ~ 8位字节)。因此,我们可以将此段读取出来,与File Signature的库对应,即可找到实际的文件类型。
网上很多的解决方法,都是通过将文件上传到服务器之后,通过后端语言读取识别。
但这样就会产生一定的延迟,如果遇到较大的文件,让用户等待的时间则过长,体验并不好。
在此,着重介绍一下,如果利用HTML5的新特性FileReader
,直接在浏览器中读取文件内容并识别类型。
利用HTML5直接识别文件类型
不废话,上源码
实际应用
好了,上述核心功能已介绍完成。但使用时,会发现,不过的文件类型,它的Signature的要求是不同的。
有些是前8位字节,有些则是前4位字节。而有些则不是从文件头开始的,需要从512字节开始读取。
更变态的是,同一扩展名会有多个Signature。
因此,在实际运用时,我们并不能只截取文件的前几位字节来识别。那如何可以准备识别文件类型呢?
目前思路如下,单独建立一个库,先根据所上传文件的扩展名,找到它所需要截取的文件部分,读取后对应其Signature,如果不匹配,则说明该文件类型与扩展名是不符合的。
目前,我建立的库如下,分别包含每个文件扩展名对应的Signature,文件截取位置(offset),及截取大小(sizet)。
实现如下:
总结
目前通过纯前端读取文件内容来识别文件类型,可以算是最快最简洁的方法。
但同样也存在一些缺陷:
1. 部分文件类型的Signature完全一样,并非能完全区分开
如微软的Office系列文件,.xlsx
,.docx
,.pptx
,其Signature是完全一样的。
拿了几个文件来测试,结果如下:
文件 |
测试结果 |
mp4 改 pdf |
已拦截 |
zip 改 docx |
已拦截 |
zip 改 jpg |
已拦截 |
docx 改 zip |
未拦截 |
docx 改 xlsx |
未拦截 |
2. 兼容性问题
目前支持HTML5
的FileReader
方法的浏览器如下:
Feature |
Firefox (Gecko) |
Chrome |
Internet Explorer* |
Opera* |
Safari |
Basic support |
3.6 (1.9.2) |
7 |
10 |
Not supported |
Not supported |
可以看出,IE9及以下版本的浏览器并无法很好的支持。
3. File Signature的数据
关于file Signature的数据对应库,可以在File Signature Database查找。目前并不是所有文件都会有对应,只是会一直更新。
参考
[1] FileReader API
[2] Reading the first four bytes of a file
[3] FILE SIGNATURES TABLE
[4] File Signature Database