Monday, 20 September 2010

Security: Protect against POET Attacks with Custom Errors!

There’s been a big deal made of a serious security flaw in ASP.Net which potentially affects a lot of .Net sites, that allows a 3rd Party to trick ASP.Net into serving sensitive files within a web application folder.  Microsoft have released official advise on how to temporarily patch the problem which revolves around forcing Error  and Page Not found pages to return the same status page.  This would need to stay in place until a permanent fix is released.

This workaround clearly introduces an usability issue, which client may not accept.

Fortunately a quick amend to my Custom Error Module can secure your site against the attack with minimal impact to usability. 

   1: using System;
   2: using System.Web;
   3: using System.Net;
   4: using System.Collections.Generic;
   5: using System.Configuration;
   6: using System.Web.Configuration;
   7:  
   8:  
   9: namespace MartinOnDotNet.Website.Support
  10: {
  11:     /// <summary>
  12:     /// Handles errors in an SEO friendly manner
  13:     /// </summary>
  14:     public class SeoErrorLoggingModule : IHttpModule
  15:     {
  16:  
  17:         private static System.Random random = new Random((int)DateTime.Now.Ticks);
  18:  
  19:         private const int MaxDelay = 500;
  20:  
  21:         private static CustomErrorsSection customErrors = WebConfigurationManager.GetSection("system.web/customErrors") as CustomErrorsSection;
  22:  
  23:         /// <summary>
  24:         /// Called when [error].
  25:         /// </summary>
  26:         /// <param name="sender">The sender.</param>
  27:         /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
  28:         protected virtual void OnError(object sender, EventArgs e)
  29:         {
  30:             HttpApplication application = (HttpApplication)sender;
  31:             HttpContext context = application.Context;
  32:             if (context != null && context.AllErrors != null)
  33:             {
  34:                 foreach (Exception ex in context.AllErrors)
  35:                 {
  36:                     ex.Data["RawUrl"] = context.Request.RawUrl;
  37:                     HttpException hex = ex as HttpException;
  38:                     if (hex != null && hex.GetHttpCode() == (int)HttpStatusCode.NotFound)
  39:                     {
  40:                         Logging.Logger.LogWarning(string.Format(System.Globalization.CultureInfo.InvariantCulture, "Requested File Not Found {0} ({1})", context.Request.RawUrl, context.Request.Url));
  41:                     }
  42:                     else
  43:                     {
  44:                         Logging.Logger.Log(ex);
  45:                     }
  46:                    
  47:                 }
  48:             }
  49:             HttpException httpException = context.Error as HttpException;
  50:             context.Response.Clear();
  51:             if (httpException != null && !IsResourceRequest(context.CurrentHandler))
  52:                 context.Response.StatusCode = httpException.GetHttpCode();
  53:             else
  54:                 context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
  55:             if (((context.IsCustomErrorEnabled && !context.Request.Browser.Crawler) || IsResourceRequest(context.CurrentHandler) )
  56:                 && !IsAnErrorPage(context.Request.RawUrl))
  57:             {
  58:                 System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(_random.Next(MaxDelay)));
  59:                 context.ClearError();
  60:                 string path = GetPathForError(context, (HttpStatusCode)context.Response.StatusCode);
  61:                 if (!string.IsNullOrEmpty(path))
  62:                 {
  63:                    if (CustomErrors.RedirectMode == CustomErrorsRedirectMode.ResponseRedirect && !IsResourceRequest(context.CurrentHandler) )
  64:                    {
  65:                        context.Response.Redirect(path, true);
  66:                    }
  67:                    else
  68:                    {
  69:                        context.RewritePath(path);
  70:                    }
  71:                 }
  72:             }
  73:         }
  74:  
  75:         /// <summary>
  76:         /// Determines whether current request is to a resource handler
  77:         /// </summary>
  78:         /// <param name="handler">The handler.</param>
  79:         /// <returns>
  80:         ///     <c>true</c> if [is resource request] [the specified handler]; otherwise, <c>false</c>.
  81:         /// </returns>
  82:         protected virtual bool IsResourceRequest(IHttpHandler handler)
  83:         {
  84:             return handler != null
  85:                 &&
  86:                 (typeof(System.Web.Handlers.AssemblyResourceLoader).IsInstanceOfType(handler)
  87:                 || typeof(System.Web.Handlers.ScriptResourceHandler).IsInstanceOfType(handler));
  88:  
  89:         }
  90:  
  91:         /// <summary>
  92:         /// Gets the path for error.
  93:         /// </summary>
  94:         /// <param name="current">The current.</param>
  95:         /// <param name="status">The status.</param>
  96:         /// <returns></returns>
  97:         protected virtual string GetPathForError(HttpContext current, HttpStatusCode status)
  98:         {
  99:             foreach (CustomError ce in customErrors.Errors)
 100:             {
 101:                 if (ce.StatusCode == (int)status) return ce.Redirect;
 102:             }
 103:             return customErrors.DefaultRedirect;
 104:         }
 105:  
 106:         /// <summary>
 107:         /// Determines whether the given path (RawUrl) is an error page itself
 108:         /// </summary>
 109:         /// <param name="path">The path.</param>
 110:         /// <returns>
 111:         ///     <c>true</c> if [is an error page] [the specified path]; otherwise, <c>false</c>.
 112:         /// </returns>
 113:         protected virtual bool IsAnErrorPage(string path)
 114:         {
 115:             if (ErrorPages != null)
 116:             {
 117:                 foreach (string s in ErrorPages)
 118:                 {
 119:                     if (path.IndexOf(s, StringComparison.OrdinalIgnoreCase) > -1) return true;
 120:                 }
 121:             }
 122:             return false;
 123:         }
 124:  
 125:         /// <summary>
 126:         /// Gets the error pages.
 127:         /// </summary>
 128:         /// <value>The error pages.</value>
 129:         protected virtual IEnumerable<string> ErrorPages
 130:         {
 131:             get
 132:             {
 133:                 
 134:                 foreach (CustomError ce in customErrors.Errors)
 135:                 {
 136:                     yield return ce.Redirect;
 137:                 }
 138:                 yield return customErrors.DefaultRedirect;
 139:             }
 140:         }
 141:  
 142:        /// <summary>
 143:        /// Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"/>.
 144:        /// </summary>
 145:        public void Dispose()
 146:        {
 147:            //clean-up code here.
 148:        }
 149:  
 150:        /// <summary>
 151:        /// Initializes a module and prepares it to handle requests.
 152:        /// </summary>
 153:        /// <param name="context">An <see cref="T:System.Web.HttpApplication"/> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application</param>
 154:        public void Init(HttpApplication context)
 155:        {
 156:            // Below is an example of how you can handle LogRequest event and provide 
 157:            // custom logging implementation for it
 158:            context.Error += new EventHandler(OnError);
 159:        }
 160:  
 161:  
 162:     }
 163: }

