Honeypot Captcha for Community Server

Comments

A few days ago Phil Haack wrote about Honeypot Captcha:

At the same time, spam bots tend to ignore CSS. For example, if you use CSS to hide a form field (especially via CSS in a separate file), they have a really hard time knowing that the field is not supposed to be visible.

To exploit this, you can create a honeypot form field that should be left blank and then use CSS to hide it from human users, but not bots. When the form is submitted, you check to make sure the value of that form field is blank.

A great idea, which didn’t occur to me before. And Phil wasn’t even the first one with that idea.

I added a “regular” captcha control to my site some time ago, but removed it again after a couple of days. Why burden the innocent commentor? He’s not the problem. The comment spammers are.

So I took the idea of the honeypot and leveraged Community Server’s spam filtering capabilities.

First, I replaced the original Community Server’s WeblogPostCommentForm with my own version by simply copying it to my own assembly. (If you build CS from the SDK, you can also just edit the existing one.)

  1. Added the private member

    private TextBox HoneyPot;
    
  2. Added the property HoneyPotTextBoxId, which references the actual TextBox in the form:

    public string HoneyPotTextBoxId
    {
        get { return ((string)ViewState["HoneyPotTextBoxId"]) ?? string.Empty; }
        set { ViewState["HoneyPotTextBoxId"] = value; }
    }
    
  3. Added following lines to the method AttachChildControls:

    HoneyPot = WeblogControlUtility.Instance().FindControl(this, HoneyPotTextBoxId) as TextBox;
    StringBuilder sb = new StringBuilder();
    sb.AppendLine("\n<script type=\"text/javascript\">");
    sb.AppendLine("document.getElementById(\"" + HoneyPot.ClientID + "\").style.display = \"none\";");
    sb.AppendLine("</script>");
    CSControlUtility.Instance().RegisterStartupScript(HoneyPot, typeof (TextBox), "honeypot", sb.ToString(), false);
    

    You see that the honeypot field is hidden dynamically via JavaScript. No CSS is giving a hint to the spammer that the control will not be visible. Instead, as soon as the page is loaded, the textbox will be hidden programmatically.

  4. Added following lines in Submit_Click right before WeblogPosts.Add is called:

    if (!string.IsNullOrEmpty(HoneyPot.Text)) { 
        EventLogs.Info("Spammer entered \"" + HoneyPot.Text + "\" in the honey pot", "Spam Rules", 0); 
        c.SetExtendedAttribute("GotchaInMyHoneyPot", "trapped"); 
    }
    

    So whenever the honeypot textbox is filled in, the comment gets an extended attribute named GotchaInMyHoneyPot.

Now this new form must be used on the actual page. There’s only one page (per theme), which allows visitors to leave comments, which is post.aspx. Unfortunately, this must be done for every theme.

So open post.aspx and replace the original WeblogPostCommentForm with our new one, add the HoneyPotTextBoxId attribute to the form, and add the textbox. Don’t forget to use the same id for the textbox (this example uses tbBody, which shouldn’t ring a bell for the spammer.) Here’s the modified form declaration from the PaperClip theme:

