Writing bots for fun - bypassing hCaptcha

Writing bots for fun - bypassing hCaptcha

Introduction:

In this article, you will see how captcha and bot protections can easily be bypassed or are otherwise ineffective.

I haven’t seen enough good resources about this topic and specifically about bypassing hCaptcha so I have decided to write this article to show how ineffective captchas are.

This website has 200M+ monthly page views and more than 1M App downloads, the amount of manipulation and scams you can do with bots is insane (you’ll need proxies as well), especially on websites with a tremendous amount of traffic like the one I tested.

Writing the bot

I went to the signup page of the company, it contained the usual staff, email, password, Privacy Policy, and of course, captcha.

signup.png

As you can see, the captcha is a hCaptcha from a company called “Intuition Machines Inc” and it’s used by tons of websites in order to “protect” against bots, DDoS attacks, etc.

The first thing I am going to do is to use undetected-chromedriver; it’s a custom selenium chromedriver that patches chromedriver’s binary. It can be done manually as well by editing the binary, but that’s unnecessary in our case.

For the hCaptcha we can use a service called 2captcha; it’s a human-based anti-captcha service. This service works by giving the signup url (ex. example.com/signup) and a sitekey, then it sends our request to their system, one of their Employees solve our captcha and finally we get the captcha token back.

Writing the code

Imports:

import logging
import os
from random import randint
from time import sleep

import undetected_chromedriver as uc
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from twocaptcha import TwoCaptcha

Creating the class and adding the bot’s creds:

class Registration:
    def __init__(self):
        self._email_address = "example@gmail.com"
        self._password = "BotsForEver21!"

        self.driver = uc.Chrome()

Before doing the signup, I’m going to add a new utility method that will help me insert the email & password to the relevant forms:

def _insert_element(self, element, element_data, typing_speed=0.4):
    try:
        ActionChains(self.driver).move_to_element(element).click().perform()
        for c in element_data: 
            element.send_keys(c)
            sleep(typing_speed)
    except Exception as e:
        logger.exception(e)
  • typing_speed is useful in order to simulate a real typing like a user (0.4 seconds per character).

After adding the utility method, we can start writing the register function; we can easily find the email and password element since they have their own id (user_email and user_password).

def register_user(self):
    try:
        self.driver.get("https://www.somebigwebsite.com/signup")
        sleep(randint(5, 7))

        # insert email
        email_element = WebDriverWait(self.driver, 50).until(
            EC.presence_of_element_located((By.ID, "user_email"))
        )
        self._insert_element(
            element=email_element,
            element_data=self._email_address,
        )
        sleep(randint(4, 13))  # thinking about a password...

        # insert password
        password_element = WebDriverWait(self.driver, 50).until(
            EC.presence_of_element_located((By.ID, "user_password"))
        )
        self._insert_element(
            element=password_element,
            element_data=self._password,
        )
        sleep(randint(2, 5))  # agree or not?

        # agreements
        agree_element = WebDriverWait(self.driver, 30).until(
            EC.presence_of_element_located((By.ID, "tos_agreement"))
        )
        agree_element.click()
        sleep(randint(2, 3))
    except Exception as e:
        logger.exception(e)

Ok, now we have inserted the bots credentials into the forms, we have made it to the point when we need to solve the hCaptcha. From my experience solving hCaptcha is always a bit tricky and harder than reCAPTCHA.

In order to pass the captcha we need to get the captcha token.

Creating a new function in order to solve the captcha:

def _solve_captcha(self):
    try:
        solver = TwoCaptcha(os.getenv("TWO_CAPTCHA_KEY"))
        captcha_token = solver.hcaptcha(
            sitekey=os.getenv("SITE_KEY"), url=self.driver.current_url
        )["code"]
        return captcha_token
    except Exception as e:
        logger.exception(e)

Now coming back to our register_user function, we can use the _solve_captcha() function, and inject the captcha_token, but where should we inject the captcha_token?

Let’s open Dev Tools and find out. Searching for “h-captcha” in the Dev Tool gives us the following:

<textarea id="g-recaptcha-response-03i18g8r8ezr" name="g-recaptcha-response" style="display: none;"></textarea>
<textarea id="h-captcha-response-03i18g8r8ezr" name="h-captcha-response" style="display: none;"></textarea>

