Robots Break

Adding captcha without touching website code

As online business owners, we constantly face obstacles which consume our time and resources, and instead of focusing on business growth, we have to postpone important tasks until the obstacle has been eliminated, otherwise it can damage our reputation and destroy our business.

Some of such obstacles is abuse of critical business endpoints by bots, and this can lead to:

  • Fake Accounts, which affect user engagement metrics and create false impression of user base
  • Spam Entries, which make the website appear unprofessional
  • Resource Wastage, which can slow down the website and increase hosting costs
  • Security Threats, which can lead to data breaches and unauthorised access
  • User Frustration, because spam can frustrate genuine users and drive them away from the website

Unfortunately, there is not silver bullet, even digital giants struggle to mitigate bots impact on their business. However, what we all know is that captcha is a solution to bots and bots impact. Captcha has high effectiveness. High effectiveness and high annoyance.

  • Registering a new account? – Please, solve captcha!
  • Posting a comment? – Please, solve captcha!
  • Want to login? – Please, solve captcha!
  • Deleting account and leaving the website because of captcha? – Please, solve captcha!

Of course, nobody loves solving captcha. Captcha can damage business more than bots if it is used heedlessly.
Thankfully, captcha services have progressed and provide now the “invisible” mode: captcha verifies whether the current visitor is a genuine user or not without requiring the user to solve anything. One of such captcha services with the invisible mode is Turnstile by Cloudflare.

So, we have a captcha service, the next question is how hard is its integration?

Integration

Captcha integration has several stages:

  • Captcha should be loaded in the visitor’s browser when they are visiting our website
  • On some actions the captcha should be solved, and its response should be sent along with forms and other requests
  • The server and scripts which process the requests should verify the captcha response and act accordingly

It sounds like: ouch… ouch… ouch…
Who is going to do that? How much time does that need? Seems like we need an engineer or engineers who know our frontend framework, our backend framework, and can adjust all places in the source code to trigger and verify captcha. Of course, in acceptable time, because it should have been implemented yesterday. And, without causing new issues, because the least thing we want is to break what works. Sounds too complex and impossible, doesn’t it?

But… what if I tell you that you can integrate captcha without touching website code?
It does not matter which frontend framework you use: Angular, React, Vue, SPA, PWA, SSR, you name it.
It does not matter which backend framework you use: Python, PHP, Golang, Node, you name it.
It does not matter whether it is a classic contact form or a modern AJAX request, if you understand what I mean.

This solution is built on Cloudflare Workers and you can find its source code on GitHub: cf-workers-turnstile-injection. This is the secret sauce which will inject Turnstile and verify its responses.

The only thing which really matters here is Cloudflare: your website should be on Cloudflare.
If it is not, feel free to sign up and add your website: https://dash.cloudflare.com/sign-up.
Cloudflare provides quite a decent amount of free services which help to protect and accelerate your website, and the solution in this article is based on them, so you do not need a penny to implement it.

Let’s use this blog as an example. The blog is using WordPress under the hood. And its admin login form is located here: https://sudo.eu/wp-login.php. Because WordPress is very popular (43% of websites are built on it according to 2023 stats), there is a ton of scripts and bots on the Internet which try to find a vulnerability or brute force login / password and gain unauthorised admin access. And, because WordPress is very popular, its zero day vulnerability will be a huge disaster. Despite the fact that WordPress team will patch it quickly, I do not want to wait for the patch and I do not want to wait for my developers to apply it on this blog, I do not want to challenge the security strength of WordPress at any point in time. I only want to be sure that nothing automated can touch the login form of this blog even if the login / password combination is correct.

So, let us also assume I do not have access to the source code of this blog, so I cannot install captcha plugins or change anything. But, as it was stated, I want to block those scripts and bots even if they have right login / password combinations.

So my plan is:

  1. Understand places where I want to load captcha
  2. Understand endpoints where I want to verify captcha
  3. Deploy captcha without touching website code
  4. Verify that the login form blocks requests without captcha response

The first step is easy: https://sudo.eu/wp-login.php this is the place where I want to load captcha. With help of Web Inspector, I can check the source code of the login form and find:

<form
  name="loginform"
  id="loginform"
  action="https://sudo.eu/wp-login.php"
  method="post"
  class="shake"
>
  ...
</form>

So, the second step is the same url: https://sudo.eu/wp-login.php, but the request method is POST.

It is time to deploy captcha.
For that, in “Cloudflare Account” > “Turnstile” click “Add site”

The next step is to fill up the form:
– Site name: sudo.eu (you should use your domain name)
– Domain: sudo.eu (you should use your domain name)
– Widget Mode: Invisible
– Pre-clearance: No
– Click “Create” button
– Profit

Copy “Site Key” and “Secret Key” somewhere. We will need them later on.

Now, we need to download the latest release of the workers script: cf-workers-turnstile-injection, our secret sauce.

Unpack the archive with the release and find there index.mjs (not .js, you need .mjs)

Now, it is time to create a workers script on Cloudflare. Go back to “Cloudflare Account” > “Workers & Pages” > “Create application”

