IT_Programming/ASP.NET (WEB)

ASP.NET Security: 8 Ways to Avoid Attack

JJun ™ 2007. 7. 10. 10:04
Adding security to Web apps may not be great fun but it doesn't have to hard. You can easily ensure your apps meet today's best practices by applying eight principles for secure Web development. by Wei Meng Lee
 
 
Building ASP.NET Web applications has never been easier. Visual Studio.NET hides so many technical details behind the scenes that developers need only concentrate on the core business logic. However, hackers are on the lookout for any opportunity to hack into your application. Which means the pressure is on you to keep defense top of mind. If you don't, you'll quickly find your ASP.NET apps vulnerable to attack.

I've compiled a list of the eight most vital defenses, complete with the information you need to arm yourself against them.

 

 

Tip 1—Cross-site Scripting
Cross-site Scripting (XSS) is one of the most common attacks on Web applications today. Put simply, XSS happens when a hacker injects a script into your Web application (normally through user inputs) and your application accepts it without checking. When the data (containing the script) gets saved into your Web application, subsequent users may be affected as the script may inadvertently get loaded onto their Web browsers.

To better understand XSS, consider an example in which your Web application asks for a user’s name via a text box (see Figure 1). When the user clicks on the button, his name will be displayed in the label control.

However, instead of entering his name, the user might enter a line of script, such as “<script>alert("Hello there!");</script>” (see Figure 2).

Figure 1. What Say You? This form prompts the user to enter a string.

 

 

Figure 2. Not a String. Without proper precautions, a user can enter a script in a text box instead of string.

 

 

 

 

In this case, when the button is clicked, the script will be written to the Label control and the Web browser will execute the script rather than display it (see Figure 3).

 

Figure 3. Not the Plan. The script from Figure 2 executes on the client.
My example is benign, but if a malicious script is injected and gets stored into your application, it may then be sent to other users to cause more severe damage. For example, one type of script could read and display the current cookie values or redirect the user to another Web site.

Fortunately, ASP.NET 1.1 ships with built-in request protection to detect inputs that contain scripts. Figure 4 shows what would happen in ASP.NET 1.1 if a user tried to enter scripting code in an input control.

 

Figure 4. Throwing an Error. ASP.NET's built-in request validation kicks into action and warns you that a script has tried to execute.

Despite the built-in defense by ASP.NET, there were reports of loopholes. By inserting a null character (%00) into the <Script> element, it will bypass detection:


<%00SCRIPT>alert("Hello there!");</SCRIPT>
While this vulnerability has been hot-fixed, it is nevertheless important that you employ added precaution. one good method is to use the Server.HtmlEncode() method to encode all user inputs into HTML-encode strings:

    Private Sub Button1_Click(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) _
            Handles Button1.Click
        lblMessage.Text = Server.HtmlEncode(txtName.Text)
    End Sub

Figure 5. Workaround. You can manually turn off page validation if built-in script protection is disabled.
If the user injects a script, the HTML-encoded input will then look like this:

<script>alert("Hello there!");</SCRIPT>
When this HTML-encoded string is displayed in a browser, it will be displayed as a string, and not executed as a client-side script.

If you need to turn off the built-in script protection in ASP.NET 1.1 for some reason, you can set the validateRequest attribute in your page to false (see Figure 5)



 

 

 

Tip 2 —SQL Injection
SQL Injection is another well-known exploit that hackers love. But surprisingly there are still a lot of people who don't seem to care about this problem. An example will help illustrate its importance: Suppose I have a simple login form with fields for user name and password.

This is a very common function in Web applications. Most people (especially beginning database programmers) will write the functionality as shown:


Private Sub btnLogin_Click(ByVal sender As System.Object, _
                 ByVal e As System.EventArgs) _
                 Handles btnLogin.Click
    SqlConnection1.Open()
    Dim str As String = "SELECT * FROM Users WHERE UserID='" _
              & txtName.Text & "' AND Password='" & _
              txtPassword.Text & "'"
    Dim comm As New SqlCommand(str, SqlConnection1)
    Dim reader As SqlDataReader = comm.ExecuteReader()
    If Not reader.HasRows Then _
       Response.Write("Login failed. Please try again")
    While reader.Read()
        Response.Write("Hello " & reader("UserName"))
    End While
