EasyASP v2.2新功能介绍(1):Easp是如何实现防sql注入的
EasyASP终于到v2.2了,目前还在完善手册,群里有很多人问如何使用的问题,所以打算在写手册的同时写一些新功能的介绍,方便使用Easp的童鞋们快速进入状态。说实话,看到这么多人还在写ASP、还在支持Easp,觉得蛮难得的,所以也一直没有丢下Easp的开发,还是希望Easp能给最后的ASPer们一点微薄的帮助。
EasyASP v2.2的改动比较大,放弃了不少原来我觉得不好用的方法,当然,更多的是加入了不少我觉得会用得很哈屁的新功能。这是这个系列的第一篇文章,准备讲讲EasyASP是如何防范SQL注入的。
说起防SQL注入,大家肯定又是各抒己见,这篇文章也并不打算作深入的探讨,我呢就比较赞同 这篇文章 的观点,所以,我也会从这个角度来讲EasyASP v2.2是如何防范SQL注入的。
1.找到注入点
首先,还是要看看在ASP可以从哪些方面得到注入机会(以下均基于VBScript)。要使用ASP获取用户可以输入的数据,无非是这三个方面:
Request.QueryString
Request.Form
Request.Cookies
如果你的程序里没有对通过Request取得的数据进行处理而直接用在拼接的SQL语句中,则基本上无法避免SQL注入的产生。所以,必须对参与SQL拼接的值进行预先处理,但是又该如何处理呢?
2.处理注入字符
参照我比较赞同的上面那篇文章最后给出的结论,可以简单的这样理解如何处理这些可能被注入的数据:
(1) 对于字符串型的数据,处理其中的单引号,将一个单引号换为两个单引号;
(2) 对于数值型的数据,验证它是否是数字;
(3) 对于日期型的数据,验证它是否是日期型。
所以,ASP的防注入应该是在拼接SQL前对通过第1点取得的数据按第2点的方法进行交叉处理,并返回可以被正确执行在SQL语句中的字符串。
3.EasyASP的解决方案
找到了解决的办法,我们就可以着手处理了。在EasyASP v2.2里,取消了Easp.R/RQ/RF系列方法,而增加了三个方法分别对应第1点里的三种情况,它们分别是:
Easp.Get 用于获取Request.QueryString值 Easp.Post 用于获取Request.Form值 Easp.Cookie 用于获取Request.Cookies值
现在再来看看它们的语法以及它们是如何安全获取值的:
Easp.Get name[:type[separator]][:default] Easp.Post name[:type[separator]][:default] Easp.Cookie name[>subname][:type[separator]][:default]
其中,各个参数的含义如下:
参数名称 | 类型 | 说明 |
name | string | 要获取的参数名,如省略其它参数则相当于原始的Request.QueryString/Form/Cookies |
type | string | 可以是以下字符串: s - 表示name是字符串类型的值,会自动处理其中的单引号 n - 表示name是数值型的值,会验证是否为数值 d - 表示name是日期型的值,会验证是否为日期 na - 表示name是数值型的值,如果name验证不是数字,则会弹出alert警告框并返回前页 da - 表示name是日期型的值,如果name验证不是日期,则会弹出alert警告框并返回前页 ne - 表示name是数值型的值,如果name验证不是数字,则抛出用户错误信息 de - 表示name是日期型的值,如果name验证不是日期,则抛出用户错误信息 |
separator | string | name是由此字符串(特殊符号)隔开的序列,如不省略则会逐个检查name串中的值,并返回一个数组 |
default | string | 如果name为空或按type检查不符合数值/日期要求,则赋给此默认值;序列则逐个赋值 |
subname | string | Easp.Cookie特有,如不省略,则获取如Request.Cookies(A)(B)的值,name为A,subname为B |
举几个例子说明一下:
Easp.Get("user") '相当于Request.QueryString("user") Easp.Post("userid:n") '获取表单项userid的值,如果不是数字则返回空 Easp.Post("list:na, ") '获取表单序列值(用逗号", "隔开,比如用checkbox提交的),并逐个检查是否为数字,不是数字就弹出js警告框,检查都通过则返回一个数组 Easp.Get("starttime:d:2010-1-1") '以日期格式获取Url参数starttime,如果为空或不是日期则返回 "2010-1-1" Easp.Post("allow:s|:jpg") '获取表单项allow的值,以 | 分隔,如果序列中某个值为空则给该项赋值 "jpg" Easp.Cookie("site>power:s:null") '获取一个Cookies值,等同于安全取Request.Cookies("site")("power"), 如果为空则返回 "null"
和v2.1中的Easp.R系列相比,这几个方法既可以安全的获取需要的值,而且如果有取原始数据的需要,也可以通过不带任何其它参数得到。从语义上说,Get、Post和Cookie也更容易理解。而且通过这样处理之后,也就可以安全的用于SQL的拼接了。
4.核心代码
以下是处理程序的核心代码,其它的具体代码,可以下载EasyASP v2.2 Alpha的源文件查看。
(其中的CLeft,CRight,IsN,Has,Use等方法均为Easp的内建方法,请阅读完整源码了解其含义。另外,从下面的源码中也可以看到2.2的另一些新功能,比如支持伪URL Rewrite和Cookies的AES算法加密,以后我会陆续介绍。)
'取QueryString值,支持取Rewrite值 Function [Get](Byval s) Dim tmp, i, arrQs, t If Instr(s,":")>0 Then '如果有类型参数,则取出为t t = CRight(s,":") : s = CLeft(s,":") End If If isRewrite Then '如果是Rewrite的页面地址 arrQs = Split(s_rwtU,"&") For i = 0 To Ubound(arrQs) If s = CLeft(arrQs(i),"=") Then tmp = RegReplace(s_url,s_rwtS,CRight(arrQs(i),"=")) Exit For End If Next Else '否则直接取QueryString tmp = Request.QueryString(s) End If [Get] = Safe(tmp,t) End Function '取Form值 Function Post(ByVal s) Dim t,tmp If Instr(s,":")>0 Then t = CRight(s,":") : s = CLeft(s,":") End If tmp = Request.Form(s) Post = Safe(tmp,t) End Function '取Cookies值 Function Cookie(ByVal s) Dim p,t,coo If Instr(s,">") > 0 Then p = CLeft(s,">") s = CRight(s,">") End If If Instr(s,":")>0 Then t = CRight(s,":") s = CLeft(s,":") End If If Has(p) And Has(s) Then If Response.Cookies(p).HasKeys Then coo = Request.Cookies(p)(s) End If ElseIf Has(s) Then coo = Request.Cookies(s) Else Cookie = "" : Exit Function End If If IsN(coo) Then Cookie = "": Exit Function If b_cooen Then Use("Aes") : coo = Aes.Decode(coo) End If Cookie = Safe(coo,t) End Function '安全获取值基础方法 Function Safe(ByVal s, ByVal t) Dim spl,d,l,li,i,tmp,arr() : l = False '如果类型中有默认值 If Instr(t,":")>0 Then d = CRight(t,":") : t = CLeft(t,":") End If If Instr(",sa,da,na,se,de,ne,", "," & Left(LCase(t),2) & ",")>0 Then '如果有分隔符且要警告 If Len(t)>2 Then spl = Mid(t,3) : t = LCase(Left(t,2)) : l = True End If ElseIf Instr("sdn", Left(LCase(t),1))>0 Then '如果有分隔符且不警告 If Len(t)>1 Then spl = Mid(t,2) : t = LCase(Left(t,1)) : l = True End If ElseIf Has(t) Then '仅有分隔符无类型 spl = t : t = "" : l = True End If li = Split(s,spl) If l Then Redim arr(Ubound(li)) For i = 0 To Ubound(li) If i<>0 Then tmp = tmp & spl Select Case t Case "s","sa","se" '字符串类型 If isN(li(i)) Then li(i) = d tmp = tmp & Replace(li(i),"'","''") If l Then arr(i) = Replace(li(i),"'","''") Case "d","da","de" '日期类型 If t = "da" Then If Not isDate(li(i)) And Has(li(i)) Then Alert("不正确的日期值!") ElseIf t = "de" Then If Not isDate(li(i)) And Has(li(i)) Then [error].Throw("不正确的日期值!") End If tmp = IIF(isDate(li(i)), tmp & li(i), tmp & d) If l Then arr(i) = IIF(isDate(li(i)), li(i), d) Case "n","na","ne" '数字类型 If t = "na" Then If Not isNumeric(li(i)) And Has(li(i)) Then Alert("不正确的数值!") ElseIf t = "ne" Then If Not isNumeric(li(i)) And Has(li(i)) Then [error].Throw("不正确的数值!") End If tmp = IIF(isNumeric(li(i)), tmp & li(i), tmp & d) If l Then arr(i) = IIF(isNumeric(li(i)), li(i), d) Case Else '未指定类型则不处理 tmp = IIF(isN(li(i)), tmp & d, tmp & li(i)) If l Then arr(i) = IIF(isN(li(i)), d, li(i)) End Select Next Safe = IIF(l,arr,tmp) End Function