9. Secure Coding Guidelines – Application Security in the ISO27001 Environment

Chapter 9. Secure Coding Guidelines

In Chapter 7 we discussed the role of secure coding guidelines in ensuring that applications are secure. In this chapter, we look at some of the most important guidelines developers should follow. Since many of these are low level code writing requirements, we illustrate the guidelines with code snippets. As the coding guidelines are platform-agnostic and apply to all popular platforms, we show code snippets only for one platform, .Net. The examples we show with .Net can be ported to J2EE, PHP, Perl and other platforms too.

We classify the coding guidelines into six categories:

  1. Input validation guidelines.

  2. Authentication guidelines.

  3. Guidelines for handling sensitive data.

  4. Session management guidelines.

  5. Error handling guidelines.

  6. Miscellaneous guidelines.

Input validation guidelines (ISO27001 A.12.2.1)

Validate all user inputs to prevent any malicious input from being accepted. If such inputs are not validated, it may lead to several possible attacks, as we have already seen: SQL injection, LDAP injection, cross-site scripting, etc. A strong input validation strategy will have a number of elements.

Validate at server

Input validations implemented at the client can be easily bypassed. We have seen that an adversary can use web proxy editors to intercept traffic from client to server and modify the input values. Input validations implemented at the server cannot be bypassed.

Web applications may enforce use of strong passwords by incorporating input validations on the client using Javascript on the registration form. We look at such an insecure usage below:

Unsafe usage

<script language="javascript">
if ((password.value==null)||(password.value==‘’))//   Check for blank
passwords
{
alert(‘Blank passwords are not allowed’)
password.focus();
password.select();
return false
}
if (password.value.length < 6)// Check for passwords less than 6
characters
{
alert(‘The password should be at least 6 characters in length’)
password.focus();
password.select();
return false
}

The above Javascript implements the password policy of enforcing passwords longer than six characters. This code will execute on the user’s browser. But it is possible for an adversary to bypass this validation. Hence users of the application can set blank passwords or weak passwords and violate the password policy.

It is safer to use server-side scripting so that validation is done at the server as opposed to the client.

Safe usage

// Server side code
if (Request.RequestType == "POST")
{
//Input validations
string password = Request.Form["password"];
if (password != null && password.Length >= 6)
{
//Code to save the password to the database
}
else
{
Response.write(‘The password should be at least 6 characters in
length’);}
}

Validate datatype, length, range

Before checking whether input data satisfy application rules, check the datatype, length and range of the input. Input values assigned to variables are stored in memory in the form of ‘buffers’ which have finite capacity. Some platforms do not constrain the input values within the finite ‘buffer’ space. The input values may overflow the allocated buffer and lead to denial of service. If the inputs are crafted to be executed at the server they may give an adversary administrative access over the machine. This is known as a buffer overflow attack. Passing input values with conflicting datatypes or ranges can cause the application to behave abnormally and may inadvertently disclose sensitive data.

Allow only known input

An application can classify what is ‘good data’; this is also known as a ‘white list’. This is especially true when dealing with input values of string/character types. Determine the standard input types that the application will store, evaluate and display. Some of the common input types that have well-defined formats are as follows:

  • Zip or postal codes

  • Social security numbers

  • IP addresses

  • Customer IDs

  • Credit card numbers.

Depending on the company’s password policy, usernames and passwords can also be included in the list. Use regular expressions to validate well-defined formats.

Unsafe usage

In this unsafe example, the input is not validated:

<form name = Payment>
Enter your Last Name:
<br />
<Input type ="TextBox" name="Last Name" />

Enter your Account Number:
<br />
<Input type ="TextBox" name="Account Number" />
Enter the Amount:
<br />
<Input type ="TextBox" name="Amount" />
<br /><br />
<asp:Button Text="Submit" runat="server" />
<br /><br />
</form>

Safe usage

In this safe example, the inputs are validated using two input validator controls in ASP.Net, the range validator and the custom validator. These controls execute on the server and verify the input.

<script language="vb" runat="server">
Sub ValidateAcc(sender as Object, args as ServerValidateEventArgs)
Dim Acc as String = Cstring(args.Value)
If Acc.Length < 13 then
args.IsValid = False
Exit Sub
End If
args.IsValid = True
End Sub
<html>
<body>