<tfr:HoneyPotWeblogPostCommentForm runat="server" ValidationGroup="CreateCommentForm" 
    MessageTextBoxId="tbComment" 
    NameTextBoxId="tbName" 
    RememberCheckboxId="chkRemember" 
    SubjectTextBoxId="tbTitle" 
    SubmitButtonId="btnSubmit" 
    UrlTextBoxId="tbUrl" 
    ControlIdsToHideFromRegisteredUsers="RememberWrapper" 
    HoneyPotTextBoxId="tbBody" 
    > 
    <SuccessActions> 
        <CSControl:GoToModifiedUrlAction runat="server" QueryStringModification="CommentPosted=true" TargetLocationModification="commentmessage" /> 
    </SuccessActions> 
    <FormTemplate> 
        <fieldset id="commentform"> 
        <legend><CSControl:ResourceControl runat="server" ResourceName="Weblog_CommentForm_WhatDoYouThink" id="rc_think"/></legend> 
            <p /> 
            <div><CSControl:FormLabel runat="server" ResourceName="Title" LabelForId="tbTitle" /> <em>(<CSControl:ResourceControl runat="server" ResourceName="Required"/>)</em><asp:RequiredFieldValidator runat="server" ErrorMessage="*" ControlToValidate="tbTitle" ValidationGroup="CreateCommentForm" /></div> 
            <div><asp:TextBox id="tbTitle" runat="server" CssClass="smallbox" ValidationGroup="CreateCommentForm" /></div> 
            <p /> 
            <div id="NameTitle" runat="server"><CSControl:FormLabel LabelForId="tbName" runat="server" ResourceName="Weblog_CommentForm_Name" /> <em>(<CSControl:ResourceControl runat="server" ResourceName="Required" />)</em><asp:RequiredFieldValidator runat="server" ErrorMessage="*" ControlToValidate="tbName" ValidationGroup="CreateCommentForm" /></div> 
            <div id="NameDesc" runat="server"><asp:TextBox id="tbName" runat="server" CssClass="smallbox" ValidationGroup="CreateCommentForm" /></div> 
            <p /> 
            <div><CSControl:FormLabel runat="server" LabelForId="tbUrl" ResourceName="Weblog_CommentForm_YourUrl" /> <em>(<CSControl:ResourceControl runat="server" ResourceName="Optional" /></em>)</div> 
            <div><asp:TextBox id="tbUrl" runat="server" CssClass="smallbox" ValidationGroup="CreateCommentForm" /></div> 
            <%-- the honeybot textbox --%> 
            <asp:TextBox id="tbBody" runat="server" CssClass="smallbox" ValidationGroup="CreateCommentForm" /> 
            <p /> 
            <div><CSControl:FormLabel LabelForId="tbComment" runat="server" ResourceName="Weblog_CommentForm_Comments" /> <em>(<CSControl:ResourceControl runat="server" ResourceName="Required" />)</em><asp:RequiredFieldValidator runat="server" ErrorMessage="*" ControlToValidate="tbComment" ValidationGroup="CreateCommentForm" /></div> 
            <div><asp:TextBox id="tbComment" runat="server" Rows="5" Columns="25" TextMode="MultiLine" ValidationGroup="CreateCommentForm" /></div> 
            <asp:PlaceHolder runat="server" id="RememberWrapper"> 
                <p /> 
                <div><asp:CheckBox id="chkRemember" runat="server" Text="Remember Me?" ValidationGroup="CreateCommentForm"></asp:CheckBox></div> 
            </asp:PlaceHolder> 
            <p /> 
            <asp:Button id="btnSubmit" runat="server" Text="Submit" ValidationGroup="CreateCommentForm"></asp:Button> 
        </fieldset> 
    </FormTemplate> 
</tfr:HoneyPotWeblogPostCommentForm>

But that’s only the first half. Now whenever the honeypot field is filled with some text the comment will have an extended attribute GotchaInMyHoneyPot.

The second step is to give the comment “spam points”, which is done by a CS spam rule. Keyvan Nayyeri published a complete tutorial how to write your own spam rules, so I won’t get into details. Here’s the complete code for the new rule:

public class HoneyPotRule : BlogSpamRule { 
    private static readonly Guid _ruleId = new Guid("57E216D4-D100-468d-BB37-1B7A0A103CEF"); 
    private const int _defaultPoints = 10; 

    public override ArrayList GetAvailableSettings() { 
        ArrayList list = new ArrayList(); 
        list.Add(new RuleSetting(_ruleId, "points", "Points", _defaultPoints.ToString())); 
        return list; 
    } 
    
    public override int CalculateSpamScore(WeblogPost weblogPost, CSPostEventArgs e) { 
        if (weblogPost.BlogPostType == BlogPostType.Comment) { 
            if (!String.IsNullOrEmpty(weblogPost.GetExtendedAttribute("GotchaInMyHoneyPot"))) { 
                EventLogs.Info("A spammer fell for the honey pot", "Spam Rules", 0); 
                return Globals.SafeInt(GetSettingValue("points"), _defaultPoints); 
            } 
        } 

        return base.CalculateSpamScore(weblogPost, e); 
    } 

    public override string Name { 
        get { return "HoneyPot"; } 
    } 

    public override string Description { 
        get { return "HoneyPot description"; } 
    } 

    public override Guid RuleID { 
        get { return _ruleId; } 
    } 
}

I’m running this solution for a couple of days now on my site, and it works pretty well. It’s amazing how many spam bots fill each and every textbox they can find. But I admit that a lot of steps are involved in my solution, there’s lot of programming required. However, I decided against publishing an all-inclusive package, because I’d like to add this solution to next release of either the CSMVP CSModules or Community Server Stuff. So either follow my instructions above, wait for an official release, or leverage Phil’s honeypot control which is part of the Subkismet project (not released yet too, but you can already get the sources.)