On the next screen, click “Workers” > “Create Worker”

Fill up the name, it can be “wp-turnstile”, and click “Deploy”

Congratulations! Click “Configure Worker” button now

On the configuration screen, click “Settings” > “Variables” > Add variable

Remember “Site Key” and “Secret Key”? This is the time to set them here as encrypted variables.
The first variable should be called TURNSTILE_SITE_KEY and its value should be “Site Key”.
The second variable should be called TURNSTILE_SECRET_KEY and its value should be “Secret Key”.
Don’t forget to click “Encrypt” and “Save and deploy”.

Great, the next step is to edit its code, click “Quick edit” at the top.

You will get a boilerplate of worker.js

Remember “index.mjs” (not .js)? Simply drag & drop it into “workers.js”, you will get a new tab with a strange name

Right click on the tab and choose “Close”

Click “Save” in the opened dialog window

In the new window, click “worker.js”, so the file name is “/wp-turnstile/worker.js”, and click “OK”

Very good, now you can click “Save and deploy” at the top right

In the opened dialog window, click “Save and deploy” again

If everything worked out well, you should still see the editor, click the back link at the top left

So far so good. We have a turnstile widget ready, we have a workers script ready. The next step is to configure the workers script to load captcha and to verify it on /wp-login.php. To do so, we should consider two things: triggers to execute the workers script and paths we want it to verify requests on.

In our case, all of them are https://sudo.eu/wp-login.php, so we can go to “Triggers” > Routes > “Add route”

The route is “https://sudo.eu/wp-login.php*” (notice * at the end), replace sudo.eu with your domain name.
The zone is “sudo.eu” (choose here your domain name)
Click “Add route”

And, we need one more for special javascript to inject Turnstile in forms and AJAX requests:

The route is “https://sudo.eu/cftsc.js*” (notice * at the end), replace sudo.eu with your domain name.
The zone is “sudo.eu” (choose here your domain name)
Click “Add route”

Profit

The default route “wp-turnstile.satan-time.workers.dev” can be disabled.

Time to verify, let us open https://sudo.eu/wp-login.php and inspect its source code.
It should have a turnstile response.

That is great, the a response is here. Now, let us submit the login form and check the request body, it should have the turnstile response among the login form data.

Not bad! It is working, and we have not touched the website code.

Let us also check analytics in “Cloudflare Account” > “Turnstile” > “sudo.eu”, it should have some stats

It does, that is cool! But, until now, we only have something what adds turnstile responses to the login form. Nothing blocks requests without turnstile responses.

This is managed by TURNSTILE_ACTION variable. By default, the action is to attach two headers: “X-Turnstile-Success” and “X-Turnstile-Time”, but, in our case, we agreed we cannot touch the source code of the website, so we cannot write code which would check that “X-Turnstile-Success” equals “yes” or otherwise the login process is terminated.
The only value TURNSTILE_ACTION supports at the moment of writing this article is “block”, and this is exactly what we need!

There are also a couple of extra things we need. We need maximum security! So, we need to let the workers script accept turnstile responses only from sudo.eu (not from any other domain the turnstile widget may support). And, the verification of the turnstile response should be only for POST / PUT requests to https://sudo.eu/wp-login.php, because the triggers of workers may cover not only “wp-login.php”, but many other pages or whole domains in the future.

The answer here is additional variables: TURNSTILE_FRONTENDS and TURNSTILE_BACKENDS. So let us go back to “Cloudflare Account” > “Workers & Pages” > “wp-turnstile” > “Settings” > “Variables” > “Edit variables”

Here we need to add three variables:
TURNSTILE_ACTION “block”
TURNSTILE_FRONTENDS “sudo.eu”
TURNSTILE_BACKENDS “sudo.eu/wp-login.php”
– no need to encrypt them
– click “Save and deploy”

Let us check the form again. Now, if I try to bypass the captcha, I expect the request to be blocked.

Juicy! Awesome! Unbelievable! Exactly what I wanted! Security on the edge without touching website code!

Bots which we do not expect can be a real problem for online business. They come silently among genuine visitors, do things we don’t see, and cause harm we don’t notice at the beginning. And it is even not the whole issue. We should not forget about complexity of our own infrastructure and services we run: some of them are legacy nobody wants to deal with, some of them are from 3rd-party vendors nobody can change without escalations and patch requests. But bot owners won’t wait, and we should have security in place from the very beginning in every service we run despite its status: legacy, 3rd-party, it all does not matter, they have to have contemporary and comprehensive protection. This is where Cloudflare and its services, such as Turnstile and Workers, come to rescue. Without touching website code, we can customise its behaviour, enrich security and protect critical endpoints to avoid damage to our business, and genuine users are still happy, because they do not need to solve captcha.

Stay tuned!

P.S.

cf-workers-turnstile-injection is open source software licensed under the Apache License v2.0 and is maintained by me. It was created for demo purpose and if you are facing any issues, please, do not hesitate to reach out to me.

satanTime

I enjoy absorbing knowledge, sharing it, and helping customers to build solutions which achieve their business goals in the most optimal way.