Saturday, December 26, 2009

Blogger Migration

I'm moving my blog to Blogger, formerly hosted in a custom ASP.NET app running at home. I want to get out of the business of running a server at home, and have migrated that box to Windows Home Server (I'll rave about that in another blog post).

Only one problem to migrating via the Blogger APIs: they rate limit you to fifty posts per day (the failure is an HTTP 400 with a plain text body "Blog has exceeded rate limit or otherwise requires word verification for new posts"). I'm not sure if that includes comments or not (which I'm importing, too). But either way, it will be somewhere between a week or two until I'm done (the code's all written, I just need to run it until it stops every day).


Home, sweet home.

Wednesday, December 16, 2009

Cookie Gotchas in ASP.NET

As a result of a recent security audit, we were asked to implement a more secure session identifier to help make session hijacking harder. Specifically, our requirements were:

  • Ensure that client-specified session IDs are system-generated
  • Generate a new session ID on login
  • Invalidate anonymous session IDs on login

We started following a strategy very similar to this solution on MSDN. If you don't click the link, the summary is, implement an HttpModule, in BeginRequest inspect the request cookie and extract & verify a hash from it, overwrite the Session ID cookie in the Request so that the underlying session implementation is unaware of any cookie mucking, and then on EndRequest write a new session cookie to the Response with a hash appended. The primary difference between that solution and ours is that ours encrypted the session ID and authentication information into the token so that we could meet the latter two requirements. (I'll blog about the specific solution at a later date.)

There's only one problem: it doesn't work. At all. You can't overwrite a Request cookie.

Sure, it works in trivial solution (including my POC), but if you modify the Response.Cookies collection, you lose your modifications to the Request.Cookies collection.

I cracked open Reflector to understand why. Take a look at System.Web.HttpCookieCollection.Remove(string):

public void Remove(string name)

{
    if (this._response != null)
    {

        this._response.BeforeCookieCollectionChange();
    }
    this.RemoveCookie(name);

    if (this._response != null)
    {
        this._response.OnCookieCollectionChange();

    }
}

And then System.Web.HttpResponse.OnCookieCollectionChange():

internal void OnCookieCollectionChange()
{

    this.Request.ResetCookies();
}

And as if this were necessary, System.Web.HttpRequest.ResetCookies():

internal void ResetCookies()

{
    if (this._cookies != null)
    {

        this._cookies.Reset();
        this.FillInCookiesCollection(this._cookies, true);

    }
    if (this._params != null)
    {

        this._params.MakeReadWrite();
        this._params.Reset();
        this.FillInParamsCollection();

        this._params.MakeReadOnly();
    }
}

Digging through the Analyzer, the cookies in the HttpRequest are reset by the following seemingly innocuous methods:

  • System.Web.HttpCookieCollection.Remove(String)
  • System.Web.HttpCookieCollection.Set(HttpCookie)
  • System.Web.HttpResponse.SetCookie(HttpCookie)

So if you ever try and overwrite a cookie in the HttpRequest, you had better not call any of those three methods, otherwise the cookies will get reloaded by parsing the original values from the raw request.

Final thoughts:

- You can use reflection to set HttpCookieCollection._response to null (from HttpResponse.Cookies), which then avoids the OnCookieCollectionChange call, but if you do, it will break other things, like automatic adding of cookies on accessing them. Like in System.Web.HttpCookieCollection.Get(string) (called by HttpCookieCollection this[string]):

public HttpCookie Get(string name)
{
    HttpCookie cookie = (HttpCookie) base.BaseGet(name);

    if ((cookie == null) && (this._response != null))

    {
        cookie = new HttpCookie(name);
        this.AddCookie(cookie, true);

        this._response.OnCookieAdd(cookie);
    }
    return cookie;

}

- I could only find one other person on the Internet who experience this problem.

- There's some other weirdness where adding a Response cookie resets the Request.Cookies collection, and adds the Response.Cookies to the Request.Cookies collection. You might be able to benefit from that; add a cookie to the Response.Cookies collection, call one of the methods that triggers resetting the Request cookies, and now your Response cookie will be one of the Request cookies. That didn't work in our case due to later cookie manipulation by the Session provider.