Sunday, November 20, 2011

Trapping spambots with honeypots in MVC

Form spam is still a real issue for any public website. Spambots are mostly very primitive scripts. They’ll scan HTML for form fields, fill all of them with some value and submit the form. Using regular textbox fields, hidden from ‘real’ users by CSS, you can trick spam bots into filling in fields a normal user would leave blank. This gives you an easy way to tell people from bots.

Unobtrusive MVC honeypots
It’s quite straight forward to implement honeypot fields in ASP.NET MVC, but of course it should be as unobtrusive as possible. So, what I’ve come up with is an action filter to help out:

[HttpPost]
[HoneypotCaptcha("UserName")]
public virtual ActionResult Questionnaire( InputModel model )
{
...
}

The action filter checks the specified field in the post data, it must be present but empty. If it’s not empty an HTTP Exception with a 403 (access denied) status code is thrown.

public class HoneypotCaptchaAttribute : ActionFilterAttribute
{
  public HoneypotCaptchaAttribute( string formField )
  {
     if ( string.IsNullOrWhiteSpace( formField ) ) throw new ArgumentNullException( "formField" );
     FormField = formField;
  }

  public override void OnActionExecuting( ActionExecutingContext filterContext )
  {
     if ( filterContext.HttpContext.Request.HttpMethod == "POST" )
     {
        var value = filterContext.HttpContext.Request.Form[FormField];

        if ( value == null || value.Length > 0 )
        {
           throw new HttpException( 403, "Stop spamming this site." );
        }
     }
     base.OnActionExecuting( filterContext );
  }

  public string FormField { get; set; }
}

Html helper

To use the honeypot action filter, insert a form field and make it invisible using CSS.

<html>
  <head>
    <style type="text/css">
     .pooh { display: none; }
    <style>
  </head>
  </body>
    <% using( Html.Form() ) {%>
    <!-- your form stuff here -->
    <div class="pooh"><%: Html.Honeypot( "UserName" ) %></div>
    <% } %>
  <body>
<html>

This injects a label and text field:

public static class FormExtensions
{
    public static MvcHtmlString Honeypot(this HtmlHelper htmlHelper, string fieldName )
    {
       return MvcHtmlString.Create( htmlHelper.Label( fieldName, fieldName ).ToString() + htmlHelper.TextBox( fieldName, "" ) );
    }
}

If using this solution in a larger app you'll probably want to include the div in the helper and setup the CSS in the main style sheet.

Monday, September 12, 2011

Fixing Sitefinity 3.7 URL handling

One of the things I’ve been wrestling with lately is Sitefinity’s somewhat outdated way of handling URL’s. There are two issues that can be fixed relatively easily: PathInfo handling and support for IIS URL rewriting module.

UPDATE: There’s a follow up to this article with more fixes.

PathInfo
Sitefinity seems to have been built with a notion that all URL’s look like this:

www.example.com/documentation/userguide.aspx?version=2.3.6

A direct url to a page and optionally a query string (the part after ?).
However, nowadays we like pretty url’s, and for good reasons.
A prettier version of the above url could look like this:

www.example.com/documentation/userguide.aspx/2.3.6

It’s even better to get rid of the .aspx file extension, but that’s beyond the scope of this post. The bit of the URL after .aspx is known in ASP.NET as PathInfo.

If you try to implement this type of URL in Sitefinity you’ll run into some oddity in the way Sitefinity parses URL’s.

Sitefinity scans the URL to find the page name by looking for the last period (.) in the path (excluding the query string). In the first URL, that’ll work. In the second URL, it won’t. It’ll give a 404 because Sitefinity thinks you’re not requesting a resource managed by the CMS.

The remedy is to hand Sitefinity only the path to the page and the query string and strip out the additional path information. No actual information is lost since the ASP.NET Request object will still hold the original URL, including the path info. We’re just not telling Sitefinity about it…

