
Asp Net 2.0 Security Membership And Role Management
.pdf
Chapter 7
Cookie Sharing across Applications
If you write other application code that depends on Session.SessionID, the same value is going to show up in different applications. If your intent is to hook other application logic and data storage off of SessionID, you may want to use a different identifier such as a combination of authenticated username and application name. The one thing you definitely don’t want to do is to come up with a solution that forces creation of a new session identifier in each unique application.
Think about the scenario where you have multiple applications sitting on the same server. The HttpCookie that the session state feature issues will have the following characteristics:
The Domain property is never set on the HttpCookie so it will default to the domain of the server.
The Path property will be hardcoded to /.
No explicit expiration date will be set on the cookie.
The value of the cookie is set to the 24-character identifier that you can get from
Session.SessionID.
With this combination of values, anytime the browser user surfs between applications on the same server (or applications living under the same DNS name in the case of a load-balanced web farm), the session cookie will be sent to each and every application. This means that, over time, the session state feature will be accumulating session data for each application. If you were suddenly to send back a fake cookie that reset the session state cookie from one of your responses, the net result would be that all of the session state data in all of the other applications would be lost.
Let me state that a different way, because this is central to the way the ASP.NET session state feature works. For each full DNS hostname, a browser gets one, and only one, session state cookie. That cookie is shared across all applications, and if the cookie is ever lost or reset, all session data in all applications that received that cookie will be lost. I want to drive home that point because sometimes developers wonder whether they should include custom logic in their logout process for session state.
There is a method on the Session object called Abandon. Calling Session.Abandon invalidates the session state data in the back-end data store (cache entry invalidation for in-process and session state server and deleting the row of data for SQL-based session data) for the specific application that called the method. However, calling Session.Abandon doesn’t clear the session cookie. If you called Session.Abandon from application A, and if ASP.NET then cleared the session cookie, any session data in other applications would be lost. The fact that the session identifier can be shared between many applications is the reason ASP.NET invalidates only session data, not the cookie, during a call to Abandon.
If you do want to enforce that session data for a user is eliminated when that user logs out of an application, calling Abandon is sufficient. Extending the previous sample applications a bit more, you can add a page that explicitly calls the Abandon method and see the effect inside of SQL Server. When you first access the sample site, you get a row of session data as expected:
SessionId |
Created |
-------------------------------- |
---------------------------------- |
cqiyhanqbi2xk2vksixmybi108a8b5d6 |
2005-05-23 20:24:47.210 |
292

Session State
When Abandon is called, in the case of the SQL Server based provider, an immediate delete command is issued and the session data is removed from the database. If you then access another page in the application, thus recreating the session data, the same session ID is retained (shown in bold), but a new row in the database is created with new values for the creation and expiration date.
SessionId |
Created |
-------------------------------- |
---------------------------------- |
cqiyhanqbi2xk2vksixmybi108a8b5d6 |
2005-05-23 20:50:42.537 |
If you happen to be developing a standalone application, and thus you don’t need the session identifier to remain stable across different applications, you can issue a clear cookie from your logout logic. However, this is the only scenario where explicitly clearing the session cookie can be done, because there aren’t any other ASP.NET applications relying on the value.
Protecting Session Cookies
As with forms authentication in ASP.NET 2.0, the session state feature explicitly sets the HttpOnly property on the cookie to true. Because applications store interesting information inside of session state, ASP.NET protects the session identifier from client-side cross-site scripting (XSS) attacks (for more details on XSS attacks and other security features of HttpOnly cookies, see the discussion in Chapter 5 on forms authentication cookies). The likelihood of an attacker ever guessing a live session cookie is astronomically low (with 120 bits in the session identifier, that works out to an average of 2^60 guesses required. Come back in the next millennia when you finally get a match.)
That pretty much leaves cookie hijacking as the most viable option for getting to someone else’s session data; hence the addition of HttpOnly protection in ASP.NET 2.0. The theory is that few (if any) applications should harvest the session identifier client-side for other uses. Typically, developers slipstream off the value of Session.SessionID in their server-side logic and don’t need to pass it around client-side. As a result of risks of accidentally exposing a session identifier across multiple client-side applications, I definitely recommend changing that type of logic prior to upgrading to ASP.NET 2.0.
Some developers may wonder why session state doesn’t include at least the encryption and signing protections found in other cookie-based features such as forms authentication and Role Manager. There was a fair amount of debate around adding encryption and signing in ASP.NET 2.0 to the session state cookie. However, because the default session state cookie is a cryptographically strong 120-bit random number, there didn’t seem to be much point in layering the overhead of encryption and signing on top of it. Furthermore, not only is the session state identifier a strong random number, because the session state identifier is stored in a session based cookie, the session ID changes from browser session to browser session.
Unlike forms authentication (for example), which relies on a fixed encryption key and a fixed validation key, with session state the only time you can really someone else’s session state is while that user’s session is still alive. There is no such thing as an offline brute force decryption attack or hash collision attack with session state. With session state, an attacker must successfully guess (incredibly unlikely) or hijack (possible but difficult to accomplish) a session identifier while that session is still alive somewhere in an application. Although an attacker could theoretically stumble across a session identifier associated with an expired session, this isn’t of any use because an expired session means that the data associated with that session is no longer available.
293