The amendments hinge on the fact that the exploit only really affects the WebResource.axd and ScriptResource.axd so any error relating from these handlers is automatically given an 500 status (Internal Server Error) and treated as a normal error.  This is an acceptable compromise for me as all references to these handlers should be programmatically generated and by your site and therefore ‘correct’.

As per, Scott Gu’s recommendation I’ve added a random <500ms delay to the processing of all errors to help muddy the waters and added support for the ResponseRewrite property on the CustomErrors element.

Sunday, 5 September 2010

Yummy! Cleaner, sweeter WCF proxy usage with DynamicTidyProxy!

This is a follow up to my previous (in fact my first) post “Mmmmm… clean, sweet WCF proxy usage with TidyProxy!”, which showed how to safely use an arbitrary WCF proxy class safely. 

A substantial part of my day-to-day development is working with WCF services, mainly web and MSMQ and after a few teething troubles everything’s been ticking along nicely.  Generally these are internal windows->web comm’s where I can control both client and server implementations, so lots of shared types.

However, one of my fastest ever growing webservice API’s recently hit a metadata limit (apparently there’s are reason for the up to 30 methods guideline!) which meant that neither VS2008 or svcutil could reliable generate usable proxies without altering the standard configuration of every machine which may need to generate proxies. 

So after a monster refactoring session to break down my one service into fourteen (14! – how did it get that big!) smaller, more cohesive services – I’m faced with an even more mammoth task of retrofitting the smaller services in place of the larger one.  Clearly, this was not going to be pretty as we’ll need to replace the one service reference with fourteenAs this service is used across several projects, this could result in me being nailed to a wall.

After a bit a Googling, Reflectoring and inspecting the Generated Code it quickly became apparent that for my case, I didn’t need to be generating proxies at all!  As, all the types were shared between client and server using common libraries and the WCF configuration in config files, I could just wrap the ChannelFactory<>.  This clever class does all the heavy lifting in WCF and only needs the service contract interface and an endpoint name!