<form name = Payment runat = ‘server’>
Enter your Customer ID:
<br />
<asp:TextBox id="CustID" runat="server"/>
Enter your Account Number:
<br />
<asp:TextBox id="Acc" runat="server"/>
Enter the Amount:
<br />
<asp:TextBox id="Amount" runat="server"/>
<br /><br />
<asp:Button Text="Submit" runat="server" />
<br /><br />
<asp:RangeValidator id="AmountRangeCheck"
ControlToValidate="Amount"
MinimumValue="10.00"
MaximumValue="9999999.99"
Type="Double"
EnableClientScript="false"
Text="The amount must be between 10 to 9999999.99"
runat="server"/>
<asp:CustomValidator id="AccLengthCheck"
ControlToValidate="Acc"
OnServerValidate="ValidateAcc"
ErrorMessage="Invalid Account Number"
EnableClientScript="false"
runat="server" />
<asp:RegularExpressionValidator
ControlToValidate="CustID"
ValidationExpression="C[0-9]{5}"
// enter a Customer ID that starts with the uppercase letter C and contains
no more, or no fewer, than five numerals
Text="Invalid Customer ID"
EnableClientScript="false"
runat="Server" />
</form>
</body>
</html>

Reject known bad input

It may not always be possible to define the acceptable format of inputs that the user may submit. Wikipedia is an example – it allows users to add HTML content for others to view or see. Attackers may submit malicious javascript that executes whenever an unsuspecting user accesses the page on the wiki. This is the cross-site scripting attack described in Chapter 6. In a wiki, it is almost impossible to define the range of acceptable inputs since it is a very wide range indeed.

In such cases the application should be able to reject known bad inputs, e.g. the script tags (<script></script>) in this example.

In ASP.NET the HttpRequestValidationException object checks all input data against a hard coded list of HTML elements and reserved characters. To use this object you need to set the ValidateRequest attribute to true at the web.config file. Alternately, it can be enabled or disabled for each page.

Rejecting known bad input or ‘black listing’ has disadvantages though. The context of ‘bad input’ may change with the requirements and environment of each application. Newer attacks that do not use the black-listed element will breach these filters too.

The MSDN site lists the common HTML tags that could allow an adversary to inject script code:

<applet>
<body>
<embed>
<frame>
<script>
<frameset>
<html>
<iframe>
<img>
<style>
<layer>
<link>
<ilayer>
<meta>
<object>

Sanitise input

In addition to the above techniques for input validation, the application can sanitise certain types of input value. It may be difficult to define a range or format for certain inputs, typically because of the very wide range of inputs possible. This leads to the inability to manage a ‘white list’ or a ‘black list’. In such cases it is safer to parse data and convert them into literal values that will not be executed. For example, the application allows posting of messages in bulletin boards where several users can add any possible text for others to view. Commonly followed techniques for sanitising inputs in web applications are HTML encoding and URL encoding to wrap data and treat them as literals.

In .NET we use the HttpUtility.HtmlEncode and the HttpUtility.UrlEncode methods to achieve HTML and URL encoding. These methods replace or encode characters that have a special meaning in HTML like < and ‘ to &lt and &qout. Encoded characters are rendered as harmless HTML text by browsers.

Sample usage

//Encoding User Input
HttpUtility.HtmlEncode(Request.Form(‘Comments’))
//Encoding URL Strings
HttpUtility.UrlEncode(urlString)

Modularise input validation

It is best to use the above input validation techniques in tandem with a centralised approach of keeping ‘white lists’ and ‘black lists’ in shared libraries or classes. Using libraries helps in applying validation rules consistently and managing application code better. The class diagram below shows a possible modular implementation:

Input validator

Black list

White list

+ CheckLimits

+ CheckSQLInj

+ CheckXSS

+ SanitiseInput

The class has predefined its set of ‘good’ and ‘bad’ inputs, based on which it defines methods like ‘CheckSQLInj’ and ‘CheckXSS’. A good way of implementing this class would be to derive it in other classes of the application and override its methods if necessary in order to implement custom input validation.

Authentication guidelines (ISO27001 A.11.5.2)

Authentication is a security feature which allows only users with valid logins to access an application. But as Michael Howard and David LeBlanc write in their book Writing Secure Code, ‘Security Features! = Secure Features’. Similarly, an authentication feature by itself does not guarantee security to the application. You need to secure this feature in order to protect the credentials of valid users from being stolen.

Defend against password guessing

In password-guessing attacks an adversary tries to obtain the password by systematic trial and error. Here are the defences:

Enforce strong passwords (ISO27001 A.11.5.3)

