Preventing automated sign-ups

The Session goes through periods of getting spammed with automated sign-ups. I’m not sure why. It’s not like they do anything with the accounts. They’re just created and then they sit there (until I delete them).

In the past I’ve dealt with them in an ad-hoc way. If the sign-ups were all coming from the same IP addresses, I could block them. If the sign-ups showed some pattern in the usernames or emails, I could use that to block them.

Recently though, there was a spate of sign-ups that didn’t have any patterns, all coming from different IP addresses.

I decided it was time to knuckle down and figure out a way to prevent automated sign-ups.

I knew what I didn’t want to do. I didn’t want to put any obstacles in the way of genuine sign-ups. There’d be no CAPTCHAs or other “prove you’re a human” shite. That’s the airport security model: inconvenience everyone to stop a tiny number of bad actors.

The first step I took was the bare minimum. I added two form fields—called “wheat” and “chaff”—that are randomly generated every time the sign-up form is loaded. There’s a connection between those two fields that I can check on the server.

Here’s how I’m generating the fields in PHP:

$saltstring = 'A string known only to me.';
$wheat = base64_encode(openssl_random_pseudo_bytes(16));
$chaff = password_hash($saltstring.$wheat, PASSWORD_BCRYPT);

See how the fields are generated from a combination of random bytes and a string of characters never revealed on the client? To keep it from goint stale, this string—the salt—includes something related to the current date.

Now when the form is submitted, I can check to see if the relationship holds true:

if (!password_verify($saltstring.$_POST['wheat'], $_POST['chaff'])) {
    // Spammer!
}

That’s just the first line of defence. After thinking about it for a while, I came to conclusion that it wasn’t enough to just generate some random form field values; I needed to generate random form field names.

Previously, the names for the form fields were easily-guessable: “username”, “password”, “email”. What I needed to do was generate unique form field names every time the sign-up page was loaded.

First of all, I create a one-time password:

$otp = base64_encode(openssl_random_pseudo_bytes(16));

Now I generate form field names by hashing that random value with known strings (“username”, “password”, “email”) together with a salt string known only to me.

$otp_hashed_for_username = md5($saltstring.'username'.$otp);
$otp_hashed_for_password = md5($saltstring.'password'.$otp);
$otp_hashed_for_email = md5($saltstring.'email'.$otp);

Those are all used for form field names on the client, like this:

<input type="text" name="<?php echo $otp_hashed_for_username; ?>">
<input type="password" name="<?php echo $otp_hashed_for_password; ?>">
<input type="email" name="<?php echo $otp_hashed_for_email; ?>">

(Remember, the name—or the ID—of the form field makes no difference to semantics or accessibility; the accessible name is derived from the associated label element.)

The one-time password also becomes a form field on the client:

<input type="hidden" name="otp" value="<?php echo $otp; ?>">

When the form is submitted, I use the value of that form field along with the salt string to recreate the field names:

$otp_hashed_for_username = md5($saltstring.'username'.$_POST['otp']);
$otp_hashed_for_password = md5($saltstring.'password'.$_POST['otp']);
$otp_hashed_for_email = md5($saltstring.'email'.$_POST['otp']);

If those form fields don’t exist, the sign-up is rejected.

As an added extra, I leave honeypot hidden forms named “username”, “password”, and “email”. If any of those fields are filled out, the sign-up is rejected.

I put that code live and the automated sign-ups stopped straight away.

It’s not entirely foolproof. It would be possible to create an automated sign-up system that grabs the names of the form fields from the sign-up form each time. But this puts enough friction in the way to make automated sign-ups a pain.

You can view source on the sign-up page to see what the form fields are like.

I used the same technique on the contact page to prevent automated spam there too.

Have you published a response to this? :

Responses

Manton Reece

Here’s what the new prompt for email newsletter subscriptions looks like for your blog. The extra step lets us generate some random values hidden from real users, inspired by this post from Jeremy Keith, to make it a tiny bit harder for bots. Could do more later now that this is in place.

