Решаем hcaptcha

Alexandr Sokolov
4 min readJun 4, 2021

--

Photo by Pete Willis on Unsplash

У hcaptcha вновь изменения. На этот раз изменился способ обработки ответа.

Введение

Немного хронологии. Когда hcaptcha появилась, то требовала следующих действий:

  • заполнить g-recaptcha-response-{salt}
  • h-captcha-response-{salt}, salt выдергивался у iframe из аттрибута data-hcaptcha-widget-id
  • функция grecaptcha.getResponse должна была возвращать токен

Через 2 месяца пришлось заменить document.getElementById("hcaptcha_submit").click(); на ..."challenge-form").submit();

Еще через 3 месяца наконец убрали рудименты g-recaptcha

Т.е. что получилось, ключ сайта отправлялся на сервис распознавания, токен, полученный в результате, вставлялся в textarea и форма сабмитилась. А теперь Cloudflare поменяла способ взаимодействия с сервисом и таким способом капчу больше не пройти. Все что произойдет это перезагрузка страницы.

Ищем решение

После долгого и мучительного изучения исходников скриптов, я написал в тех поддержку сервиса распознавания. На что мне дали ссылку на их блог, в котором они упомянули о своем дополнении и описали решение.

Так как мне не подходит “используйте наше дополнение”, так оно еще и для хрома. И я решил скачать файл дополнения из магазина аддонов и надеялся, что это будет архив.

Однако магазин хрома не дает возможности получить прямую ссылку на файл. Во всяком случае из окна FF.

Зато в магазине есть дополнение для скачивания других дополнений Chrome extension source viewer. Код которого есть на гитхабе. И там мы узнаем как именно генерируется ссылка на загрузку дополнения

https://clients2.google.com/service/update2/crx?response=redirect&prodversion=[PRODVERSION]&acceptformat=crx2,crx3&x=id%3D[EXTENSIONID]%26uc

  • [PRODVERSION] — это версия браузера, я взял последнюю
  • [EXTENSIONID] — это ID расширения

Подробнее тут https://github.com/Rob--W/crxviewer/blob/master/src/cws_pattern.js#L134

Дополнения имеет расширение crx и это не переименованный zip, а application/x-chrome-extension, хотя тоже архив и открывается менеджером архивов GNOME.

Внутри, по пути /content/captcha/hcaptcha/interceptor.js, как раз таки находится скрипт выполняющий рекомендации описанные в блоге. А именно, дефайнит объект hcaptcha до того как это сделает скрипт hcaptcha-challenge.js.

Я взял эту монструозную конструкцию и создал новый скрипт в Tampermonkey (это такое дополнение для FF)

// ==UserScript==
// @name New Userscript
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match http://site.com/
// @icon https://www.google.com/s2/favicons?domain=site.com
// @grant none
// ==/UserScript==

(function() {
'use strict';

window.location = "javascript: (" + function () {

let hCaptchaInstance;

Object.defineProperty(window, "hcaptcha", {
get: function () {
return hCaptchaInstance;
},
set: function (e) {
hCaptchaInstance = e;

let originalRenderFunc = e.render;

hCaptchaInstance.render = function (container, opts) {
createHCaptchaWidget(container, opts);
return originalRenderFunc(container, opts);
};
},
});

let createHCaptchaWidget = function (container, opts) {
if (opts.callback !== undefined && typeof opts.callback === "function") {
let key = "hcaptchaCallback" + Date.now();
window[key] = opts.callback;
opts.callback = key;
}

let widgetInfo = {
captchaType: "hcaptcha",
widgetId: 0,
containerId: container,
sitekey: opts.sitekey,
callback: opts.callback,
};

console.log(widgetInfo);
}

} + ")()";
})();

Теперь, после перезагрузки страницы я наконец получаю callback функцию, которую так давно искал.

результат работы скрипта из interceptor.js

Если просто ввести имя колбека в консоль, то будет видно определение. Функция принимает аргумент i и нужно выяснить требуется ли его указывать. А то в этом JS хрен поймешь какие аргументы нужно какие не нужно, как на нем вообще кто-то пишет!?

В той же директории, в файле processor.js находим метод onSolved, в котором написано куда нужно засунуть свой ответ.

onSolved: function(widget, answer) {
let container = $("#" + widget.containerId);

container.find("textarea").val(answer);
container.find("iframe").attr("data-hcaptcha-response", answer);
},

Ну тут все понятно, потому что в контейнере только один iframe и только один textarea

Идем дальше и ищем кто использует метод onSolved. Спасибо огромное разработчикам JetBrains за великолепную функцию Find usages.

Я нашел единственное место

function doActionsOnSuccess(msg) {
...

processor.onSolved(widget, msg.response.code);

Config.getAll().then(config => {
let callback = processor.getCallback(widget);

if (callback) {
location.href = `javascript: window["${callback}"]("${msg.response.code}")`;
}

Опасненько, msg.response.code это не HTTP Status Code, а answer, потому что onSolved принимает answer, что является токеном решения капчи.

Все эти изыскания говорят о том, что для решения задачи нужно:

  • заполнить токен в textarea[@id=’h-captcha-response-{salt}’]
  • заполнить токен в аттрибут data-hcaptcha-response в iframe
  • вызвать колбек и передать ему токен

Для проверки я написал скрипт который решает капчу и заполняет токен.

hcaptcha.js

Результат? Все работает.

Значит теперь решать капчу невозможно без сторонних компонент, таких как дополнение, которое будет выполнять инъекции. О создании такого дополнения.

--

--