Chapter 7
Session ID Reuse
This leads to another point around the behavior of cookie-based sessions after the session has expired. If a browser user accesses an application and sends a session cookie along with the request, but the session has expired since the last time the application was accessed, the old session data is no longer accessible. However, when running in cookied mode, the session identifier will be reused to create a new session for the application.
Because a session identifier may be shared across multiple web applications, the session state feature will not invalidate the session identifier just because the session has expired. Instead, the session state feature sets up a new session state object that is associated with the preexisting identifier. By doing so, session state prevents the problem of one application invalidating a session identifier when there is still live session data associated with that identifier in other applications.
You can see this pretty easily by using two applications, both with session state enabled. Set the timeout for session state in one application to one minute, and leave the other application’s timeout at its default. After accessing both applications at least once, wait for a bit more than one minute. This gives the application with the short timeout the opportunity for the session state to expire.
When you access the applications again (using the same browser session), the application with the short timeout has indeed expired its session data. However, the second application, with the default timeout, still has an active session, and the data in that session is still retrievable because expiration of cookiebased sessions doesn’t cause the session identifier to be regenerated.
Put a different way, cookie-based session state always supports Session ID reuse. As long as the browser sends a well-formed session identifier to the server, that identifier will be reused. Sometimes developers assume that session state will create a new session identifier when a session expires, and as a result, developers create application functionality that depends on a new session identifier being created after a session expires. This assumption is incorrect though, and developers cannot rely on new session identifier being generated when running in cookied mode.
Cookieless Sessions
ASP.NET 1.1 added support for cookieless session state. As mentioned in earlier chapters, the cookieless mechanism that was added in ASP.NET 1.1 for session state has been expanded to encompass cookieless support for forms authentication as well anonymous identification. You can easily enable cookieless operations with the following configuration:
<sessionState cookieless=”UseUri” />
You can also issue cookieless session identifiers based on the capabilities of a user’s browser with one of the following options: AutoDetect or UseDeviceProfile. These options use different detection mechanisms to determine whether or not the user’s browser should be sent a cookieless session identifier.
Accessing an application that uses cookieless session state results in the session identifier showing up on the URL
http://localhost/Chapter7/CookielessSessionState/(S(z0xade23qlr20245h54lkkym))/
Default.aspx
294

