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
posted @ 2010-01-12 22:49  coldstone  阅读(2871)  评论(6编辑  收藏  举报