7 Likes

# Liked by Royce Williams on Monday, September 30th, 2024 at 4:12pm

# Liked by Chris Silverman 🌻 on Monday, September 30th, 2024 at 4:44pm

# Liked by Jason Crowther on Monday, September 30th, 2024 at 5:10pm

# Liked by Trey on Monday, September 30th, 2024 at 7:13pm

# Liked by Thomas Vander Wal on Tuesday, October 1st, 2024 at 12:53am

# Liked by Benjamin on Tuesday, October 1st, 2024 at 7:45am

# Liked by felipe on Tuesday, October 1st, 2024 at 12:20pm

Related posts

Train coding

Generating a static copy of The Session from the comfort of European trains.

Securing client-side JavaScript

Tightening up my content security policy.

My approach to HTML web components

Naming custom elements, naming attributes, the single responsibility principle, and communicating across components.

Secure tunes

Closing a security hole on The Session.

Progressive disclosure with HTML

The `details` element is like the TL;DR of markup.

Related links

SCALABLE: Save form data to localStorage and auto-complete on refresh

When I was in Amsterdam I was really impressed with the code that Rose was writing and I encouraged her to share it. Here it is: drop this script into a web page with a form to have its values automatically saved into local storage (and automatically loaded into the form if something goes wrong before the form is submitted).

Tagged with

An example of an HTML Web Component | Go Make Things

Another example of an HTML web component from Chris, who concludes:

Web Components are rapidly becoming my preferred way to add progressive enhancement to HTML elements.

Tagged with

HTML Web Components | Go Make Things

Chris walks through a really good example of an HTML web component he made for NASA: wrapping a regular form element in a custom element to add Ajax functionality.

This approach let me slash the JavaScript used for this project in half, easily progressively enhance the UI, and provide an authoring approach that’s much easier to read and make sense of.

Tagged with

Bring Focus to the First Form Field with an Error :: Aaron Gustafson

A handy little script from Aaron to improve the form validation experience.

Tagged with

The ‘Form’ Element Created the Modern Web. Was It a Big Mistake? | WIRED

Paul Ford:

The web was born to distribute information on computers, but the technology industry can never leave well enough alone. It needs to make everything into software. To the point that your internet browser is basically no longer a magical book of links but a virtual machine that can simulate a full-fledged computer.

Tagged with

Previously on this day

2 years ago I wrote Supporting logical properties

Using the CSS trinity of feature queries, logical properties, and unset.

3 years ago I wrote Twenty years of writing on my website

This online journal is two decades old.

8 years ago I wrote Someday

Changing defaults in browsers …someday.

13 years ago I wrote Ten

Happy birthday to this.

15 years ago I wrote Culchavulching

In Brighton, no-one can hear you geek out.

15 years ago I wrote Full Frontal

Be in Brighton on November 20th.

16 years ago I wrote Reading immaterial

Something for your digital bookshelf.

16 years ago I wrote Automata

Carla Diana talks about robots at Flash on the Beach in Brighton.

17 years ago I wrote Kung Shui

Ajax and accessibility; a presentation transcribed.

18 years ago I wrote Sydney to Melbourne

It’s time for me to expand my Australian horizons.

18 years ago I wrote Wrapping up Web Directions South

Two thumbs up from me.

19 years ago I wrote He comes from a land down under

The podcasts and the photos have started coming in from Web Essentials in Syndey. Oh, how I wish I could be there!

21 years ago I wrote WiFi Regained

Remember how I was saying that the wireless reception in my iBook went all screwy a while back? Well, I sent the iBook off to Apple so that they could have a look at it.

22 years ago I wrote Anniversary

I have been blogging now for exactly one year.

22 years ago I wrote dooce

Rejoice! For Heather Hamilton is back.

22 years ago I wrote JCPenney

Jessica refuses to believe that JCPenney are actually selling the "Forward Command Post" model in their catalogue:

23 years ago I wrote Welcome to my world

This is my first entry in my first online journal.