Best practices: Secure ASP.NET Web with DevExpress: A2 - Broken Authentication and Session Management

Don Wibier's Blog
12 November 2014

I’ve had a lot of experience in dealing with writing secure ASP.NET websites. I’ve created a custom CMS framework using DevExpress ASP.NET and been through several security audits with this project. I’d like to share my experience with you in using best practices to make your websites secure. In this series of blog posts, I'll explain common web security topics and how you can make sure your websites are secure.

Open Web Application Security Project

OWASP is one of the most well-known organizations which focuses on improving security of software. They have published a checklist with common design errors and issues. The OWASP Top Ten represents a broad consensus about what the most critical web application security flaws are.

Let’s see what this type of attack is about and how to fix it:

A2 - Broken Authentication and Session Management

Session variables are great for maintaining a state or keeping some personalized information at hand in a web application. These variables are stored on the server and are globally accessible throughout the application. After a certain time of inactivity of a user, they are also cleaned up automatically.

Every user has its own instances of these variables so the data is not exposed to any other users. 

Hackers, however, try to exploit any weakness to gain access to other user’s session data.

Lets see how the ASP.NET framework handles this so we can see if there are ways to tamper with this.

In general, there are roughly 3 ways to identify a session:

  • Specifying a sessionID as part of the URL or QueryString parameter with all URL’s and requests to the web-server.
  • By submitting a (hidden) FormField with some sessionID with all requests
  • Storing a sessionID in a cookie which will be send over with all requests

By default, ASP.NET uses the last method since it has the least side affects, but for the test we will tell the ASP.NET framework to use the first method by specifying the following in the web.config of a new empty project:

<system.web>
  <sessionState cookieless="true" />
</system.web>

Next we create a simple default.aspx page which looks like this:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication8.Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
          <asp:Panel runat="server" ID="pnlAnon">
                <fieldset>
                     <asp:Label runat="server" ID="lbUsername" AssociatedControlID="tbUsername" Text="Username" /><br />
                     <asp:TextBox runat="server" ID="tbUsername" /><br />
                     <asp:Label runat="server" ID="lbPassword" AssociatedControlID="tbPassword" Text="Password" /><br />
                     <asp:TextBox runat="server" ID="tbPassword" /><br />
                </fieldset>
                <asp:Button runat="server" ID="btLogin" Text="Login" OnClick="btLogin_Click" />
          </asp:Panel>
          <asp:Panel runat="server" ID="pnlLoggedIn" Visible="false">
                <asp:Label runat="server" ID="lbWelcome" /><br />
                <asp:Button runat="server" ID="btLogout" Text="Logout" OnClick="btLogout_Click" />
          </asp:Panel>
    </div>
    </form>
</body>
</html>

With the following code-behind:

namespace WebApplication8
{
     public partial class Default : System.Web.UI.Page
     {
          protected void Page_Load(object sender, EventArgs e)
          {
                if ((!IsPostBack) && (!IsCallback))
                {
                     UpdateControls();
                }
          }
          protected void UpdateControls()
          {
                pnlAnon.Visible = String.IsNullOrEmpty((string)Session["Username"]);
                pnlLoggedIn.Visible = !pnlAnon.Visible;
                if (pnlLoggedIn.Visible)
                     lbWelcome.Text = String.Format("Welcome {0}!", Session["Username"]);

          }
          protected void btLogin_Click(object sender, EventArgs e)
          {
                Session["Username"] = tbUsername.Text;
                UpdateControls();
                Response.Redirect("~/");
          }

          protected void btLogout_Click(object sender, EventArgs e)
          {
                Session.Remove("Username");
                UpdateControls();
                Response.Redirect("~/");
          }
     }
}

As you can see, the code is pretty straight forward; when no session variable is present, we show the pnlAnon which allows us to enter some username and password.

When we submit,  the entered username is placed in the session variable and the controls are updated. If you run this in your web-browser, you will see the following:

image

Because of the cookieless setting, you see a mangled URL being produced by the ASP.NET framework which inserts the sessionID into the URL.

Now we fill something in and hit Login:

image

Lets copy the URL in Internet Explorer:

image

As you can see both browsers are now working in the same session.

When removing the web.config setting, ASP.NET will store this information in a cookie named ASP.NET_SessionId and it is created if it didn’t already exists. The cookie holds the same value as the URL portion in the example above, and when you copy the cookie value from one browser to another, you will have the same effect.

This is a potential opening for a hacker which is called Session Fixation. For this to happen a hacker needs to setup a session first and next try to trick a user to go to the site by providing a hyperlink (in a fake e-mail from your bank for example). If this succeeds, the user will work in the session of the hacker. If you want to read more about these kind of attacks, there is a good article here.

If you have developed your own login mechanism in your site which relies on Session variables like the example above, you can imagine that this is a potential security issue.