Session State
The value in the URL is the same value that is returned from Session.SessionID. If you use the following line of code on the default.aspx page shown earlier:
Response.Write(Session.SessionID + “<br />”);
the identifier output on the page matches the value shown in the URL:
z0xade23qlr20245h54lkkym
This behavior should start a few security antennae wiggling! Now anybody who looks at the address bar in the browser knows their session identifier. A user who understands how ASP.NET works will recognize this value and a malicious user that understands ASP.NET session state may start thinking about what can be done with this information.
Especially in cookieless mode, don’t use the session identifier as an indication of an authentication session. If you have logic that works this way, all a user has to do is come up with a 24-character string, and suddenly that user would be authenticated.
Of course, the real security issue with cookieless session state is the common weakness that was discussed earlier with cookieless forms authentication. It is very, very easy for a user to unwittingly leak the session identifier to other people (email it, save it to disk as an Internet Explorer shortcut, and so on). On shared machines such as kiosks, the cookieless identifier has a very real likelihood of sticking around across the browser session of completely different users.
Given the comparative weakness of cookieless session identifiers, when is cookieless session state appropriate?
For an internal corporate application that needs to be available from a mobile device that doesn’t support cookies — The likelihood of leaking the identifier is much lower in this scenario.
For Internet facing applications that need to support mobile users — For such an application you should not store anything sensitive inside of session state: this means no personally identifiable information, and definitely nothing like credit card numbers, Social Security identifiers, and so on. Furthermore, the session identifier should not be used within the application’s logic as a key that can lead to any kind of sensitive or personally identifiable information.
I intentionally left out a potential third scenario of an e-commerce site that wants to support cookieless users. If you need to support these types of customers and you are thinking of using cookieless session state, exercise caution. A customer using a desktop browser with cookieless session state is at risk for leaking the session identifier outside of the browser due to the ease with which you can get to an email application from inside of all popular browsers (for example, Hi Mom — here’s that item I was talking about on the Web!). If you do choose to support cookieless session state on an e-commerce site, only use it to hold anonymous information such as shopping cart items. Don’t use session state in a way that a session identifier could ever be used to get back to information about a specific person. Although running the entire e-commerce site under SSL is also a way to mitigate the security problem of cookieless identifiers, for performance reasons most e-commerce sites would probably be unwilling to do this.
295

Chapter 7
The following list contains many of the security limitations of cookieless session identifiers:
The identifier is immediately visible inside the address bar of the browser.
The only way to prevent man-in-the middle attacks is to run the entire site under SSL, although this is also a limitation of the session state feature as a whole.
The identifier can be easily pasted into an email and shared with other users.
Because the identifier is in the URL, cached URLs with the session identifier can end up in the browser’s URL history.
Proxy servers and caching servers can end up with URLs in their caches that contain the cookieless session identifier.
Session ID Reuse and Expired Sessions
Many of these weaknesses revolve around the ability for a URL with a session identifier to be reused by someone other than the original intended recipient of the identifier. Because the session state feature doesn’t have the concept of an absolute expiration, as long as someone (or some user agent) continues to access a site with a valid session identifier, the underlying data will be kept alive. This behavior is more of a problem with cookieless session state though.
Any browser, caching server, proxy server, and so on that keeps URLs lying around in a cache results in potentially long-term storage of URLs with embedded session identifiers. This is a much less likely problem in the cookied case because most user agents and caching software ignore session-based cookies. (The browser isn’t going to keep a history of your session-based session cookie for the next 30 days.)
On the other hand, it is almost guaranteed that between the possibility of accidentally leaking session identifiers and the long-lived storage of URLs through various caching mechanisms, someone will eventually return to a site and replay a cookieless session identifier. The most likely scenario is one where the user that was originally issued the identifier comes back to the site through some kind of shortcut. You only need to use the Internet Explorer history feature to see what I mean. Or a site with cookieless sessions all URLs with the embedded session identifier in it are sitting there in the browser history waiting for you to click them.
Unlike cookied mode though, cookieless session state automatically reissues a session identifier under the following conditions:
A valid (that is, well-formed) session identifier is contained on the request URL.
The session data associated with that identifier has expired.
If both of these conditions are true, then the session state feature will automatically create a new session identifier when it initializes a new session state object. Note that if you call Session.Abandon from an application using cookieless sessions, the session ID will also be regenerated the next time you access a page in the application. In this case, calling Abandon is just another way of ending up in the situation where you have a valid but expired identifier.
296