URL rewriting
Another issue I have with Sitefinity is in the way URL rewriting is handled. Sitefinity offers it’s own Advanced URL rewriting; which basically consists of regex replacements. It works just fine. However there’s also an IIS Rewrite Module that does the same thing (and a lot more) and it’s fully integrated with IIS7 admin console for easy management.

Upon rewriting a URL the IIS Rewrite Module modifies the HttpRequest.Url property.
Sitefinity however does not get that modified URL because it works with HttpRequest.RawUrl ( also reported by Joe Groner here) which contains the URL requested by the browser.

The fact that these properties differ also enables us to detect whether a URL has been rewritten. The fix is to return the rewritten URL in the same way that Sitefinity’s own rewriting mechanism would.

Update: There is one small detail that should not be overlooked. The Uri.PathAndQuery property returns an escaped path where HttpRequest.RawUrl is an unescaped. This means the path returned by Uri.PathAndQuery needs to be unescaped otherwise encoded characters in the path (like %20 for space) will not be handled correctly. In Integrated Pipeline mode it could even mean that requests for static files will fail with 404.0 – Not found.

Implementation
Both fixes must be applied to the HttpModule used by Sitefinty as it’s main entry point: CmsHttpModule. To allow Sitefinity’s own rewriting logic to continue to work I’ve subclassed Telerik.Cms.Web.CmsHttpModuleUrlRewrite (classic ASP.NET pipeline) and Telerik.Cms.Web.CmsHttpModuleIIS7Integrated (integrated pipeline).

Full source on github: https://gist.github.com/1164613
The required changes to web.config are also listed there.

UPDATE: Please see the follow up post for more information and a link to the GitHub repository.

Please note that this solution is not intended for sites that use extensionless URL’s.

Tuesday, May 3, 2011

Splash pages for Sitefinity

Up on Github now is a tiny Sitefinity control called ‘Splash’ that enables splash pages on Sitefinity-based web sites. The control supports both Sitefinity 3.7 and 4.1.

A splash page is one of those things that can really help generate attention to a specific topic or product on your site. Basically what happens is that the user is redirected to a dedicated page that highlights only one specific topic. The splash page should offer the user the option to resume browsing or get more information.

The control allows any Sitefinity user to setup a splash page and configure when to start showing it and when to stop.

Redirecting users to a page they did not request is something that can get annoying really quickly so this control uses cookies to make sure the page is shown once to each user.

The Splash control is freely available, licensed under a BSD license. All contributions and comments are welcome.

Source code
Download latest

[UPDATE] Splash pages for Sitefinity is now Sitefinity Validated and available on the Sitefinity Marketplace.

Sunday, February 13, 2011

Web Deploy : Customizing a deployment package

Microsoft’s Web Deployment Tool is one of those great improvements for managing ASP.NET applications that I’ve been wanting to give try. Ever since Scott Hanselman wrote about it.

Web Deploy was created to automate common deployment tasks by packaging ASP.NET applications into a .zip with a manifest. It supports both command line deployment and an interactive mode, through the IIS management console.

This post is the result of my first attempt to roll out package based deployment using Web Deploy at a customer.

It’s been sort of a bumpy ride getting everything implemented but the tool definitely delivers what it promises; seamless deployment for your web app.

Here’s what I set out to do:

  1. Build a deployment package for an existing application
  2. Verify the correct pipeline mode on deployment
  3. Include an empty folder (not something supported out of the box, oddly enough)
  4. Give write access to specific folders
  5. Prevent specific folders from being cleared on deployment
  6. Customize the deployment package name to include a version and/or build number

I managed to get all of these implemented, mostly by hand-authoring an MSBuild script. One thing that disappointed me though was that interactive installation does not allow me to prevent specific folders from being cleared. That’s for command line deployment only.

In this post I’ll first give some quick pointers on how to get started with Web Deploy and building packages. After that, I’ll work down the list of requirements posted above one by one.