asp.net附件上传验证方式
附件上传是大家经常用到的功能,虽然大家都很容易把附件上传到服务器或数据库中,但是大家却疏忽了上传的文件的安全性,现在网站上大部分的页面上传验证只是通过脚本或者后台对文件的后缀名进行验证,访客很容易用一个伪装成正确格式的木马文件进行上传操作,一旦上传成功,访客很容易得到webshell,通过webshell可以攻击、控制服务器,从而得到黑客想要的任何信息,这是多么恐怖的一件事情,我们如何避免呢?个人总结了几种方式去避免,主要有:1、对文件后缀名进行验证;2、对文件头进行验证;3、屏蔽文件上传目录及修改文件名。
对文件后缀名的验证,可以通过js和后台两种方式去验证,这里贴一下后台验证的方式,见如下代码:
string fullName = this.upload.PostedFile.FileName; string exeName = string.Empty; if (fullName != string.Empty) { if (this.upload.PostedFile.ContentLength > 102400) { Response.Write("<script>alert('请选择小于100k的文件!!')</script>"); return; } exeName = fullName.Substring(fullName.LastIndexOf(".") + 1); //逻辑过滤文件后缀 if (exeName.ToLower().Equals("log") || exeName.ToLower().Equals("txt")) { // 这里执行文件保存操作 } else { Response.Write("<script>alert('只能上传jpg,gif,bmp格式的文件!!!')</script>"); return; } }
对文件头进行验证,对于我们经常用到的文件,每个文件一般都会有文件头,一般是前两个字节,我们通过读取前两个字节与文件头枚举进行对比,这里列举一下经常用到的文件头,见下图:
对于这种数据格式我们可以封装成为枚举,代码如下:
public enum E_FileExtension { NONE = 0, JPG = 255216, GIF = 7173, BMP=6677, PNG = 13780, SWF = 6787, RAR = 8297, ZIP = 8075, XML = 6063, DOC = 208207, DOCX = 8075, ASPX = 239187, CS = 117115, SQL = 255254, HTML = 6063, EXE = 7790 }
我们可以把文件头的验证专门封装为一个类,主要是按照职责分离的原则,代码如下:
/// <summary> /// 文件后缀名验证类 /// </summary> public class FileCheckExtension { // 提供按照UploadFile和Byte[]进行的过滤验证和算法; private static object obj = new object(); // 锁定对象 private static FileCheckExtension fce; // 验证类实体 private List<E_FileExtension> filterList = new List<E_FileExtension>(); // 过滤的格式集合 private FileCheckExtension(List<E_FileExtension> _filterList) { init(_filterList); } /// <summary> /// 初始化 /// </summary> /// <param name="_filterList">过滤的格式集合</param> private void init(List<E_FileExtension> _filterList) { filterList = _filterList; } /// <summary> /// 单例模式主入口 /// </summary> /// <param name="_filterList">过滤的格式集合</param> /// <returns>FileCheckExtension</returns> public static FileCheckExtension BulderFCE(List<E_FileExtension> _filterList) { if (fce == null) { lock (obj) { if (fce == null) { fce = new FileCheckExtension(_filterList); } } } return fce; } /// <summary> /// 验证上传控件的文件类型 /// </summary> /// <param name="fu">FileUpload</param> /// <returns>类型枚举</returns> public E_FileExtension GetFileExtension(FileUpload fu) { Byte[] bytesContent = new Byte[2]; fu.PostedFile.InputStream.Read(bytesContent, 0, 2); return GetFileExtension(bytesContent); } /// <summary> /// 验证Byte数组的文件类型 /// </summary> /// <param name="bytes">Byte[]</param> /// <returns>类型枚举</returns> public E_FileExtension GetFileExtension(Byte[] bytes) { E_FileExtension eFX = E_FileExtension.NONE; MemoryStream ms = new MemoryStream(bytes); BinaryReader br = new BinaryReader(ms); string fileTop = string.Empty; byte buffer; try { buffer = br.ReadByte(); fileTop = buffer.ToString(); buffer = br.ReadByte(); fileTop += buffer.ToString(); foreach (E_FileExtension efx in filterList) { if (System.Int32.Parse(fileTop).Equals((int)efx)) { eFX = efx; } } } catch(Exception ex) { throw ex; } finally { br.Close(); ms.Dispose(); } return eFX; } /// <summary> /// 验证文件后缀名是否合法 /// </summary> /// <param name="fu"></param> /// <returns>true or false</returns> public bool ExtensionVerify(FileUpload fu) { string fullName = fu.PostedFile.FileName; string exeName = string.Empty; if (fullName != string.Empty) { exeName = fullName.Substring(fullName.LastIndexOf(".") + 1); // 对于特殊文件格式txt,直接放行 if (exeName.ToLower().Equals("txt") || exeName.ToLower().Equals("log")) { return CheckIsTextFile(fu); } } E_FileExtension efe = GetFileExtension(fu); if ((int)efe == 0) { return false; } else { return true; } } /// <summary> /// 验证文件后缀名是否合法 /// </summary> /// <param name="bytes"></param> /// <returns>true or false</returns> public bool ExtensionVerify(Byte[] bytes) { E_FileExtension efe = GetFileExtension(bytes); if ((int)efe == 0) { return false; } else { return true; } } public bool CheckIsTextFile(FileUpload fu) { Byte[] bytesContent = new Byte[fu.PostedFile.ContentLength]; fu.PostedFile.InputStream.Read(bytesContent, 0, fu.PostedFile.ContentLength); MemoryStream ms = new MemoryStream(bytesContent); bool isTextFile = true; try { int i = 0; int length = (int)ms.Length; byte data; while (i < length && isTextFile) { data = (byte)ms.ReadByte(); isTextFile = (data != 0); i++; } return isTextFile; } catch (Exception ex) { throw ex; } finally { if (ms != null) { ms.Close(); } } } }
这样我们把对文件后缀的验证都交由这个类来处理,前台页面调用方式见如下代码:
string fullName = this.upload.PostedFile.FileName ; string exeName = string.Empty; if (fullName != string.Empty) { if (this.upload.PostedFile.ContentLength > 102400) { Response.Write("<script>alert('请选择小于100k的文件!!')</script>"); return; } exeName = fullName.Substring(fullName.LastIndexOf(".") + 1); //逻辑过滤文件后缀 if (exeName.ToLower().Equals("log") || exeName.ToLower().Equals("txt")) { // 组合需要验证的枚举数组 List<E_FileExtension> fes = new List<E_FileExtension> { E_FileExtension.GIF, E_FileExtension.JPG, E_FileExtension.PNG, E_FileExtension.XML, E_FileExtension.DOC, E_FileExtension.DOCX }; // 单例启动验证类 FileCheckExtension fcx = FileCheckExtension.BulderFCE(fes); //fcx.CheckIsTextFile(upload); // 采用文件头验证 bool bl = fcx.ExtensionVerify(upload); if (!bl) { Response.Write("<script>alert('上传的文件不合法!!!')</script>"); return; } // 这里执行文件保存操作 } else { Response.Write("<script>alert('只能上传jpg,gif,bmp格式的文件!!!')</script>"); return; } }
接下来是屏蔽文件上传目录及修改文件名,修改文件名是比较直观的,用户上传的是A,我们可以按照自己的生成算法生成一个新的名称,这个名称可以是没有规律可循的,这样黑客就不好访问这个文件,还有屏蔽文件上传目录,例如我们的上传目录在服务器程序的A文件夹,那么我们可以通过实现System.Web.IHttpModule接口,实现对URL的重定向和伪地址的访问,这样访客就不会知道上传的文件放在哪,也不能直接访问。
总结一下:作为开发来说我们应该尽量从程序上控制安全性,当然对于以上的避免措施对高明的黑客来说还是小菜一碟,但是至少我们已经有意识去做一些安全性的措施了,以上纯属个人观点,只为总结检讨!