Recently I have been revisiting several ways to implement web apps that are partially secured by SSL. That means that only parts of the application use SSL transport security whereas other parts transmit data over clear text.
This can be a requirement because SSL increases the CPU load on the web server and can limit scalability. Often you need SSL only for a small subset of an application. On the other hand SSL is not the “CPU killer” as sometime stated. IIS6 also support SSL accelerators that allow to off-load the crypto operations to specialized CPUs.
IMO – in many cases it is totally OK to enable SSL protection for the whole application which will save you from some headaches. But if you want a partitioned application, you have to take several things into account.
Leaking Information
Besides the obvious cases where you maybe accidentally allow clear text connections to pages that should be SSL secured, there are more subtle things you could accidentally leak to clear text (which means it can be potentially sniffed and disclosed). Authentication cookies are a popular example. You have to make sure that such cookies are only transmitted over SSL (e.g. when you users leave the SSL secured area again after authentication).
Cookies support the secure flag which instructs RFC compliant browsers to send the cookie only over SSL. You can set this flag using the Secure property on the HttpCookie class or via web.config:
<httpCookies requireSSL="true" />
In the case of Forms authentication, the requireSSL attribute also checks if the user is logging in from an SSL connection before calling FormsAuthentication.SetAuthCookie (something you have to check yourself if you are issuing tickets manually).
<authentication mode="Forms">
<forms requireSSL="true" />
</authentication>
Switching to SSL
You have to explicitly switch to SSL when you are redirecting to an SSL secured resource. This has to be done using an absolute URL like https://server/app/page.aspx. Relative URLs like the convenient ~/page.aspx won’t work. This would mean that you have to hardcode those values in your code or markup.
There are several ways to work around that problem. One approach would be to write a page base class for SSL secured pages that checks for SSL and does an absolute redirect to itself if required. Like this:
public class SslPageBase : Page
{
protected override void OnInit(EventArgs e)
{
if (!Request.IsSecureConnection)
SslTools.SwitchToSsl();
base.OnInit(e);
}
}
If you want to use automatic redirection to your login page by Forms authentication, you have to use one of the above approaches. Otherwise the redirection will be done using the current protocol, which may be clear text.
These both approaches are very convenient because they allow you to use relative URLs in code and markup. But unfortunately they rely on a “misconfiguration” of IIS. If IIS is configured correctly the pages and directories that should be SSL protected are marked for “SSL required” in IIS. In this case the IIS SSL check will run much earlier than ASP.NET. IIS will bounce requests with a 403.4 status code before a module or a page base class can run. Such a configuration is very typical and recommended when thinking defense in depth.
So you have to do absolute redirects at the page level. A little helper can accomplish that without having to hardcode server or vdir names.
A replacement for Response.Redirect allows the following semantics:
SslTools.Redirect("~/ssl/default.aspx", RedirectOptions.AbsoluteHttps);
…and for HTML links:
<a href="<%= SslTools.GetAbsoluteUrl
("~/ssl/default.aspx", ProtocolOptions.Https) %>">SSL Page</a>
GetAbsoluteUrl (which is ultimately used by the Redirect and SwitchToSsl methods shown earlier) constructs an absolute URL from the relative one and conserves query string parameters.
public static string GetAbsoluteUrl(string url, ProtocolOptions protocol)
{
if (url == null)
{
return url;
}
// check for querystring parameters
string path, query;
if (url.Contains(“?”))
{
int qpos = url.IndexOf(‘?’);
path = url.Substring(0, qpos);
query = url.Substring(qpos);
}
else
{
path = url;
query = “”;
}
if (VirtualPathUtility.IsAppRelative(path))
{
path = VirtualPathUtility.ToAbsolute(path);
}
Uri baseUri;
string hostName = HttpContext.Current.Request.Url.Host;
if (protocol == ProtocolOptions.Http)
{
baseUri = new Uri(String.Format(“http://{0}”, hostName));
}
else
{
baseUri = new Uri(String.Format(“https://{0}”, hostName));
}
return new Uri(baseUri, path).ToString() + query;
}
I would love to see this functionality rolled into HttpResponse.Redirect in the next version of ASP.NET. I also think that the FormsAuthenticationModule should do an SSL redirect to the login page if the requireSSL attribute is set to true.
Will IIS7 change the situation?
Currently not. It is by design to not allow any user code to execute before IIS does the SSL check. I think it would be nice if the IIS team would implement an automatic redirect feature into the SSL check…