Introduction
ASP.NET Session keeps track of the user by creating a cookie called ASP.NET_SessionId
in the user browser. This cookie value is checked for every request to ensure that the data being served is specific to that user. In many applications, a Session variable is used to track the logged in user, i.e., if a session variable exists for that user, then the user is logged in, otherwise not.
Background - The Vulnerability
Whenever any data is saved into the Session, the ASP.NET_SessionId
cookie is created in the user’s browser. Even if the user has logged out (means the Session data has been removed by calling the Session.Abandon()
or Session.RemoveAll()
or Session.Clear()
method), this ASP.NET_SessionId
cookie and its value is not deleted from the user browser. This legitimate cookie value can be used by the hijacker to hijack the user session by giving a link that exploits cross site scripting vulnerability to set this pre-defined cookie. When the user clicks this link and logs in, the user will have the same ASP.NET_SessionId
cookie value that hijackers knows and he will also be able to browse the user account and will be able to access all the information pertaining to that user. This attack is called Session fixation vulnerability.
You can get hundreds of tips and tricks like this from my blog: .NET How to Tips and Tricks.
Let’s create a demo application that shows the existence of the ASP.NET_SessionId
cookie even if the user has logged out and all Session data has been removed.
ASPX Page
<fieldset>
<legend>Login</legend>
<p>Username : <asp:TextBox ID="txtU" runat="server" /> </p>
<p>Password : <asp:TextBox ID="txtP" runat="server" /> </p>
<p><asp:Button ID="btnSubmit" runat="server"
Text="Login" OnClick="LoginMe" />
<asp:Label ID="lblMessage" runat="server" EnableViewState="false" />
<asp:Button ID="btnLogout" runat="server"
Text="Logout" OnClick="LogoutMe" Visible="false" />
</p>
</fieldset>
In the above code snippet, we have two TextBox
es, two Button
s, and a Label
control.
Code-behind
protected void Page_Load(object sender, EventArgs e)
{
if (Session["LoggedIn"] != null)
{
lblMessage.Text = "Congratulations !, you are logged in.";
lblMessage.ForeColor = System.Drawing.Color.Green;
btnLogout.Visible = true;
}
else
{
lblMessage.Text = "You are not logged in.";
lblMessage.ForeColor = System.Drawing.Color.Red;
}
}
protected void LoginMe(object sender, EventArgs e)
{
if (txtU.Text.Trim().Equals("u") && txtP.Text.Trim().Equals("p"))
{
Session["LoggedIn"] = txtU.Text.Trim();
}
else
{
lblMessage.Text = "Wrong username or password";
}
}
protected void LogoutMe(object sender, EventArgs e)
{
Session.Clear();
Session.Abandon();
Session.RemoveAll();
}
On click of the Login button, the LoginMe
method fires that creates Session[“LoggedIn”]
after validating the TextBox
es' value.
On click of the Logout button, we call the Session.Clear()
, Session.Abandon()
and Session.RemoveAll()
methods to ensure that the session variable is removed.
Output
The ASP.NET_SessionId cookie when user is logged in
Notice in the below image that when the user has logged in, an ASP.NET_SessionId
cookie has been created.
After clicking on Login, go back and refresh the page.
Now when we click on the Logout button, even if the Session has been abandoned / removed, the ASP.NET_SessionId
cookie exists.
ASP.NET_SessionId cookie even if user is logged out
After clicking on Login, go back and refresh the page.
How to fix this vulnerability
Simple fix
To avoid Session fixation vulnerability attacks, we can explicitly remove the ASP.NET_SessionId
cookie in the Logout method.
Bullet proof fix
To bullet proof this attack, we can create another cookie (e.g., AuthCookie
) with a unique value and the same value can be stored into the Session as well. On every page load, we can match this cookie value with the Session value; if both matches, then let the use enter the application otherwise redirect to the Login page.
In the Logout function, ensure that you are removing this new Cookie “AuthCookie
” as well. To remove this cookie, simply set its expiration date time to a few months earlier than the current date time.
So my modified code-behind for this page looks like below:
Modified code-behind
protected void Page_Load(object sender, EventArgs e)
{
if (Session["LoggedIn"] != null && Session["AuthToken"] != null
&& Request.Cookies["AuthToken"] != null)
{
if (!Session["AuthToken"].ToString().Equals(
Request.Cookies["AuthToken"].Value))
{
lblMessage.Text = "You are not logged in.";
}
else
{
lblMessage.Text = "Congratulations !, you are logged in.";
lblMessage.ForeColor = System.Drawing.Color.Green;
btnLogout.Visible = true;
}
}
else
{
lblMessage.Text = "You are not logged in.";
lblMessage.ForeColor = System.Drawing.Color.Red;
}
}
protected void LoginMe(object sender, EventArgs e)
{
if (txtU.Text.Trim().Equals("u") &&
txtP.Text.Trim().Equals("p"))
{
Session["LoggedIn"] = txtU.Text.Trim();
string guid = Guid.NewGuid().ToString();
Session["AuthToken"] = guid;
Response.Cookies.Add(new HttpCookie("AuthToken", guid));
}
else
{
lblMessage.Text = "Wrong username or password";
}
}
protected void LogoutMe(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["AuthToken"] != null)
{
Response.Cookies["AuthToken"].Value = string.Empty;
Response.Cookies["AuthToken"].Expires = DateTime.Now.AddMonths(-20);
}
}
LoginMe method
First, let's focus on the LoginMe
method that fires on the click of the Login button. In this method, after setting the normal Session variable, we create a GUID (a unique value and almost impossible to guess) and save it as a new Session variable called AuthToken
. The same GUID is then saved into a cookie named AuthToken
.
LogoutMe method
In the LogoutMe
method, we first explicitly expire the ASP.NET_SessionId
cookie to make sure that this cookie is removed from the browser when the user clicks on the Logout button, and after that, we expire the AuthToken
cookie as well.
Page_Load event (In real time applications, keep this logic in the Master Page Page_Load method)
In the Page_Load
event, we check for the normal LoggedIn
session variable and along with that, we also check for the new Session variable called AuthToken
and the new Cookie AuthToken
. If all three of them are not null, then again we match the new Session variable AuthToken
and the new Cookie AuthToken
values. If both are not the same, then we write a failure message (in a real time application, redirect the user to the Login page).
This logic makes sure that even if the ASP.NET_SessionId
cookie value is known to the hijacker, he will not be able to login to the application as we are checking for the new Session value with the new cookie that is created by us and their GUID value is created by us. A hijacker can know the Cookie value but he can’t know the Session value that is stored in the web server level, and as this AuthToken
value changes every time the user logs in, the older value will not work and the hijacker will not be able to even guess this value. Unless the new Session (AuthToken
) value and the new Cookie (AuthToken
) are the same, no one will be able to login to the application.
Output
ASP.NET_SessionId along with AuthToken cookie
Hope you find this article interesting, thanks for reading. Subscribe to my RSS feed to read more articles on a regular basis.
Originally posted here.