End Sub

Figure 6. Not a Password. This form can allow manipulation your database using SQL keywords.
In this code, the developer simply gets whatever data the user has entered and uses them to formulate the SQL string. At a minimum the developer should also do an input validation; a good choice is to check the length of user-entered data to be sure it is not overly long.

But there's a far worse danger with such sloppy code. There are certain things that hackers can enter in the password field to query your database. For example, the 'password' shown in Figure 6 will display all user names in the database. This is definitely not something you want to support.

Another exploit the hacker can try is known as the SQL Union attack. The following text, entered as a string in either the user name or password text box will execute as shown in Figure 7, giving the hacker plenty of information about your server.

 

xyz' union select @@servername, @@servicename, @@version --

Figure 7. Secrets Unearthed. The user has used the log in form to successfully uncover information about SQL Server.
A much safer way to formulate your SQL string is to use the Parameters object in the SqlCommand object. The advantage to using this approach is that ADO.NET doesn't do the substitution; it passes the parameters to SQL Server where the substitution and validation occurs.

The following shows the updated login method:

 
 
 

Private Sub btnLogin_Click(ByVal sender As System.Object, _
                  ByVal e As System.EventArgs) _
                  Handles btnsecureLogin.Click
    SqlConnection1.Open()
    Dim str As String = "SELECT * FROM Users WHERE " & _
          "UserID=@userID AND Password=@password"
    Dim comm As New SqlCommand(str, SqlConnection1)
    comm.Parameters.Add("@userID", txtName.Text)
    comm.Parameters.Add("@password", txtPassword.Text)
    Dim reader As SqlDataReader = comm.ExecuteReader()
    If Not reader.HasRows Then _
       Response.Write("Login failed. Please try again")
   While reader.Read()
        Response.Write("Hello " & reader("UserName"))
    End While
End Sub

 

 

Tip 3—Validate your User Inputs
Validate your user inputs religiously. The rule of thumb here is to assume the worst about your end users. They are bound to enter inputs that are totally unexpected. Be sure to check for illegal characters and limit the amount of data they can enter. ASP.NET ships with a couple of validation controls: make full use of them, both at the client side and server side.



 

 

Tip 4—Use Hashing to Store your Passwords
I have seen a number of cases where developers simply store users' passwords in plain text. This is a dangerous thing to do; if your SQL Server is compromised, you run the risk of exposing all the passwords. (There are those who argue that if your database server is compromised, it doesn’t matter how you save your passwords—they are no longer secure).

A much better way to store passwords in your database is to use hashing. Hashing is a one-way process of mapping data (plain text) of any length to a unique fixed-length byte sequence. This fixed-length byte sequence is called a hash. Statistically, two different pieces of data would not generate the same hash. And a hash cannot be used to reverse-generate the plain text. In the case of saving passwords in the database, saving the hash value of each password is preferred over the saving the plain password. When a user logs in, the hash value of the password is computed and then compared to the hash value stored in the database. In this case, even if the database server is compromised, the hackers have no way of knowing the users’ real passwords (though he could still alter the hash value of a user’s password to one he generated himself and gain illegal access).

The following function shows how to use the SHA1 hash algorithm implementation in the .NET Framework:


    Public Function ComputeHashValue(ByVal data() As Byte) As Byte()
        Dim hashAlg As SHA1 = SHA1.Create
        Dim hashvalue() As Byte = hashAlg.ComputeHash(data)
        Return hashvalue
    End Function
You could derive the hash value of a password like this:

   Dim hashValue() As Byte
       hashValue = ComputeHashValue(Encoding.ASCII.GetBytes(txtPassword.Text))

The hash value could then be stored in place of the user’s password.

 

 

 

 

 

Tip 5—Encrypt Sensitive Data
ASP.NET Web developers know that it is sometimes useful to store information such as database connection strings in the Web.config file rather than hardcode them in the application. Doing so allows the database server to be changed without modifying and recompiling the application. However, storing sensitive information such as the connection string (which may contain user information and password) in plain text format in Web.config file is not a very good idea, as Web.config is an XML document stored as a text file and thus easily accessed.

