This is the third part of the multi-part series on writing secure ASP.NET code. The post series was inspired by the online training class Creating Secure Code for ASP.NET offered by TeamProfessor from Security Innovations. In the first part part I covered input data validation and types of online attacks such as SQL injection and HTTP Response splitting. The second part dealt with secure error handling. Today, I am going to discuss best practices for storing sensitive data and web application configuration.
Storing sensitive data
Any sensitive information such as social security number, medical records, credit card number can be compromised while it is present either in memory, storage, or in transfer. Therefore, it needs to be handled securely. There are three main approaches to encrypting the data. Each one being suitable for a particular use case. Hashing approach is appropriate when the data is used for comparison purposes only. The minimum salt size should be 16 bits:
public byte[] HashData(byte[] data, byte[] salt) { HMACSHA512 md; byte[] digest; md = new HMACSHA256(salt); md.Initialize(); digest = md.ComputeHash(data); md.Clear(); return digest; } public static byte[] GenerateSalt() { byte[] randBytes = newbyte[4]; RNGCryptoServiceProvider gen = new RNGCryptoServiceProvider(); gen.GetNonZeroBytes(randBytes); return randBytes; }
If the data needs to be retrieved later, then symmetric encryption is the most appropriate. It uses a private key of 256 bits minimum and AES algorithm:
Rijndael encMD; ICryptoTransform encCT; byte[] digest; // The following code obtains the encryption key and initialization vector // from the application's key repository. encMD = Rijndael.Create(); encMD.Key = MyApp.Crypto.GetEncryptionKey(); encMD.IV = MyApp.Crypto.GetEncryptionIV(); encCT = encMD.CreateEncryptor(); // Note that sensitiveData is of type byte[] and contains // data that is sensitive in nature digest = encCT.TransformFinalBlock(sensitiveData, 0, sensitiveData.Length); encMD.Clear();
Asymmetric encryption is utilized to encrypt data that is retrieved by multiple entities. It uses private/public key pair of 2048 bits minimum to encrypt the data using RSA algorithm:
byte[] encryptedData; // Creating a new instance of RSACryptoServiceProvider will generate // public and private key data. RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(); // Note that sensitiveData is of type byte[] and contains // data that is sensitive in nature encryptedData = RSA.Encrypt(sensitiveData, false)
In ASP.NET applications, web.config contains configuration settings that need to be protected: <appSettings>, <connectionStrings>, <identity>, <sessionState>.
Sensitive data should never be cached, except for a session ID. The session ID is the only data that may be cached and stored in a cookie. Use HTTPOnly flag to mark the cookie in order to prevent a malicious script from stealing the cookie content.
Web pages containing the sensitive content should not be cached. Caching is prevented using <META HTTP-EQUIV=”PRAGMA” CONTENT=”NO-CACHE”> in the head section of the HTML file:
<HTML> <HEAD> <META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE"> <TITLE> Pragma No-cache </TITLE> </HEAD> <BODY> This is an example of where to place the second header section so that the "Pragama, No-Cache" metatag will work as it is supposed to. </BODY> </HTML>
In order to disable output caching for all sensitive pages use:
<%@ OutputCache Location="None"%>
Tracing should be disabled for sensitive pages:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Login.aspx.cs" Inherits="Login" Trace="false" %>
Data transmitted need to be encrypted or the communication channel needs to be encrypted (SSL). When submitting a form, an absolute path should be used to ensure that SSL is used:
<form method="POST" action="https://login.example.com/myapp/login.aspx">
Applications should transmit cookies using SSL. This is done by setting webconfig:
<authentication mode="Forms"> <forms loginUrl="https://login.example.com/myapp/login.aspx" requireSSL="true"/> </authentication>
If insecure channel is used to transmit encrypted data, then POST request should be used. GET request needs to be avoided because it may cause the data to be stored in web history, server logs, or proxy logs.
The web.config file contains web application security settings. It is imperative to configure them appropriately. It is recommended to set the customErrors setting to RemoteOnly or On in the web.config file. If customErrors is set to “Off”, an attacker may see a complete stack trace as well as a highly detailed error message generated by the .NET run-time or other backend systems. In order to prevent session hijacking the cookies need to be encrypted. This is easily done by configuration in web.config:
<roleManager cookieProtection="All" cookieRequireSSL="true"/>
In cases where SSL is not used, user should be forced to re-authenticate after a timeout period. This can be set for authentication cookies as follows:
<authentication mode="Forms"> <forms slidingExpiration="false" .../> </authentication>
It can be set for authorization cookies as follows:
<roleManager cookieSlidingExpiration="false" .../>
Authentication cookies are only used on the server side to authenticate incoming request. They are never used on the client side. Preventing XSS attack requires web.config setting:
<system.web> <httpCookies httpOnlyCookies="true" requireSSL="true" topic="" /> </system.web>
This value can also be also set programmatically using System.Net.Cookies class. The protection can be circumvented in a variety of ways, but still adds a layer of protection. The persistent cookies should be avoided because they allow an attacked who has physical access to the machine to compromise user session.
If a Login control is used, then set the DisplayRememberMe property to false. If Login controls is not used then use non-persistent cookie when calling RedirectFromLoginPage or SetAuthCookie methods of the FormsAuthentication class:
public void Login_Click(object sender, EventArgs e) { // Is the user valid? if (Membership.ValidateUser(userName.Text, password.Text)) { // Parameter two set to false indicates non-persistent cookie FormsAuthentication.RedirectFromLoginPage(username.Text, false); } else { Status.Text = "Invalid credentials. Please try again."; } }
For authorization cookie:
<roleManager createPersistentCookie="false" .../>
Guidelines for secure session management:
- Do not reuse sessions: to ensure the session ID is never recycled set web.config as:
<sessionState regenerateExpiredSessionId="False" />
- Ensure session timeout: set expiration timeout period:
<sessionState timeout="number of minutes" />
- Allow users to terminate their sessions by letting them logout. It is accomplished by calling Session.Abandon() method.
- Use unique cookie names and paths in order to avoid conflict between multiple web applications running on the same web server. It is accomplished by setting unique name and path attributes in web.config:
<forms name="unique HTTP cookie to use for authentication" path="path for cookies issued by the application” </forms>
Application parameters are potential sources of input and they should not be trusted. Parameters may be: URL based, form based, ViewState, hidden fields, cookies. User tracking should be done server-side using sessions. Avoid storing sensitive info in hidden fields or cookies.
No security decision point should be made by the client (ie. list of roles provided by the client to the server).
Make sure the client cannot influence the data used to make security decisions.
Ensure that security related data cannot be tempered.
Enforce security checks on the server even if the business requirement mandates security decision to be made on the client.
Super……