I will try to do it in the “traditional” by injecting the captcha_token to h-captcha-response element and hopefully this will be accepted:

def _inject_captcha_token(self, captcha_token):
    try:
        self.driver.execute_script(
            """
            document.getElementsByName('h-captcha-response')[0].innerHTML = arguments[0]
            """,
            captcha_token,
        )
    except Exception as e:
        logger.exception(e)

After injecting the captcha_token in to the h-captcha-response, it supposed to look something like this:

<textarea id="h-captcha-response-09xrd587ap08" name="h-captcha-response" style="display: none;">P0_eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.....</textarea>

The signup button is not clickable even after injection:

button.png

<button id="sign-up-button" disabled="disabled" data-target="captcha.submit sign-up.submit" type="submit" data-view-component="true" class="dark:focus:tw-outline-none dark:focus:tw-text-white dark:hover:tw-bg-primary-400 dark:hover:tw-text-white dark:tw-text-white disabled:tw-opacity-50 focus:tw-outline-none focus:tw-text-white hover:tw-bg-primary-700 hover:tw-text-white tw-bg-primary-500 tw-border tw-border-transparent tw-font-medium tw-inline-flex tw-items-center tw-leading-4 tw-rounded-md tw-text-white tw-justify-center tw-flex tw-w-full tw-py-3.5 tw-text-sm">
Sign up
</button>

Let’s make it clickable:

signup_button_element = WebDriverWait(self.driver, 30).until(
    EC.presence_of_element_located((By.ID, "sign-up-button"))
)
# make the button clickable:
self.driver.execute_script(
    'arguments[0].removeAttribute("disabled");', signup_button_element
)

After removing the disabled attribute the button is clickable, we can just simply do:

signup_button_element.click()

We clicked the signup bottom but unfortunately we got “HCaptcha Verification failed.” error :(

failed.png

What can we do about it? Our captcha solution wasn’t accepted.

Let’s take a step back and see what happens to the html elements after solving the captcha manually and then we will try to translate it to code. After a bit of research I found an interesting iframe that changes after solving the captcha

<iframe src="https://newassets.hcaptcha.com/captcha/v1/5ddf2f3/static/hcaptcha.html#frame=checkbox&amp;id=0ni43p29wple&amp;host=www.findout.com&amp;sentry=true&amp;reportapi=https%3A%2F%2Faccounts.hcaptcha.com&amp;recaptchacompat=true&amp;custom=false&amp;tplinks=on&amp;sitekey=.....:)&amp;theme=light" title="widget containing checkbox for hCaptcha security challenge" tabindex="0" frameborder="0" scrolling="no" data-hcaptcha-widget-id="0ni43p29wple" data-hcaptcha-response="" style="width: 303px; height: 78px; overflow: hidden;"></iframe>

I tried to inject the captcha_token into “data-hcaptcha-response” but that didn’t work as well and all I got is verification failed again.

That’s more complicated than I thought. I tried again, I wanted to see why it failed. After searching and digging in the HTML I found a new hidden element that was added once the captcha was solved successfully.

<input type="hidden" name="response_token" value="P0_eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...">

Now I knew that all I have to do is to inject the captcha_token into response_token so I have created a hidden input element and injected the captcha_token.

def _inject_captcha_token(self, captcha_token):
    try:
        js_script = f"""
            let captchaElement = document.getElementById('captcha')
            let inputElement = document.createElement('input');
            inputElement.setAttribute('type', 'hidden');
            inputElement.setAttribute('name', 'response_token');
            inputElement.setAttribute('value', "{captcha_token}");
            captchaElement.appendChild(inputElement)
        """
        self.driver.execute_script(js_script)
    except Exception as e:
        logger.exception(e)

I run the script again and our bot signup was finally successful :)

success.png

Now all we have to do is to confirm our account (and of course that’s can be easily automated as well).

Conclusion

We saw how easy it is to bypass security mechanisms such as captchas even on bigger and more “secure” sites. You can perform automated tasks on the platform with the bots as well.

I hope you enjoyed reading this article!