By using the membership API which is available in ASP.NET from version 2 and up, you have reduced these kind of threads for a great deal. The membership API gives you all functionality you would expect for handling users and logins.

The standard configuration of the Membership API uses its own data store for persisting user details, but if you have another data store to keep your users, consider creating your own Membership provider

public class MyMembershipProvider : MembershipProvider
{
     public override bool ValidateUser(string username, string password)
     {
           //...
     }
     public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
     {
           //...
     }
     public override MembershipUser GetUser(string username, bool userIsOnline)
     {
           //...
     }
     public override bool ValidateUser(string username, string password)
     {
           //...
     }
     // override other methods you need
}

You will need to configure it in your web.config:

<system.web>
    <membership defaultProvider="MyMembershipProvider" userIsOnlineTimeWindow="10">
      <providers>
        <clear />
        <add name="MyMembershipProvider" type="MyNamespace.MyMembershipProvider, MyAssembly" 
                enablePasswordReset="true" requiresQuestionAndAnswer="false" applicationName="/" />
      </providers>
    </membership>

Optionally you could create a Role provider as well and configure it accordingly.

The membership API uses an authentication ticket which will also be stored in a cookie but it is much harder to tamper with since it holds some additional information. A nice side-effect of using the membership API is that all controls in the toolbox like the LoginControl and LoginStatus control can be used without additional coding.

So by using the membership API, we have reduced the risk of a hacker getting your sensitive information, and we can be pretty sure about the identity of a user but we still have the Session Fixation issue. There are several counter measures you can take:

Make sure the business logic don’t depend on Session variables and use as less as possible. In several of our demo projects, we use session variables but only for persisting some status in between callbacks and we also make sure we initialize them first before reading them.

If the above is not possible, you could introduce an extra cookie e.g. Auth which holds a unique generated value which will also be stored in a session variable as soon as a user logs in. Next with every page load, you can check if the cookie value is equal to the session variable.

A login method for this could look like this:

protected void btLogin_Click(object sender, EventArgs e)
{
      if (Membership.ValidateUser(tbUsername.Text, tbPassword.Text))
      {
           string guid = Guid.NewGuid().ToString();
           Session["Auth"] = guid;
           // now create a new cookie with this guid value
           Response.Cookies.Add(new HttpCookie("Auth", guid));
      }
      else 
           throw new HttpException("Invalid username or password")
}

And a logout method would look like:

protected void btLogout_Click(object sender, EventArgs e)
{
      Session.Clear();
      Session.Abandon();
      Session.RemoveAll();
      if (Request.Cookies["ASP.NET_SessionId"] != null)
      {
           Response.Cookies["ASP.NET_SessionId"].Value = string.Empty;
           Response.Cookies["ASP.NET_SessionId"].Expires = DateTime.Now.AddMonths(-20);
      }

      if (Request.Cookies["Auth"] != null)
      {
           Response.Cookies["Auth"].Value = string.Empty;
           Response.Cookies["Auth"].Expires = DateTime.Now.AddMonths(-20);
      }
}

In every page load you could then place the following code:

string sessionVal = (string)Session["Auth"];
string cookieVal = Request.Cookies["Auth"] != null ? Request.Cookies["Auth"].Value : "";
if (!Request.IsAuthenticated || String.IsNullOrEmpty(sessionVal) || 
      String.IsNullOrEmpty(cookieVal) || (sessionVal != cookieVal))
{
   // NOT Logged in (or tampered) redirect to the login page
}
//else
   //Logged in so everything is ok

For this to work effectively, you will need to give the user some incentive to use the Logout button on your site so you can cleanup the session yourself, and empty the ASP.NET_sessionId cookie so the user will receive a new one id on his next visit to the site:

Sessions expire after a certain amount of inactivity of the user. This time is by default 20 minutes. If possible, make it even shorter by setting the following web.config setting:

<system.web>
  <sessionState timeout="10" />
</system.web>

Another way of preventing Session Fixation is by changing the sessionID with every request in the cookie. This should be tested though since this might cause problems when embedding Java Applets or ActiveX controls in your page which also rely on the user session.

Alternatively, you could build your own SessionStateProvider.

As you can see, protecting your data is a serious task, but also consider the following points to reduce other types of hack attempts:

  • Install an SSL certificate on your web-site and make sure at least the login process runs on SSL
  • Try to assign a new sessionID to the user upon login
  • Store passwords encrypted in the database
  • Do not use home grown authentication mechanisms
  • Keep session time-outs short
  • Do not send one e-mail which contains both username and password (it is plain text after all)

DevExpress is Secure

DevExpress takes ASP.NET security as a top priority. We constantly test our tools and if by chance a flaw appears then we’ll notify you immediately on a fix/workaround/etc.

Leave a comment below with your thoughts on ASP.NET security.

no comments
No Comments

Please login or register to post comments.