So, a safer way would be to encrypt the sensitive information and store the ciphertext into the Web.config file. There are two types of encryption algorithms that you can use:

  • Symmetric
  • Asymmetric
Symmetric algorithms encrypt and decrypt information using a common key. It is a simple and efficient way of encrypting/decrypting information. However the use of a common key makes it less secure if more than one party needs to know the key.

Asymmetric algorithms encrypt and decrypt information using a pair of keys. This pair of keys is made up of a private and a public key. Data encrypted using the public key can only be decrypted using the private key and vice versa. Asymmetric algorithms are much more complex and are computationally expensive. However, it is also much more secure than symmetric algorithms.

Listing 1 shows the use of the Rijndael symmetric encryption algorithm implementation in .NET. Listing 2 shows the RSA asymmetric encryption algorithm implementation in .NET.

The functions shown in Listings 1 and 2 will allow you encrypt the sensitive data in your Web application, especially configuration and XML files. Listing 3 shows the supporting function used by the functions in Listings 1 and 2. The supporting function converts a string to a byte array. For example, it converts a string "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16" to a byte array of {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}.

For asymmetric encryption, you need to first create the pair of private and public keys:


        '===========For Asymmetric use=============
        Dim publicKey, privateKey As String
        Dim RSA As New RSACryptoServiceProvider()
        publicKey = RSA.ToXmlString(False)  ' get public key
        privateKey = RSA.ToXmlString(True)  ' get private key
        '===========Asymmetric Encryption=============
        Dim cipherText as String = AsymmetricEncryption _
                  (txtAsyPlainText.Text, publicKey)
        '===========Asymmetric Decryption=============
        Dim plainText as String = AsymmetricDecryption _
                  (txtAsyCipherText.Text, privateKey)
For symmetric encryption, you need a 16-byte key and Initialization Vector (IV):

        '===========Symmetric=============
        Dim Key, IV as String
        Key="1234567890123456"
        IV ="1234567890123456"
        Dim cipherText As String = SymmetricEncryption _
                  (txtSyPlainText.Text, Key, IV)
        '===========Symmetric=============
        Dim plainText as String = SymmetricDecryption _
                  (txtSyCipherText.Text, Key, IV)
Because SOAP messages are sent in plain text, Web services could also benefit from encryption. Instead of using SSL to protect the entire communication path (which is overkill), you could use encryption to protect sensitive information such as credit card numbers from prying eyes.


 


 

Tip 6—Store Secure Information in the Registry
Besides encrypting data manually, you might also want to use the registry to store sensitive information. For example, you might configure your Web server to log in to a remote database server using Windows authentication. And so you might configure your Web application to use impersonation, specifying the username and password:

<identity impersonate="true" 
   userName="someuser"
   password="tosecret"
/>

Figure 8. Using the Registry Editor. Navigate to the registry directory shown to make your changes.

However, storing the username and password in Web.config in plain text is not a good idea. A better idea is to use the registry to store the username and password.

In the following series of steps, I will show you how to store your database connection string in Web.config and then use the ASPNET_SETREG.exe utility provided by Microsoft to store the username and password in the registry.

1. Download the aspnet_setreg.exe utility from http://support.microsoft.com/default.aspx?scid=kb;en-us;Q329290.

2. Create a new user account in your Windows machine. I have called it ASPNETUSER with a password of “secret.”

3. Add the <appSettings> element into your Web.config file. This setting saves the database connection string into Web.config:


<configuration>
  <appSettings>
     <add key="Distributor" value="workstation id=F16;packet size=4096;integrated security=SSPI;data 
source=F16;persist security info=True;initial catalog=Distributor" />
  </appSettings>
4. In your code, you can retrieve the connection string defined in your Web.config file using:

    Dim connStr As String = _
        ConfigurationSettings.AppSettings("Distributor")
    Dim Conn As New SqlConnection(connStr)
5. Next, use the aspnet_setreg.exe utility to add the username and password of the user account that your ASP.NET application will impersonate, into the registry:

