Securing ASP.NET Cookies
Securing ASP.NET Cookies
Part of a series on securing ASP.NET applications
When your application creates a HTTP Cookie in the browser, it does a couple of things most folks are aware of. It stores a key-value data pair on the client’s browser for future use within our application. It allows for caching and lifetime management by providing an expiry date. It also makes a few assumptions you might not expect at first glance.
Cookies have been around since 1994, and are a fundamental tool in the web developers’ toolbox. Unfortunately, they’re also among the most favoured targets of would-be attackers due to the poor understanding at large of how they behave, and the attack surfaces this opens up.
Mark cookies as Secure
The first flag we want to set is Secure
This flag tells the browser that we should only allow cookies to be setusing a secured connection. Frustratingly, it does not prevent the reading of the cookies on unsecured connections in older browsers (but does prevent unsecured reading on versions 52 or higher of Chrome and Firefox — confusing, right?).
Fortunately, our goal here is not so much to prevent unsecured connections from reading the cookies (although that’s definitely a benefit) — rather by using secured connections we’re attempting to mitigate man-in-the-middle (MITM) attacks on our cookies that could compromise a session.
The reason we don’t worry too much about unsecured cookies is that we shouldn’t ever really trust them in the first place. Cookies are inherently insecure as a data storage mechanism.
While the Secure flag relates to TLS, it does not by itself mean that the Cookies are being encrypted in all cases — which is why we should always be forcing secured connections throughout our applications.
<configuration> <system.web> <!-- Force secure connections for all Cookies --> <httpCookies requireSSL="true" /> </system.web></configuration>
We also need to ensure that if we’re using Forms Authentication, we force secured connections for this as well — failing to do so can negate the effect of doing so for the Cookies (as the data would need to be available in the clear to match the Forms Authentication setting).
<configuration> <system.web> <authentication mode="Forms"> <!-- Force secure connections for Forms Authentication --> <forms requireSSL="true" /> </authentication> </system.web></configuration>
Mark cookies as HttpOnly
By default, HTTP cookies can be manipulated by client-side script. While this seems innocuous enough at first glance (why would we ever write client-side script to do something malicious with our own cookies?), the problem becomes apparent when you consider cross-site scripting (XSS) attacks. If an attacker is able to trick our application into executing Javascript in anotherusers’ browser, then one of the first steps they’ll take is to look for private data in the cookies.
We can stop this dead with a HttpOnly flag in the request headers, which instructs the browser not to allow client script to read the cookies at all.
<configuration> <system.web> <!-- Prevent client script from reading Cookies --> <httpCookies httpOnlyCookies="true" /> </system.web></configuration>
Lock cookies to your domain and path
In addition to preventing client script from accessing our cookies, we need to make explicit which sources can access cookies for our domain. By default, the browser should treat cookies with unspecified domains as being restricted to the domain of the referrer. Again this seems like a harmless assumption at first glance, but depending on how our application is served, may open attack vectors we haven’t yet considered.
Example scenario
Let’s imagine that a new startup decides to host applications for third parties on a domain (*.com). You create app1.*.com, and I (as a harmful and malicious hacker type) create hackapp1.*.com.
If you don’t specify otherwise (and I as the service provider don’t take steps to mitigate this), a user who signs into your application, and then subsequently sends a request to mine through any vector (clicking a link, requesting an image etc.) will also send me the cookies for your application, too.
This problem is compounded if we consider paths. Virtual directories, sitting side-by-side within IIS, will by default also share cookie domains. As a result, filtering on domain alone will not be enough.
We can filter access to our Cookies to a domain and a path by using the Domain and Path directives of the header.
To restrict the domain of our cookies, we can use some Web.config settings.
<configuration> <system.web> <!-- Prevent access to cookies from other sub-domains --> <httpCookies domain="app1.*.com" /> </system.web></configuration>
To restrict the path, we’ll need to add some server-side code. How we handle this is largely dependent on the structure of our application, but the example function below allows us to specify the path from a value in our Web.config when we set a cookie.
private void SetCookie(string Key, string Value) {
Response.Cookies[Key].Value = Value;
Response.Cookies[Key].Path = _ ConfigurationManager.AppSettings["UserDefinedCookiePathFilter"];
}
If we use this method to create all of our user-defined cookies, it will then allow us to restrict the path from our web.config like so:
<configuration> <appSettings> <add key="UserDefiniedCookiePathFilter" value="/VirtualDirectoryToFilter"/> </appSettings></configuration>
In the example above, the cookie will be available to our virtual directory (and the paths underneath it) only. When used with domain this allows us much more granular filtering of outside access to our cookies. It’s worth remembering that this path filtration will only apply to cookies we create explicitly with this function — cookies generated by ASP.NET itself (like those used for managing session), will not be affected (as they won’t be using our method).
For even greater granularity, you could move the path filter out of the Web.config file and into an argument to the SetCookie function explicitly. This would allow paths to be filtered on a per-cookie basis — whether or not this is practical or beneficial depends entirely on the structure of your application.