Session State
To see the behavior when a session expires, you can take the cookieless URL that was shown earlier:
http://localhost/Chapter7/CookielessSessionState/(S(z0xade23qlr20245h54lkkym))/ Default.aspx
Paste this URL into the browser (assuming of course that 20 minutes have passed, which is the default session timeout). The page still runs successfully, but the URL that comes back in the browser reflects a new session identifier:
http://localhost/Chapter7/CookielessSessionState/(S(5e1yfz55otmtfjq1lcqwbje4))/ Default.aspx
The reason for this behavior is that in ASP.NET 2.0, the session state configuration supports a new attribute: regenerateExpiredSessionId. By default this attribute is set to true, which is why when expired session identifiers are sent in the URL, ASP.NET automatically issues a new identifier. This behavior is enabled by default for a few reasons:
It is the best choice from a security standpoint. Given the ease with which cookieless identifiers can live far beyond their intended life, it makes sense to invalidate the identifiers by default.
Unlike cookied sessions, cookieless session identifiers aren’t shared across multiple applications. You can see that cookieless session identifiers do not flow across applications by setting up two applications on the same server and configuring both to use cookieless session state. When you access each application in turn, you end up with two different identifiers. This intuitively makes sense because URLs are by their very nature unique to an application; hence values embedded in the URL would also be application-local.
If for some reason you don’t want session identifiers to be regenerated, you can set regenerateExpiredSessionId to false. However, if your application depends on retaining stable session identifiers across browser sessions (this is one possible reason why you wouldn’t want to issue a new identifier), you should look at why your application is depending on stable session identifiers. If at all possible move to some other mechanism (perhaps requiring a login at which point you have a user identifier) that is more secure for tracking specific users across different browser sessions.
Session Denial of Ser vice Attacks
The idea behind a session ID denial of service attack is that a malicious user “poisons” session state by sending it numerous bogus session identifiers or by forcing the creation of sessions that will never be used after being initialized. Unlike other poisonings (for example, DNS cache poisoning) that involve placing incorrect or malicious data into a cache, session ID poisoning is very basic. A malicious user can spam the web server with session identifiers that are well-formed, but that are not associated with any active session. Hence the term poisoning because the ASP.NET server ends up with an internal cache that is polluted with spurious session identifiers.
In a similar manner, a malicious user can access a page in an application that results in the issuance of a session identifier, but then throw away the cookie that is sent back by the application. In this manner, a malicious user can force an application to spin up a new session each time the page is accessed — again resulting in a session state store that is polluted with unused session state data.
297

