{"id":136,"date":"2011-06-30T01:18:57","date_gmt":"2011-06-30T08:18:57","guid":{"rendered":"http:\/\/peterwong.net\/blog\/?p=136"},"modified":"2011-06-30T01:18:57","modified_gmt":"2011-06-30T08:18:57","slug":"asp-net-session-and-forms-authentication","status":"publish","type":"post","link":"https:\/\/peterwong.net\/blog\/asp-net-session-and-forms-authentication\/","title":{"rendered":"ASP.NET Session and Forms Authentication"},"content":{"rendered":"<p>The title can be misleading, because in concept, one is not related to the other.&#160; However, a lot of web applications mix them up, causing bugs that are hard to troubleshoot, and, at worst, causing security vulnerabilities.<\/p>\n<p>A little bit of background on each one.&#160; ASP.NET sessions are used to keep track and keep information related to a \u201cuser\u201d session.&#160; When a web server is initially accessed by a browser, the server generates a unique session ID, and sends that session ID to the browser as the value of a cookie (the name of the cookie is <em>ASP.NET_SessionId<\/em>).&#160; Along with that session ID, a dictionary of objects on the server, often referred to as session state, is allocated corresponding to that session ID.&#160; This dictionary can be used to keep track of information unique to that session.&#160; For example, it could be used to keep track of items placed in a shopping cart metaphor.<\/p>\n<p>Note that this \u201csession\u201d can exist even if the user has not authenticated.&#160; And this is often useful.&#160; In a retail web site (like Amazon), you can put items in your shopping cart, and only need to authenticate or sign on when you are ready to checkout &#8212; and even then, you can actually make a purchase without needing to authenticate, provided, of course, that a valid credit card is used.<\/p>\n<p>Because this \u201csession\u201d is disjoint from authentication, it is better referred to as a \u201cbrowser\u201d session instead of as a \u201cuser\u201d session.&#160; In a kiosk environment, if a user walks away from the kiosk while there are items in a shopping cart, the next user to use the kiosk will still see the same shopping cart.&#160; The web server doesn\u2019t know any better that a different user is using the kiosk, because the same session ID is being sent back in the session cookie during interaction with the web server.<\/p>\n<p>That dictionary of objects on the server, the session state, also poses certain complications that most developers are aware of.&#160; In a web farm, some form of sticky load balancer has to be used so that session state can be kept in memory.&#160; Or a centralized store for the session state is used to make the state consistent across the servers in the web farm.&#160; In either case, service performance can be affected.&#160; I have a very strong opinion against using session state.&#160; I avoid it, if at all possible.<\/p>\n<p>What about Forms Authentication?&#160; Forms Authentication is the most common authentication mechanism for ASP.NET web sites.&#160; When a user is authenticated, most commonly using a user ID and password, a Forms Authentication cookie is generated and is sent to the browser (the name of the cookie, by default, is <em>.ASPXAUTH<\/em>).&#160; The cookie contains the encrypted form of an authentication ticket that contains, among other things, the user ID that uniquely identifies the user.&#160; The same cookie is sent to the web server on each HTTP request, so the web server has an idea of the user identity to correlate to a particular HTTP request.<\/p>\n<p>Everything I mentioned above is common knowledge for web developers.&#160; Trouble and confusion only comes about when an expectation is made that an ASP.NET session can be associated with ASP.NET authentication.&#160; To be clear, it can be done, but precautionary measures have to be taken.<\/p>\n<p>The problem is related to session hijacking, but better known as session fixation.&#160; Assuming that you\u2019ve done your diligence of using SSL\/TLS and HttpOnly cookies, there isn\u2019t a big risk of having the session ID stolen\/hijacked by sniffing the network.&#160; And most applications also perform some session cleanup when the user logs out.&#160; Some applications even ensure that a new session ID is created when the user logs in, thinking that this is enough to correlate a session state with a user identity.<\/p>\n<p>Remember that the session cookie and the forms authentication cookie are two different cookies.&#160; If the two are not synchronized, the web server could potentially allow or disallow some operations incorrectly.<\/p>\n<p>Here\u2019s a hypothetical (albeit unrealistic) scenario.&#160; A banking application puts a savings account balance into session state once the user logs in.&#160; Perhaps it is computationally expensive to obtain the account balance, so to improve performance, it is kept at session state.&#160; The application ensures that a new session ID is created after the user logs in and clears the session state when the user logs out.&#160; This prevents the occurrence of one user reusing the session state of another user.&#160; Does it really prevent it?&#160; No.<\/p>\n<p>As an end-user having control of my browser, I am privy to the traffic\/data that the browser receives.&#160; With the appropriate tools like Fiddler2 or Firebug, I can see the session and forms authentication cookies.&#160; I may not be able to tamper them (i.e., the forms authentication cookie is encrypted and hashed to prevent tampering), but I could still capture them and store them for a subsequent replay attack.<\/p>\n<p>In the hypothetical banking application above, I initially log in and get SessionIDCookie1 and FormsAuthCookie1.&#160; Let\u2019s say the account balance stored in session state corresponding to SessionIDCookie1 is $100.&#160; I don\u2019t log out, but open up another window\/tab and somehow prevent (through Fiddler2 maybe) the cookies from being sent through the second window.&#160; I log in to that second window.&#160; The web server, noting that the request from the second window has no cookies, starts off another session state, and also returns SessionIDCookie2 and FormsAuthCookie2.&#160; Browsers usually overwrite cookies with the same names, so my SessionCookieID2 and FormsAuthCookie2 are my new session ID and forms authentication cookies.&#160; But remember that I captured SessionIDCookie1 and FormsAuthCookie1 to use in a future attack.<\/p>\n<p>In that second window, I transfer $80 away from my account, thereby updating the session state corresponding to SessionIDCookie2 to be $20.&#160; I cannot make another $80 transfer in the second window because I do not have sufficient funds.<\/p>\n<p>Note that SessionIDCookie1 has not been cleaned up and there is a session state on the server corresponding to SessionIDCookie1 which still thinks that the account balance is $100.&#160; I now perform my replay attack, sending to the web server SessionIDCookie1 and FormsAuthCookie1.&#160; For that given session state, I can make another $80 transfer away from my account.<\/p>\n<p>You might say that the application could easily keep track of the forms authentication cookie issued for a particular user, so that when FormsAuthCookie2 is issued, FormsAuthCookie1 becomes invalid and will be rejected by the server.&#160; But what if I use SessionIDCookie1 and FormsAuthCookie2 on the second window?&#160; It\u2019s the same result &#8212; I can make another $80 transfer away from my account.<\/p>\n<p>Oh, you might say that the application should invalidate SessionIDCookie1 when SessionIDCookie2 is issued.&#160; Sure, but how?&#160; Unlike the forms authentication cookies, where the user identity is the same within both cookies, there is nothing common between SessionIDCookie1 and SessionIDCookie2.&#160; And since there is nothing relating SessionIDCookies with FormsAuthCookies, there\u2019s no mechanism to search for and invalidate SessionIDCookie1.<\/p>\n<p>The only workaround for this is custom code that ties a SessionIDCookie with the FormsAuthCookie that was issued for the same logical session.&#160; One of the following options should provide a solution.<\/p>\n<ul>\n<li>Key your session states by an authenticated user ID instead of by a session ID.&#160; No need for the session cookie.&#160; This will not work for applications that need to keep track of session without authentication (e.g., online shopping). <\/li>\n<li>Store the session ID as part of the payload for the forms authentication cookie.&#160; Verify that the session ID in the session cookie is the same as that stored in the forms authentication cookie.&#160; Keep track of the forms authentication issued for each user so that only a single forms authentication cookie (the most recently issued) is valid for the same user.<\/li>\n<\/ul>\n<p>Maybe an overarching solution is to avoid storing user-specific information in the session state.&#160; Remember that it is a \u201cbrowser\u201d session state, and has nothing to do with an authenticated user.&#160; If you keep that in mind and only store \u201cbrowser\u201d-related information into session state, then you could avoid the problems altogether.<\/p>\n<p>ASP.NET session fixation is not a very publicized problem, but is potentially a big risk, specially if improper assumptions are made with regard to session and authentication.&#160; ASP.NET session fixation is also described long back in <a title=\"http:\/\/software-security.sans.org\/blog\/2009\/06\/14\/session-attacks-and-aspnet-part-1\/\" href=\"http:\/\/software-security.sans.org\/blog\/2009\/06\/14\/session-attacks-and-aspnet-part-1\/\">http:\/\/software-security.sans.org\/blog\/2009\/06\/14\/session-attacks-and-aspnet-part-1\/<\/a>, and been reported through Microsoft Connect <a href=\"http:\/\/connect.microsoft.com\/feedback\/viewfeedback.aspx?FeedbackID=143361\">http:\/\/connect.microsoft.com\/feedback\/viewfeedback.aspx?FeedbackID=143361<\/a>, but to my knowledge, has not been addressed within the ASP.NET framework itself.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The title can be misleading, because in concept, one is not related to the other.&#160; However, a lot of web applications mix them up, causing bugs that are hard to troubleshoot, and, at worst, causing security vulnerabilities. A little bit &hellip; <a href=\"https:\/\/peterwong.net\/blog\/asp-net-session-and-forms-authentication\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[27],"class_list":["post-136","post","type-post","status-publish","format-standard","hentry","category-asp-net","tag-security"],"_links":{"self":[{"href":"https:\/\/peterwong.net\/blog\/wp-json\/wp\/v2\/posts\/136","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/peterwong.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/peterwong.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/peterwong.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/peterwong.net\/blog\/wp-json\/wp\/v2\/comments?post=136"}],"version-history":[{"count":0,"href":"https:\/\/peterwong.net\/blog\/wp-json\/wp\/v2\/posts\/136\/revisions"}],"wp:attachment":[{"href":"https:\/\/peterwong.net\/blog\/wp-json\/wp\/v2\/media?parent=136"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/peterwong.net\/blog\/wp-json\/wp\/v2\/categories?post=136"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/peterwong.net\/blog\/wp-json\/wp\/v2\/tags?post=136"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}