C:\>aspnet_setreg -k:Software\ASPNetApp\Identity -u:ASPNETUSER -p:secret
6. When you do that, Windows will print a long message to the screen. The text of the message is shown below. In particular, look for two lines denoted in bold. You will need to save the two lines in a text file.

Please edit your configuration to contain the following:

        userName="registry:HKLM\Software\ASPNetApp\Identity\ASPNET_SETREG,userName"
        password="registry:HKLM\Software\ASPNetApp\Identity\ASPNET_SETREG,password"

The DACL on the registry key grants Full Control to System, Administrators, and Creator Owner.
If you have encrypted credentials for the <identity/> configuration section, or a connection string for the 
<sessionState/> configuration section, ensure that the process identity has Read access to the
registry key. Furthermore, if you have configured IIS to access content on a UNC share, the account used to 
access the share will need Read access to the registry key.
Regedt32.exe may be used to view/modify registry key permissions.
You may rename the registry subkey and registry value in order to prevent discovery.

 

7. Locate the Machine.config file at: C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\CONFIG and modify the <identity> element in Machine.config file to:


<identity impersonate="true" 
userName="registry:HKLM\Software\ASPNetApp\Identity\ASPNET_SETREG,userName" 
password="registry:HKLM\Software\ASPNetApp\Identity\ASPNET_SETREG,password"
/>

Figure 9. Read Permission. Change the settings to give ASPNET the permission to read the key.
You can override the settings in Machine.config by modifying the Web.config file in your application and specifying another user identity to impersonate.

8. Launch the registry editor and navigate to My Computer/HKEY_LOCAL_MACHINE/SOFTWARE/ASPNetApp/Identity/ASPNET_SETREG (use regedt32), as shown in Figure 8.

9. Right-click on the ASPNET_SETREG registry key and select Permissions. Add the user account ASPNET and set it to Read permission (see Figure 9).

10. Give the user account ASPNETUSER FULL CONTROL access rights to the “Temporary ASP.NET Files” folder located in C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322.

11. That’s it! Your application will now run under the impersonation of ASPNETUSER. And the credentials of the user can be securely retrieved from the registry.



 

 

 

Tip 7—Do Some Housekeeping before You Deploy Your Web Application
Tracing ASP.NET Web applications is made easy by using the <trace> element in the Web.config file as well as the page directive. However, when you are ready to deploy the Web application, be sure to disable tracing at both the page level as well as the application level, which you can do with this code:


<trace enabled="false" requestLimit="10" pageOutput="false" traceMode="SortByTime" localOnly="true" />
Also, turn off debug mode in Web.config file:

<compilation defaultLanguage="vb" debug="false" />
In the <customErrors> element in Web.config, remember to set the mode attribute to RemoteOnly:

<customErrors mode="RemoteOnly" />
The mode attribute has three possible values:
  • on" always display custom (friendly) messages
  • "Off" always display detailed ASP.NET error information.
  • "RemoteOnly" displays custom (friendly) messages only to users not running on the local Web server. This setting is recommended for security purposes, so that you do not display application detail information to remote clients.
Last, but not least, remove the Solution and Project files from your deployment server; they are not mapped to the ISAPI filter and hence it is very easy for hackers to guess their name and access their content directly from the Web browser.

 

 

 

 

 

 

Tip 8—Use Sessions, but Not Cookie-less Sessions
If there is a need to persist sensitive information about a user, use Session objects. Session objects in ASP.NET use cookies to store the Session ID on the cookie, which gets passed to-and-fro between the client and the server. The Session objects containing sensitive information are stored on the server side. Hence, the only information exposed is the Session ID, and not the sensitive information.

ASP.NET supports cookie-less sessions, which might seem tempting since many users turn cookies off in their browsers. But don't go down that road: Using cookie-less sessions subjects you to session hijacking, where a hacker can simply use the URL that you are accessing and assume the browsing. The bottom line is, always avoid cookie-less sessions.

Wei-Meng Lee is a Microsoft .NET MVP and co-founder of Active Developer, a training company specializing in .NET and wireless technologies. He is a frequent speaker and author of numerous books on .NET, XML, and wireless technologies.