Chapter 7
A session identifier does take up a little bit of space and processing overhead on the web server each time a new session is started up. However, because ASP.NET has a number of internal optimizations around new and uninitialized sessions, sending a spurious identifier in and of itself is harmless. The real danger of session ID poisoning occurs if the session state object is accessed after the spurious identifier is sent. This can be code running in the Session_Start event in global.asax, or there can just be code running on a regular .aspx page that manipulates session state.
After the Session object is accessed, storage is allocated for the session data. This means that memory is consumed on the web server for the in-process session state case, and rows are allocated in the database for the SQL OOP scenario. For the session state server, memory is allocated on the OOP session state server.
For the OOP SQL session state, spurious sessions shouldn’t have a big impact because each spurious session and subsequent use of that session results in a row in the database. An attacker that attempted a denial of service (DOS) attack against SQL based session state causes some extra CPU and disk overhead on the SQL Server but not much more, because the lifetime of a spurious session looks roughly as follows:
1.The attacker sends a fake session ID to the server as part of the request or accesses a page that makes use of session state but then intentionally throws away the a session state identifier.
2.The ASP.NET page accesses the Session object in some manner, which results in a new row being written to the ASPStateTempSessions table.
3.The attack continues to send other fake session IDs, or continues to request the same page but with no session state cookie thus resulting in the creation of a new session identifier for each request.
4.At some point the session associated with the identifier from step 2 times out.
5.Every minute (by default) the ASP.NET SQL Server session state cleanup job runs and deletes expired rows of session data from the database.
As a result of the automatic session cleanup in step 5, a spurious session is only going to take up space in the SQL Server for an amount of time equal to the timeout setting in configuration (20 minutes by default). If an attacker uses a standard desktop machine to send 10 spurious session identifiers per second (in other words, the attacker adds 10 requests per second (RPS) overall to your site’s load), an attack can accumulate 600 spurious sessions in a minute, and 12,000 spurious sessions in the default 20 minute timeout period.
If you have 12,000 spurious sessions in the database, and each session is associated with 5KB of data, you are looking at roughly 58–59MB of extra data sitting in the session state database. Furthermore, the SQL Server machine has to chug through and delete 600 rows of bogus session data each time the cleanup job wakes up on its 60 second interval. Overall, it isn’t good that this type of extra overhead is being incurred, but on the other hand short of a concentrated attack against a web farm using OOP SQL Session state, an attacker is going to have a hard time being anything more than a nuisance.
One of the reasons I picked such a low request per second value for describing the issue is that many websites have a variety of real-time security monitors in place: one of them checks on the requests per second value. If your security monitoring apparatus suddenly sees a spike in traffic — for example, the current RPS compared to the average RPS during the last 30 minutes — it probably will set off several alarms. However, slipping in an extra 10 requests per second is trivial for today’s web server hardware; probably only paranoid security measures would detect such a small increase in the overall traffic of a site.
298

Session State
Although SQL Server–based session state is pretty hard to overrun with a session ID denial of service attack, the story is a bit different when using in-process session state or the OOP session state server. In both of these cases, an attacker is causing memory consumption to occur with each and every spurious session. Unlike SQL Server session state where disk space is relatively cheap (imagine an attacker attempting to overflow a terabyte of storage on the session state server — good luck!), memory is a scarce resource on the web server.
Taking the previous scenario with 10 spurious requests per second, and 5KB of spurious data, you end up permanently losing 58–59MB of memory from your web server due to space wasted storing spurious session data. Furthermore, you incur the additional overhead of the in-memory items aging out (session state items are held in the ASP.NET Cache object) and the subsequent overhead of garbage collection attempting to recompact and reclaim memory caused by session data constantly aging out and being replaced by other spurious session data.
Although 58–59MB doesn’t seem like a lot of memory, the real risk of a session ID denial of service attack comes when you have an application that depends on storing larger amounts of data in session state. For example, if an application stores 50KB of data in session state instead of 5KB of data, you have a very real problem. An attacker could consume around 570MB of memory over a 20-minute period. On servers running multiple ASP.NET applications, that is enough memory consumption to probably force the appdomain of the problematic ASP.NET application to recycle. If you are running on Windows Server 2003 and IIS 6, and if you have set memory-based process recycling limits, it is possible that the IIS 6 worker process will also be forced into periodic recycling.
The general guidance here is that if you depend on in-process session state or the OOP session state server, and if your website is Internet-facing and hence reachable by an attacker, you should do the following to detect and mitigate session ID denial of service attacks:
Monitor the application specific ASP.NET performance counter for Sessions Active as shown in Figure 7-1.
Figure 7-1
299