Deflowered twice on one day at nrw07

nrw07 speaker

Last Friday the nrw07 took place in Wuppertal, the biggest community conference in North Rhine-Westphalia. About 100 attendees including the 22 speakers! The Diebels brewery contributes a couple of beer crates, and Subway served lots of sandwiches for lunch.

And I was deflowered twice on that day.

me speaking

First, it was my very first talk! I gave a Community Server presentation a couple of month ago at our local .NET UserGroup, and the other guys suggested that I should repeat that at the nrw07. Unfortunately, I didn´t object enough.

When I sat in the hotel lobby with some other speakers the night before nrw07, I started getting nervous, but they managed to calm me down (or was it the beers we had?). The next day was fine, my talk was one of the first. It ran pretty well (as far as I am concerned). Of course we experienced the usual technical difficulties such as a projector, whose picture was twice as large as the screen and not resizable. Although I got the second largest room in the facility, there were only 7 attendees. Seems to me as Community Server is not of interest for everyone. The good thing about that is that those folks already knew CS, so I didn´t have to start at square one, but instead dive right into the technical stuff. The time flew, I ran over about 10 minutes and had still enough material for another hour. Nevertheless my talk can not have been that bad because the audience asked the right questions afterwards. I hopethink that´s a good sign.

all speakers

My second premiere happened later that night, when Craig Murphy interviewed me for a podcast (not aired yet). At that point in time I already had a couple of beers, so it went quite smooth. It´s self-evident that we talked much about Community Server. Because I mentioned Twitter in my talk, Craig took me up on that, and somehow we drifted into social networking, a topic I´m quite interested in. Even when the podcast was over, we continued the discussion.

To sum it up, it was a great event, where I met many smart people. Many thanks to the orga team, Stephan Oetzel and Daniel Fisher!

And now that I lost my virginity, I´m looking forward to give a talk again next year (assumed they let me on stage ever again)

Here are some other speakers and attendees blogging about nrw07:

I met some more nice guys, who didn´t blog about this event. Nevertheless I´d like to send them my regards because we had such a good time and talks: Andreas Hoffmann, Christian Schütz, Mischa Hüschen, Pascal Kremmers, Constantin Klein, Marcel Gnoth, and Marcel Franke. Sorry if I forgot one, there were so many faces new to me. When you see me next time, just stop by and treat me to a beer...

ReSharper 3.0 Released

Development, Tools Comments

JetBrains released ReSharper 3.0

ReSharper 3.0 is finally out and it´s better than ever. C#, VB.NET, XML, XAML or ASP.NET - we got it all for you right here!

You simply owe it to yourself to test-drive this baby and learn a whole new way to code in Visual Studio!

Get the dirty low-down on ReSharper 3.0 at New Features or use one of the links below:

Grab a free 30-day evaluation of ReSharper at http://www.jetbrains.com/resharper/download/index.html

Twitter Publisher for CruiseControl.NET

Development, ccnet, Twitter Comments

Some weeks ago I posted a CC.NET task which pushes build results to a blog using the MetaWeblogAPI. This might be a feasable solution for projects which sources aren´t updated that often. Otherwise that blog would be really cluttered, and you won´t be able to keep track of all the build results.

Several month ago a new social networking site started called Twitter. It offers a kind of micro-blogging service, allowing its users to send text-only status, up to 140 characters long. Whenever you update your status, it is delivered instantly to other users who have put you to their "friends" list. Though you can receive the updates of your friends via an RSS feed, it is more common to either use Twitter´s website or a desktop client such as TeleTwitter. Additionally, Twitter offers a RESTful API.

Therefore it was pretty obvious to write a CC.NET task which announces new build results on Twitter. The project manager creates a special Twitter account and configures CC.NET to post build results as updates for that user. The developers then just have to add that user to their friend list, and will get the announcements in the Twitter front-end of their choice.

The attached ZIP file contains both the sources and the compiled assembly, which you have to dump into CC.NET´s server directory. The configuration of the task is pretty easy, just specify the user and the password of the Twitter account.

<publishers>
    ...
    <twitter>
        <user>username</user>
        <password>password</password>
    </twitter>
    ...