This makes it difficult for attackers to crack passwords. Best practices for selecting secure passwords are as follows:

  • Require a minimum of eight character passwords.

  • Enforce use of alphanumeric passwords.

  • Enforce use of UPPER and lower case characters.

  • Enforce user of special characters (for example, %, $, #, @).

These are useful for protecting against automated password guessing attacks. However an adversary may ‘intelligent guess’ passwords too. Examples are passwords that closely resemble the username or other common names followed by numerals and special characters like ‘test1234!’, ‘username1234$’, etc. The simplest solution to protect against such attacks is to:

  • Deny setting of passwords based on a blacklist of weak passwords.

  • Deny setting passwords similar to the username or login id.

  • Educate the users to use pass phrases such as ‘Asterixhasgonehome’.

Note: A pass phrase consists of a proper combination of upper and lower case letters, numbers and special characters which form a meaningful phrase, making them easier to remember for the users.

Enforce account lockouts (ISO27001 A.11.5.1)

Another security feature that can go hand in hand with enforcing strong passwords is the feature of locking out login accounts after a specified number of failed login attempts. The account lockout can be imposed for a specified period. This will make it more difficult to narrow down the possible passwords of valid users.

Use CAPTCHAs

This security feature is used by web applications to defend against automated password guessing attacks. CAPTCHA is an acronym for ‘Completely Automated Public Turing Test to Tell Computers and Humans Apart’ which was pioneered by researchers at Carnegie Mellon University. CAPTCHAs are distorted images of words: they are easily recognisable by humans, but pose a difficult task for automated tools or scripts to decode. Hence they enforce human input to a particular web page. CAPTCHAs are especially useful when used in applications that have easily guessable login ids – such as credit/debit card numbers. An adversary’s automated script could generate these login ids and lock out all accounts in the application by repeatedly making wrong guesses.

Here are the best practices to follow for implementing CAPTCHAs securely:

  • Dynamically generate an image.

  • Send it to client with a random token.

  • Accept user input along with token.

  • Compare user input with the correct word for token.

  • Invalidate the token after one use.

The server should dynamically generate an image and send it to the client along with a random token. The server remembers the actual word in the image and the token sent. The user input is received along with the token and then compared with the correct word for the token. The server should invalidate the token after one use, so attackers cannot replay a correct request:

Set Autocomplete = OFF

Browsers have a feature to remember the recently typed web addresses, web form entries, usernames and passwords. When a user starts typing, the browser suggests possible matches. This feature is known as ‘Autocomplete’ in IE. If a browser is configured for ‘AutoComplete settings/Remember Passwords’ to remember username and password, then every time a user logs into the application, the browser asks the user to remember the password. If the user had accidentally or intentionally clicked ‘Yes’, then an adversary can enter the application with the help of the stored credentials of the previous user.

The application should ensure that the Autocomplete attribute for all sensitive fields is set to OFF. It can be done by either of these commands:

<form AUTOCOMPLETE=‘off’> – for all form fields <input
AUTOCOMPLETE=‘off’> –


for just one field E.g. <input name=‘txtPassword’ type=‘password’
id=‘txtPassword’ class=‘elm200’ Autocomplete=‘OFF’ />

The browser will not prompt the user to remember the password if the above attribute is set to ‘OFF’. Even if the browser has the ‘AutoComplete settings/Remember Passwords’ configured to remember the password, the attribute set in the code overrides the browser settings.

Implement a secure ‘Remember me’ feature

The ‘Remember me’ feature is built into applications for the convenience of users so that they need not type in their login details each time they visit the website. This feature is usually seen as a checkbox just below the login and password text boxes. Once a user enables the checkbox, the application sets a persistent cookie. This cookie stores the user’s login credentials. An adversary can steal the cookie which stores the login credentials and get access to the user’s details. We shall see some of the ways in which this feature can be implemented securely.

Never store passwords in cookies

Follow the rule of thumb – do not store confidential or secret information on a client machine. We recommend that instead of passwords, the application should store a random and unique token in the persistent cookie. Remember which token has been assigned to which user. Store the mapping in the database. That prevents the passwords of the user being disclosed through cookies. Additionally, set the cookie to expire after a specified number of days.

Demand the password before critical operations

For applications that perform transactions and store and display other sensitive data of users (such as bank accounts and credit card numbers) it is recommended to ask for passwords when the user proceeds to:

  • Make a transaction

  • View account details

  • Change a password

Once the user’s login credentials are authenticated successfully a separate session cookie can be set to remember the user for the particular session.

Implement a secure ‘forgot password’ feature

When a user has forgotten his password, a password recovery feature is called for. One challenge to implementing the password recovery feature is that the only verifiable identifier which is known to the user is his password. A common implementation of the ‘forgot password’ feature is to ask the user for his username or email or any other verifiable identifiers (favourite colour, birth date, zip code, etc.). If the answers are correct then the password is mailed to the user’s email address. There are a few risks associated with this:

  • Usernames, email, birthdates, colour, etc., may not be ‘secret’.

  • An automated tool can initiate password recovery requests for hundreds of genuine users, resulting in spam.

  • Passwords sent in clear text mails can be sniffed by an adversary.

Here are some of the best practices for a secure ‘forgot password’ feature implementation:

  • Implement a multi-stage validation process

    Ask the user certain details which were provided by him during registration, such as ‘zip code, date of birth, last name’, etc. On providing correct answers to these, the user should be directed to answer the secret question which he provided during registration.

  • For valid users, send an email with link to ‘Select a new password’.

    The mail should be sent to the user’s email-id (preset and confirmed by the user at the time of registration) only after verifying the answer to the secret question. The mail should contain a secure link to ‘Select a new password’ form.

  • Invalidate the link soon after password is changed.

    The link should contain an ID or token number which gets invalidated after the password has been changed.

Implement a secure ‘change password’ feature

Applications must incorporate a feature to allow users to change their passwords. The ‘change password’ feature must validate the old password of the user before setting the new password.

Protect against ‘browser refresh’

Browsers ‘remember’ all the GET and POST requests made to the website as long as the browser instance is running in memory. The ‘Refresh’ button enables the browser to resubmit all the HTTP request variables that were used to fetch the current page. Web applications implement the login process in such a way that the login page submits passwords to a server side page which not only verifies the passwords but also displays the home page of the user. This would mean that the page that does the verification (for example, home page) is displayed to the user. Suppose a valid user logs out of the application but forgets to close the browser. An adversary having physical access to the machine can use the user’s browser window to go to the page that was displayed just after the login page (in this case the home page). Now clicking the browser refresh button will result in the username and password of the previous user being re-submitted. An adversary can steal the login credentials by intercepting this request (as discussed in chapter 6).

We can resubmit the request with the login credentials by refreshing the page – and that’s the cause of the problem. The application can prevent this problem by introducing an intermediate page after the login page. After the user is authenticated, the application should set a token and send a response that redirects the user to the next page. Check the token in this page to verify that user is logged in.

Safe usage

//Server Side Code
If (AuthenticateUser(user,pwd)
{
FormsAuthentication.SetAuthCookie(userID,false); //Set the auth token
Session[‘userID’] = userID;
Session{‘user’] = user;
Response.Redirect(‘Home.aspx’); //Redirect to the next page
}
Else
{
Response.write(‘<script> alert(‘Please check Login
credentials’)</script>‘);
}

Guidelines for handling sensitive data (ISO27001 A.10.7.3)

Sensitive data needs to be secured when in transit and when stored. By sensitive data we mean credit card numbers, bank account details, PIN, highly confidential or secret data such as passwords, and database connection strings. In this section we review some of the ways we can secure sensitive data against being stolen from the network, the server or the client.

Secure sockets layer (ISO27001 A.12.3)

The most commonly used solution today in most applications to secure sensitive data in transit is secure sockets layer or SSL. SSL guarantees confidentiality by the use of encryption ciphers and non-repudiation by use of digital certificates that are signed by a trusted certification authority and the use of public keys.

  • Use SSL for pages/forms that send sensitive data to server.

  • Use SSL when sensitive data is being sent to third-party sites.

Using the right cryptographic algorithm

Implementing custom encryption algorithms is a futile activity for the following reasons:

  • The algorithm may not be strong enough to defend against various encryption-cracking attacks.

  • A great deal of effort is required to build a secure custom algorithm and get it approved by experts.

It is safer and simpler to use widely-used, scrutinised secure algorithms. Many platforms provide support for libraries that implement standard cryptographic algorithms.

Unsafe usage

Public Function Encrypt(ByRef password As Variant) As Boolean

<snip>
For i = 1 To Len(password)
charPos = charPos + 1
newChar = Asc(Mid$(password, i, 1)) – (10 + charPos)
newPass = newPass & Chr$(newChar)
Next i
password = newPass
Encrypt = True
End Function

Symmetric key encryption algorithm

In this type of encryption the same key is used for encrypting and decrypting the data. The greater the length of the key the better are the chances of defending against brute-forcing and other types of cracking attacks. One of the longest-standing and secure algorithms is Triple-DES (3DES). With a key-length of more than 112 bits it is considered reasonably safe.

It is advisable to use encryption when the data is accessed only one time or only for a brief period by the application. Encrypting database connection strings is ideal, as once the connection is established between the application and database there may be no need to access or use the connection string for the period of an active user session or application instance. Similarly, an application can encrypt sensitive data that may need to be retrieved in its original form and displayed back to the user, such as bank account numbers, or may need to be used by third-party sites for verification, such as credit card numbers. The keys used for encryption need to be stored in a secure location and only the application or server-side code should have access to it.

Hashing algorithms

Hashing is a technique by which the original string or text is converted to a fixed length text or string which is a representation of the original. This converted or ‘hashed’ text cannot be decrypted or converted to get back the original text. Thus it is also known as one-way cryptography. A good hashing algorithm makes it difficult to determine the original string or text from the hashed text. MD5[55] and SHA1[56] were popular hashing algorithms. Today they are not considered safe anymore. SHA-256 and SHA-512 are stronger, safer hashing algorithms.

Since passwords are only used for verification during login authentication their hashed values can be stored in the database. This prevents the original passwords from being stolen, even by the administrator who has access to the database. It is safer to store passwords salted[57] and hashed to protect against ‘rainbow cracking’[58] attacks. The salt can be stored along with the user-id. During authentication the hash of the passwords supplied by users to the application is recomputed with the salt and compared with the corresponding hashed password stored in the database.

Message transfers between different business entities over the internet also need to be secure. For instance, when a merchant website directs a potential buyer to a bank’s payment website for providing payment information, it transfers all the purchase details via the user’s browser to the bank’s website. In such a scenario it is essential that the purchase details reach the bank’s website without being altered. This message integrity can be achieved using hashing techniques whereby the sending entity hashes the message details using a pre-shared key and sends the message plus the hash to the receiving entity, which then computes the hash of the received message details (using the same key) and compares it with the received hash to verify that the message details weren’t tampered with in transit.

Protect cryptographic keys

The strength of cryptography relies on the length of the key used. It is necessary to safeguard keys in order to protect encrypted data. Here are some guidelines to generate and secure cryptographic keys:

  • Generate random cryptographic keys unique to the server.

  • Store and manage the keys independently of the application.

For .Net applications, the Microsoft Data Protection API (DPAPI) provides the functionality to safeguard cryptographic keys. It encrypts and decrypts data using functions CryptProtectData and CryptUnprotectData that use the 3-DES algorithm. The DPAPI can generate and store two types of cryptographic keys: user-specific and machine-specific. The former requires an application to load a specific Windows operating system user-profile before making the API[59] calls. The latter can be used by any application running in the same machine or server. The machine-specific key approach is not entirely secure because any malicious application (even one running as ASP.NET) installed on the server can decrypt data encrypted by machine-specific keys. The user-specific key approach provides security as it limits the user who can access the key to encrypt or decrypt the data. However, using the user-specific key approach would involve additional development effort and performance overhead as it requires a specific user profile to be loaded using a Windows service component. A simpler approach is to use a machine-specific key approach with an optional pass phrase that can be embedded into the application and passed with API calls. Without the pass phrase and the machine-specific key the data cannot be decrypted. The pass phrase can be set programmatically when the application is started for the first time. This is not a fool-proof approach, as it is still dependent on the application to decrypt the data, but it saves a lot of development effort.

Prevent sensitive data from being stolen from client

Applications may inadvertently leave a lot of data on the client computer in the form of trace/log files, temporary files, history files, etc. In many cases these files contain information that would otherwise be accessible only to authenticated users of the application. There is the threat of such information being ‘cached’ by the client browser and an adversary with physical access to the machine can steal the sensitive data. Ensure that no sensitive information is passed on to log files or history files on the client. In one case, we saw an application that logged the change password event, and stored both the old and the new passwords in the log!

Sensitive data in query strings

All HTTP-GET requests that are accessed from the browser are stored in the browser’s history and can be viewed even after the user has logged out of the site and closed the window. These links can be viewed by clicking on the ‘history’ button. If any sensitive information is passed in the GET requests, an adversary can access it from the history. Web applications should not use query string variables to store sensitive data such as usernames, passwords, credit card numbers, etc. They must use the POST method to pass parameters to the server.

Unsafe usage

<form name="f1" action="auth.asp"> // No HTTP method specified
User Name       <INPUT type="text" id=loginid name=loginid> <br>
Password        <INPUT type="password" id=pwd name=password>
<br>
<INPUT type="submit" value="Submit" id=submit1 name=submit1>
</center>
</form>

The above code sends the login-id and password as query string variables.

Safe usage

<form name="f1" method="post" action="auth.asp">
User Name      <INPUT type="text" id=loginid name=loginid> <br>
Password        <INPUT type="password" id=pwd name=password>
<br>
<INPUT type="submit" value="Submit" id=submit1 name=submit1>
</center>
</form>

This code sends the login-id and password as HTTP-POST variables.

Sensitive web pages cached

Browsers have a feature of remembering or caching pages once they have been served up to the browser from a web server in order to improve future performance. Pages with sensitive information may also get stored in the cache. An adversary can steal this information from the cache.

In IE these pages are stored in:

C:\Documents and Settings\username\Local Settings\Temporary Internet Files\

and in Firefox in:

C:\Documents and Settings\Username\LocalSettings\ApplicationData\ Mozilla\Firefox\Profiles\5rrn80xr.default\Cache

Caching can be prevented by setting the proper cache control attributes in the response header:

Cache-control: no cache

Cache-control: no store

The first attribute tells the browser not to use the information that is cached for that particular request-response pair. It indicates that the browser must send the request to the server each time. No-cache can also be specified for certain fields alone, in which case the rest of the page may be displayed from cache. If no field is specified, then no part of the page can be displayed from cache.

No-store indicates that no part of the request-response pair should be stored. This applies to the entire page and the browser will not store any part of it in its cache.

Session management guidelines

Once the application successfully authenticates a valid user the application needs to ‘remember’ the user for all subsequent connections or requests from the user.

Maintaining and identifying the state of a particular user across different user requests is called session management and is a very essential component of all applications. Web applications identify session states using session tokens managed by the web server. Each time a request is sent to the web server the browser attaches the session token stored in a session cookie to the request. The web server identifies an authenticated user based on the session token associated with each request. Since these session tokens hold the key to authenticated sessions, it is necessary to protect these session cookies from being hijacked or stolen.

Use unique and random session tokens

An adversary tries to hijack an authenticated session by trying to guess the value of the session token. If session state is managed by the application, then it should ensure that it uses unique and random session tokens that are not easily guessable. In web applications, the application server or web server manage session tokens. Platforms like .Net, J2EE, Ruby on Rails[60] use unique and random session tokens.

Store session tokens in secure cookies

Browsers use session/authentication cookies to store the session tokens and to transmit them to web servers. The following best practices can be applied to prevent stealing of session tokens:

  • Use SSL to secure transmission of session cookies.

  • Use non-persistent, secure, HTTP-only cookies.

In ASP.NET the practice is to create an authentication cookie once login is successful. This authentication cookie identifies a user for subsequent requests to the application.

Invalidate session tokens on logout

Ideally, the application should stop ‘remembering’ the user once signed out of the application. Essentially, the application should be able to purge session tokens belonging to the corresponding user immediately. This can be done by invalidation of session tokens at the server side. In ASP.NET, where authentication tokens are used to identify user sessions, this can be achieved using the ‘FormsAuthentication.Signout’ method.

Timeout sessions (ISO27001 A.11.5.5)

We have seen so far how to secure user sessions by securing the session identifiers or tokens, and by invalidating them when the client exits from the application. What happens if the client is abruptly terminated or the user leaves his desktop when the session is active? The session tokens are still valid. There is a possibility that an adversary can get access to valid session tokens and access the application. In order to prevent this we need to set an appropriate timeout for inactive authenticated sessions. In ASP.NET we can configure the timeout property of the authentication cookie. This property sets the amount of time in minutes the session is allowed to remain inactive before it expires or times out.

Using Web.Config to secure sessions

In ASP.NET, the Web.Config file contains global configuration settings that are applied by default to the website. For secure session management the following attributes can be set in the <forms> tag:

name =‘[cookie name]’ – Name of Authentication Cookie
loginUrl = ‘[url]’ – Link to the Authentication page
timeout = ‘[minutes]’ – Sets the duration of time for cookie to be valid
path=‘/;HttpOnly’ – Sets the path of the cookie
httpOnlyCookies=‘true’ Protects against XSS attacks
requireSSL = ‘true’ – Authentication cookie sent only over SSL connection

Error handling and logging (ISO27001 A.10.10.5)

Applications experience errors due either to incorrect or invalid user inputs or to abnormal system behaviour such as database or server crashes. Based on this an application needs to handle at least two types of error: system errors and programming logic errors. To avoid logic errors, it is essential to write robust code that leaves very little chance of errors. The code must handle all expected and unexpected error conditions. The application should also be able to log a variety of errors useful for future log analysis. In the case of system errors not directly under the control of the application, custom error pages can be displayed that will allow users to gracefully exit the application.

Use Try...Catch...Finally

Many coding languages today provide a structured way of handling errors. They not only allow handling errors but also provide for the application to exit gracefully. ‘Try...catch...finally’ is one such construct. It is a C++ like structured statement to handle exceptions. When we attempt to execute some code and the code throws an exception, the runtime checks whether the exception has been handled by any of the ‘catch’ blocks. Then we may execute appropriate clean-up code. This ensures that all exceptions thrown by the application are captured and handled appropriately and avoids abnormal conditions occurring in the application.

The ‘finally’ is especially important to release all resources, such as open database connections, that might stay open when an error changes the flow of code and skips the normal call to close.

Unsafe usage

<snip>
strSql = "insert into user (username, password) values (‘john’, ‘john123’)"
mySqlCommand = new SqlCommand(strSql, mySqlConnection)
Try
mySqlConnection.Open()
mySqlCommand.ExecuteReader(CommandBehavior.CloseConnection)
Message.text = "New user added"
mySqlConnection.Close()
Catch SQLexc as sqlexception

Message.text = Message.text + sqlexc.tostring()
End Try

Safe usage

<snip>
strSql = "insert into user (username, password) values (‘john’, ‘john123’)"
mySqlCommand = new SqlCommand(strSql, mySqlConnection)
Try
mySqlConnection.Open()
mySqlCommand.ExecuteReader(CommandBehavior.CloseConnection)
Message.text = "New user added"
Catch SQLexc as sqlexception
Message.text = ‘User could not be added’
Finally
mySqlConnection.Close()
End Try

Sanitise error messages

Display custom error messages which reveal no application architecture details to users.

Use generic error messages

When a user enters a wrong username or password, keep the error message very generic. Error messages like ‘Invalid login’ or ‘Incorrect username or password’ do not reveal the verification logic of the application. On the other hand, a message like ‘Incorrect password’ reveals that the username is valid and only the password is wrong.

Remove any code for capturing and printing application debugging errors. The code used to print debug messages may reveal details of the application and database architecture.

Unsafe usage

//Code used to do a database operation
Catch SQLexc as sqlexception
Message.text = Message.text + sqlexc.tostring() //captures database
errors and prints back to the user interface.
End Try

Safe usage

//Code used to do a database operation
Catch SQLexc as sqlexception
Message.text = ‘User cannot be added’ //captures database errors and
prints back to the user interface.
End Try

Use custom error pages

Some errors, such as ‘Page not found’, ‘Internal Server error’, etc., are generated by the web server. To handle such cases, configure custom error pages to display generic error messages. This ensures that no sensitive information is displayed to the user even when unexpected system error conditions occur. There are two ways to configure custom error pages in .NET:

  1. Configure an error page for each web page.

  2. Configure a default error page for the application.

In Web.config, set the custom errors tag to configure an error page for the login page. In the example below, the error page for loginForm.aspx is setup as loginError.aspx.:

<customErrors

mode="RemoteOnly">
loginForm.aspx = ‘loginError.aspx’
To configure a default error page for the application add the following to
the <customErrors> tag in the web.config file
defaultRedirect=‘="~/errors/GeneralError.aspx’

Logging key events (ISO27001 A.10.10.1)

Logging helps in maintaining a trail of all user activities, both legitimate and erroneous. It helps detect any security violations or flaws in the application. It is essential to decide what events to log and the level of detail to be captured. Depending on the functionality of the application the following events can be logged:

Account administration

  • Adding and deleting of user accounts.

  • Assigning user privileges.

  • Resetting user passwords.

User access

  • Success and failed login attempts.

  • Account lockouts.

  • Password changes.

System errors

  • Web server errors.

  • Database errors.

  • Application framework errors.

Secure log files (ISO27001 A.10.10.3)

Since logs contain data about user activities it is essential to secure them too. As a best practice the following guidelines should be followed:

  • Store logs separately from the application server.

  • Application should only have append access to logs.

  • Provide facility to view logs via a user interface.

  • Archive logs periodically.

Miscellaneous guidelines

We have discussed how applications can securely handle inputs, authenticate and manage user sessions, store and transmit sensitive data and, finally, handle errors and generate audit logs for legitimate and invalid events in the application. All these make secure and robust applications. Next, we shall look at some of the other areas of application security that may require special attention.

Upload files securely (ISO27001 A.10.9.3)

File uploads can be an integral part of applications such as corporate portals, wikis, etc. Many financial sites interface with third parties for daily uploading of transaction files for ‘end of day’ processing. In order that files are uploaded securely the following guidelines should be observed:

  • Upload files only over SSL connection.

  • Check file size, extensions, and formats before storing on the server.

  • Do not allow execute permissions on server folders to the application.

  • Send a hash of the file contents/header information along with the file for checking the integrity of file data.

Sample usage

<snip>
Protected Sub Button1_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
If FileUpload1.HasFile Then
Dim fileExt As String
fileExt = System.IO.Path.GetExtension(FileUpload1.FileName)
If (fileExt = ‘.doc’ || fileExt = ‘.xls’) then //Upload only .doc or .xls files
Try
FileUpload1.SaveAs("C:\Uploads\" & _
FileUpload1.FileName)
<snip>
Else
Label1.Text = ‘Only Document or Excel files!’
End If
Else
Label1.Text = "You have not specified a file."
End If
End Sub

By default, the maximum size of the file that can be uploaded at a time is 4mb.

Download files securely (ISO27001 A.10.9.3)

Many applications offer content in the form of downloadable files (text, PDF, Excel, etc.). These could be salary slips for staff, bank statements, transaction records, etc. An adversary may be able to access files directly without authentication as these files will be stored on a publicly accessible directory on the server. In order to achieve secure rendering of files to legitimate users the application should be able to render the content real time, i.e. by:

  • Checking the authenticity of the user requesting the file.

  • Creating the content in real time.

  • Constructing the response to be sent to the client (in the case of web applications, set the content-type tag).

  • Also, the application should not store the content locally on the client.

Unsafe usage

//Creating/Opening the file for writing
Textwriter textwriter = new streamWriter(filename);
textwriter.WriteLine(dReader.GetValue(0).ToString()+’
‘+dReader.GetValue(1).ToString()+”+dReader.GetValue(2).ToString()+’
‘+dReader.GetValue(3).ToString()+”);
textwriter.Close();
................
//Redirecting user to text file
Response.Redirect(‘statement/AccStatement_’+userID+’.txt’);

Safe usage

//Opening a file stream
Filestream stream = new Filestream(Server.Mappath(filename),
Filenode.Create, Fileaccess.ReadWrite, Fileshare.Read);
//Filling Contents in file
............................

//Setting the contenttype tag in response
Response.Contenttype = ‘html/text’;
//Using the stream to send the data to the response
int bufsize = (int)stream.length;
byte[] buf = new byte[bufSize];
int bytesRead = stream.Read(buf, 0 ,bufsize);
Response.OutputStream.Write(buf, 0 , bytesRead);
Response.End();
}
Finally {
stream.close();
}

Use parameterised queries

Dynamic SQL queries are formed by directly feeding the user input as conditional values into the SQL query. The SQL query thus formed is then parsed and executed. This can lead to SQL injection attacks on the database. The .NET framework provides prepared statements that pre-compile the query. These prepared statements use the user input as parameters to the already parsed SQL query. Hence they are also known as parameterised queries.

Unsafe usage

string strSqlQuery = ‘Select * FROM dBank_users where & _username =
“ + TxtUsername.Text + “ and & _password = “ + TxtPassword.Text + “;’;
//An input of ‘ OR 1 = 1-- will convert the query to ‘Select * from
dBank_users where username=“ OR 1=1-- and password=“;’
//Database will return all rows and the application will take the first row
and validate the user.

Safe usage

//Parameterised Query

SqlCommand commandString = new SqlCommand(‘Select * FROM
dBank_users where username = @User and password = @Pwd’,
connectionString);
//Adding the parameters
commandString.Parameters.Add(new
SqlParamter(‘@User’,SqlDbType.VarChar);
commandString.Parameters[‘@User’].Value = TxtUsername.Text;
commandString.Parameters.Add(new
SqlParamter(‘@Pwd’,SqlDbType.VarChar);
commandString.Parameters[‘@Pwd’].Value = TxtPassword.Text;

Validate all business rules

In addition to authentication and session management the application should validate all the business rules defined prior to processing any request from the user. Each application will have certain business rules defined based on its functionalities. Business rules for the funds transfer facility of an online banking application, for example, should specify that the debit account should belong to the logged-in user, the amount should be greater than 0, etc. This is very important as it defends against legitimate users of the application trying to access or modify another user’s details by parameter manipulation.

Use least privilege (ISO27001 A.11.2.2)

Last, but not the least, the application itself should run with as few privileges as it really needs – nothing more.

Use low-privileged operating system user

Each process runs under the privilege of an Operating System user. Configure the application so that it executes as a low privileged user. If an adversary exploits any vulnerabilities in the application, the damage will be restricted to the domain of the application itself.

Most web servers today can be configured to run applications under low privileges. IIS 6.0 allows assigning of web application pools to each web application and each pool can run as a separate process under the privileges of a separate Windows user.

Use low-privileged database user

A database could be used by many applications. Applications connect to the database using database logins. These logins should have low privileges on the database. Even if that login is compromised, the damage is restricted to the data of the vulnerable application. Use a low-privileged database user, e.g. APPDATA.

In this concluding chapter, we have seen how to implement the controls required to secure an application in line with ISO27001. When designers and developers follow these best practices, the SDLC consistently bakes a secure application.



[55] Message-digest algorithm 5 is a cryptographic hash function with a 128-bit hash value.

[56] Secure hash algorithms (SHA) were designed by the US National Security Agency (NSA). SHA1 produces a 160-bit message digest, whereas SHA-512 produces a 512-bit long message digest.

[57] A salt consists of random bits of data used as inputs to the creation of a cryptographic key; a salt value can be used as a key in a hash function.

[58] A ‘rainbow cracking’ attack uses readily available databases to accelerate the time required for cracking hashed passwords.

[59] An application programming interface (API) is a source code interface that enables an application to support requests from another application.

[60] ‘Ruby on Rails’ is a free, open source web application framework.