Chapter 7
*Inside of the performance monitor MMC you can get to this counter by selecting ASP.NET Apps v2.0.x.y for the performance object, and then choosing to monitor all ASP.NET instances, or just specific ones. After you choose the desired instances the Sessions Active option is available in the Select Counters from List list box. You need to profile the usage of your application to determine an appropriate upper limit. Chances are that most applications could probably get by with a limit of somewhere between 100 and 500 sessions for an application. Because the performance monitor supports configuring alerts, you can set up an alert that sends emails or runs some other program if the number of active sessions exceeds an appropriate limit.
Monitor the overall requests per second on your site. If the RPS at any point in time shows an abnormal spike relative to the last few minutes (or perhaps hours) of activity, send out an alert so that someone can investigate and determine what is happening.
Set appropriate memory limits on applications that use session state. This is very easy to accomplish in IIS 6 because you can set a memory-based process recycling limit on the Recycling tab of an application pool. Again, you will need to determine appropriate upper limits for your applications. Once set though, the side effect of a sustained DOS is that the problematic application will periodically recycle as memory is consumed. Other applications in other application pools will be unaffected though.
The simplest way to mitigate the entire session ID denial of service scenario is to use session state only on pages that require an authenticated user. As mentioned earlier, just sending a session identifier to ASP.NET doesn’t do much of anything. ASP.NET will delay initialization of the session state object until it is actually needed. As a result, if you access the Session object only on pages that require an authenticated user, the only way an attacker could perform a DOS is to log in first. Typically attackers want to remain anonymous and aren’t going to set up a user account on your site just to launch a DOS.
Trust Levels and Session State
As with just about every other aspect of ASP.NET, the session state feature is affected by the trust level settings for your machine and your application. For in-process session state, the effect of trust level is limited to some new restrictions added in ASP.NET 2.0 around serialization and deserialization (a bit more on that later in this section). However, both SQL Server and the OOP session state server require applications to run in Medium trust or higher for these features to be used.
You can take any of the previous sample applications that used SQL Server based session state and add a <trust /> level element as follows:
<trust level=”Low”/>
You get back an error page to the effect that you can’t use session state at that trust level. If you tweak the trust level to Medium, the application will start working again.
Things get a bit interesting though if you take an additional step and edit the actual trust policy file (for all the details on trust level and their relationship to trust policy files see Chapter 3). Change the trust level to use a custom trust level:
<trust level=”Medium_Custom”/>
300

Session State
This custom trust level sets the AspNetHostingPermission.Level to Medium, so effectively the application is running a modified version of the Medium trust level. Then in the trust policy file associated with this trust level, remove the following permission element:
<IPermission
class=”SqlClientPermission”
version=”1”
Unrestricted=”true”
/>
When you rerun the application, session state still works! There are a few reasons for this behavior. Session state is a heavily used feature by customers, so ASP.NET shouldn’t impose excessive security requirements just to get session state working. However, in the case of SQL Server–based session state there is obviously a perfectly good permission class supplied by the framework that models access rights for using SQL Server. The problem is that if ASP.NET relied on the presence of SqlClientPermission in the trust policy, it would effectively be allowing any page in the application to use SQL Server.
However, if a developer wants to enable SQL Server session state and doesn’t want random pieces of page code using ADO.NET and attempting to access SQL Server, having session state condition its behavior on SqlClientPermission is excessively permissive. The compromise approach for all of this is why SQL Server session state works in the absence of SqlClientPermission. Instead, ASP.NET requires that the application be running at Medium trust or above. As long as this condition is met, the session state feature will call into SQL Server on behalf of the application.
Technically, SQL Server session state works in Medium trust because the entire code stack for session state is trusted code. For example, if you think about the process by which session data is stored, the call stack from top to bottom is roughly:
1.The EndRequest event is run by the HTTP pipeline.
2.The SessionStateModule that hooks EndRequest is called.
3.As part of the processing in SessionStateModule, it calls into the internal class that implements the SQL Server session state provider.
4.That provider calls into ADO.NET.
All of this code though is trusted code that lives in the global assembly cache (GAC). As a result, when ADO.NET in step 4 triggers a demand for SqlClientPermission, the call stack above that demand consists entirely of ASP.NET code sitting somewhere inside of System.Web.dll which exists in the GAC. From the Framework’s standpoint, only trusted code is on the stack, and as a result the call to SQL Server succeeds. In the case of the out-of-process session state server, a similar situation exists though the OOP state server uses Win32 sockets instead.
You can see from all of this that whenever significant work is performed by the session state feature, only trusted ASP.NET code is on the stack. As a result, the session state feature has to be a bit more careful in terms of what it allows because permission checks and demands will always succeed. The trust level requirements for the various modes of session state are shown in the following table.
301