As if by magic, the Proxy appeared…
   1: using System;
   2: using System.ServiceModel;
   3:  
   4: namespace MartinOnDotNet.Helpers.WCF
   5: {
   6:     /// <summary>
   7:     /// Dynamically create a WCF proxy class using the given interface
   8:     /// </summary>
   9:     /// <typeparam name="TServiceContract">The type of the service contract.</typeparam>
  10:     public class DynamicTidyProxy<TServiceContract> : IDisposable
  11:     {
  12:  
  13:         /// <summary>
  14:         /// Initializes a new instance of the <see cref="DynamicTidyProxy&lt;TServiceContract&gt;"/> class.
  15:         /// </summary>
  16:         public DynamicTidyProxy()
  17:         {
  18:             EndpointName = typeof(TServiceContract).Name;
  19:         }
  20:  
  21:         /// <summary>
  22:         /// Initializes a new instance of the <see cref="DynamicTidyProxy&lt;TServiceContract&gt;"/> class.
  23:         /// </summary>
  24:         /// <param name="endpointConfigurationName">Name of the endpoint configuration.</param>
  25:         public DynamicTidyProxy(string endpointConfigurationName)
  26:         {
  27:             EndpointName = endpointConfigurationName;
  28:         }
  29:  
  30:         /// <summary>
  31:         /// Gets or sets the name of the endpoint.
  32:         /// </summary>
  33:         /// <value>The name of the endpoint.</value>
  34:         public string EndpointName { get; set; }
  35:  
  36:         private ChannelFactory<TServiceContract> _channel;
  37:         private TServiceContract _client;
  38:  
  39:         /// <summary>
  40:         /// Gets the client.
  41:         /// </summary>
  42:         /// <value>The client.</value>
  43:         public TServiceContract Client
  44:         {
  45:             get
  46:             {
  47:                 if (_client == null)
  48:                 {
  49:                     if (!typeof(TServiceContract).IsInterface) throw new NotSupportedException("TServiceContract must be an interface!");
  50:                     if (string.IsNullOrEmpty(EndpointName)) throw new NotSupportedException("EndpointName must be set prior to use!");
  51:                     _channel = new ChannelFactory<TServiceContract>(EndpointName);
  52:                     _client = _channel.CreateChannel();
  53:                 }
  54:                 return _client;
  55:             }
  56:  
  57:         }
  58:  
  59:  
  60:  
  61:         #region IDisposable Members
  62:  
  63:         /// <summary>
  64:         /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  65:         /// </summary>
  66:         public void Dispose()
  67:         {
  68:             if (_channel != null)
  69:             {
  70:                 _channel.CloseConnection();
  71:             }
  72:             GC.SuppressFinalize(this);
  73:         }
  74:  
  75:         #endregion
  76:     }
  77: }

I’ve coded the default constructor to use the service contract interface type name… so make sure your config file matches!

You can then dynamically create WCF proxies in code using:

   1: using (var proxy = new DynamicTidyProxy<IMagicComplexServiceContract>())       
   2: {      
   3:   proxy.Client.DoSomeMagicallyComplexOperation();     
   4: }

No svcutil, or VS2008 Service Reference Required!

It also means, I didn’t have to spend all that time refactoring! (today at least…it was going to happen - promise)

Tidying Up

You’ve probably noticed the subtle “_channel.CloseConnection()” in the DynamicTidyProxy Dispose Method…this is simply an extension method that wraps all of the required WCF boiler plate code…

   1: // <summary>
   2: /// Safely closes a service client connection.
   3: /// </summary>
   4: /// <param name="serviceClient">The service client.</param>
   5: public static void CloseConnection(this ICommunicationObject serviceClient)
   6: {
   7:     if (serviceClient == null) return;
   8:     try
   9:     {
  10:         if (serviceClient.State == CommunicationState.Opened)
  11:         {
  12:             serviceClient.Close();
  13:         }
  14:         else
  15:         {
  16:             serviceClient.Abort();
  17:         }
  18:     }
  19:     catch (CommunicationException ex)
  20:     {
  21:         Logging.Logger.Log(ex);
  22:         try
  23:         {
  24:             serviceClient.Abort();
  25:         }
  26:         catch { } //nasty but nothing useful can be found by logging this exception as secondary issue
  27:     }
  28:     catch (TimeoutException ex)
  29:     {
  30:         Logging.Logger.Log(ex);
  31:         try
  32:         {
  33:             serviceClient.Abort();
  34:         }
  35:         catch { }//nasty but nothing useful can be found by logging this exception as secondary issue
  36:     }
  37:     catch (Exception ex)
  38:     {
  39:         Logging.Logger.Log(ex);
  40:         try
  41:         {
  42:             serviceClient.Abort();
  43:         }
  44:         catch { }//nasty but nothing useful can be found by logging this exception as secondary issue
  45:         throw;
  46:  
  47:     }
  48: }