DEV Community

Butterfly88
Butterfly88

Posted on

What happens when a bot gets past your CAPTCHA?

CAPTCHA is good at stopping automated registrations. But what happens when the registration succeeds?

That was the problem I ran into on a WordPress community I run.

We started seeing new accounts that looked seemingly normal during registration. The usual registration protections did their job. There was no obvious reason to block the account.

The suspicious activity started later.

The problem moved inside the community

After creating an account, some of these users started sending private messages to other members. Sometimes in obvious spam waves and other times using more refined scam methods.

With the latter, the first message looked somewhat out of the ordinary, but otherwise seemingly harmless. After a short conversation, they tried to move the conversation to TeleGram or another messaging service. Some of the patterns looked like dating scams. Others involved cryptocurrency.

What made this interesting from a technical point of view was that the account itself had already passed every bot protection check because it had already passed registration. Once logged in, most anti-bot solutions are no longer in effect.

From WordPress's point of view, it was simply an authenticated user sending a message. That made me realize that registration protection and (private) messaging protection are two different problems.

Why CAPTCHA could not solve this

A CAPTCHA can help answer:

Is this registration likely to be automated?

It does not answer:

Is this newly registered account behaving like an automated spam account?

And it certainly does not answer:

Is this person using private messaging to approach dozens of members with essentially the same message?

Those signals only become visible after the account has entered the community and I started getting reports from my users. Which is when I started looking at the messaging layer instead.

What I started looking for

One of the first things I noticed was that simple message counters were not enough. I already had these in place, but the spammy accounts would patiently wait for the message interval to reset. They were also able to alternate between messages.

That meant I needed to look at several signals together.

For example:

  • How recently the account was created?
  • Were there repeated or very similar messages?
  • Are there any email addresses and/or phone numbers in messages?
  • Are there any links and messaging-service references?
  • Are there any signs of automated clients or any unusual combinations of browser and client information?
  • Had the account had enough time to establish the normal activity you would expect from a new member?

None of these signals is necessarily enough on its own, but together they can tell a very different story.

Building the protection

Like many others, the community I run uses BuddyPress and Better Messages for private messaging. After fruitless searches around the web for a solution, I eventually built a protection system as an addon rather than trying to replace the messaging system. Being very happy with the solution for myself, and realizing that a lot of other people I discovered during my searches were facing the same problem, I realized I wanted to share the benefit with the larger community.

The result is a free, open-source WordPress plugin called Anti-Spam Guard for Better Messages.

It can apply different checks at the messaging layer, including new-account limits, duplicate-message detection, contact-detail and link detection, automation checks and safety warnings. There is also a "Log Only" mode.

I built that specifically because I really needed it to fine-tune the settings to my specific needs, before interrupting any normal user behavior. And of course, every website has different needs.

Logging first turned out to be important, because security rules can have false positives and other nasty side effects. Being able to see what would have been blocked before actually blocking anything makes it much easier to tune the rules for a real community.

I realized there is an undercovered part of spam protection

The bigger lesson for me was where the problem actually existed. There are so much plugins and other tools for battling bots and spam, but they only cover the basics, such ungated content and comment forms. But when you run gated content and use private messaging tools, there is less to find.

That distinction is easy to miss when you think of spam protection as a single feature. A site can have a good CAPTCHA, rate limiting and registration checks and still have a problem with what newly created accounts do afterwards. Accounts that can even be created by a normal human, only for automation to take over later.

So once a suspicious account gets through the front door, the private messaging system becomes the interesting attack surface, because it was not protected in the same way as the standard forms.

A second layer of protection

I don't think this means every new account should be treated as malicious, because that would make a community rather unpleasant to use. And user experience is so, so important to me.

So... the more useful approach is to apply additional checks where they make sense and make them proportional to the risk.

A new account sending its first private message is different from a long-standing member having a normal conversation.

That is the gap I was trying to address.

If you run a WordPress community with private messaging, I'd be interested to hear whether you have run into the same problem: registration spam getting through, followed by suspicious activity inside the messaging system.

Top comments (4)

Collapse
 
blobdole profile image
Doug •

No matter how hard you try to block bot accounts at the door, they can always have a human log them in and pass any number of human checks for a few pennies. I have found that a weighted threshold setup for identifying spam accounts before they spam (or at worst right as they start) works really well.

Fake numbers incoming:

  • 80% of spam accounts were created in the last month compared to only 15% of real users so if true, +10 points.
  • 75% of spam accounts have no bio information at all compared to only 25% of real users so if true +7 points.
  • 65% of spam accounts are connecting through known forwarded or foreign IP's compared to only 40% of real users so if true +2 points.
  • 95% of spam accounts do not post a non-spam message in any channel within 5 minutes of joining compared to 60% of real users so if true +3 points.
  • 50% of spam accounts first messages will be posted within a very tight time frame and into every channel they have access to compared to 2% of real users so if true +25 points.

The chance that any one of those things has a false positive is too high to use, but the combination will work well. Keep a running point total and if any account passes a given total value, immediate mute and deletion of their previous 24 hours of posts.

Collapse
 
butterfly88 profile image
Butterfly88 • • Edited

Yes, I agree with the general idea of combining multiple weak signals rather than blocking on a single one. Which is exactly what I intended to achieve with this first version.

You're right that any door check can be passed by a human for a few pennies. That's actually why this plugin doesn't fight at the door at all. It's entirely behavioural: it doesn't care whether the account was made by a bot, it watches what the account does after it gets in. Account age, message timing, the "no normal first message" and the "same message to everyone in minutes" signals you list are all covered, but as deterministic rules that block before the first spam DM is delivered rather than scoring + cleaning up afterwards. In private messaging one delivered scam message is already damage a 24h delete can't undo.

So I think we're looking at two layers of the same problem: preventing suspicious accounts from getting in, and detecting what they do if they make it through.

I also found that simple message counters weren't enough, because the behavior can be spread out or slightly altered to avoid basic duplicate checks. That's actually what led me to build the addon as a messaging-layer protection rather than another registration anti-bot system. The signals can be combined, logged and acted on when the account actually starts doing something suspicious.

One interesting difference in the data, though: on my platform the spam accounts have conspicuously polished profiles. A well-written bio is part of the romance-scam pitch. An empty bio probably correlates strongly with drive-by link spam in public channels, but it inverts for the social-engineering type. As you indicate, this can very strongly. Base rates for the same signal can point in opposite directions depending on who is attacking you, which is why I have every weight configurable.

A running per-account risk score in the log for moderators is a nice fit with the plugin's "observe before you block" mode though, because the signals are already logged, folding them into a single number is cheap. Foreign/forwarded-IP scoring I'd leave to Cloudflare/Turnstile: an external IP-reputation API is a privacy consideration I'd rather not push onto site owners.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.