Як я писав власний Reverse CAPTCHA сервіс, або чому класичні капчі бісять усіх, крім ботів

11 хвилин читання

Привіт, я Андрій, fullstack-розробник.

Останні кілька років я займався різними проєктами — робив конструктори для телеграм-ботів, B2B-системи, CRM, навіть великий HR-додаток. І щоразу, коли я стикався з необхідністю захистити форму або API від спаму, я вставляв ту саму капчу. І щоразу мене це дратувало. Не як розробника — як людину.

Я не знаю, як ви, але мене вже ну дуже дістало шукати на картинках пожежні гідранти. Особливо коли їх там немає. Або коли картинка піксельна. Або коли я клікаю правильно, а мені кажуть «спробуйте ще раз» і дають нову сітку з автобусами.

Це бісить.

Читайте також: Система, здатна аналізувати власну архітектуру, знаходити ефективніші рішення й використовувати результат для створення ще досконалішої версії себе, потенційно може змінити сам темп розвитку штучного інтелекту. Цей механізм називають рекурсивним самовдосконаленням ШІ та пов’язують із можливим переходом від вузьких систем до AGI — загального штучного інтелекту. Як працює така петля та де проходять межі її зростання, розповідає Galera News.

А от що бісить ще більше — це коли класична капча, яка мала б зупиняти ботів, насправді не зупиняє сучасних ботів. Сучасному боту вже не обов’язково бути примітивним HTTP-скриптом. Він може працювати через браузер і використовувати vision-моделі для розпізнавання зображень Тобто виходить, що ми мучимо реальних людей, а від ботів цей захист майже не допомагає.

Я вирішив спробувати щось інше.

Спочатку це була просто ідея, а не продукт Чесно кажучи, я не планував робити стартап. Мені просто стало цікаво: а що, якщо перенести навантаження з мозку людини на процесор її пристрою? Що, якщо не питати «хто ти? доведи», а просто перевірити, чи здатен твій комп’ютер виконати криптографічне завдання? І зробити це максимально непомітно?

Тобто людина навіть не знає, що її перевіряють. Бо все відбувається у фоновому режимі.

Ця думка мене зачепила. Я сів і почав писати код.

Так з’явився KineSec.

Як це працює — спрощено Якщо пояснювати без зайвої технічної складності, то логіка така:

Власник сайту додає один скрипт — і все.

Коли користувач заходить на сторінку, віджет починає працювати «під капотом».

Він збирає мінімум даних про середовище і виконує невелике обчислювальне завдання.

Якщо пристрій справляється — формується токен.

Токен автоматично додається в усі форми на сторінці, і користувач навіть не помічає, що щось відбувалося.

Ви просто заходите на сайт, заповнюєте форму і відправляєте. Без світлофорів, без автобусів, без «виберіть всі квадрати».

Звучить ідеально, правда?

От тільки на практиці виявилося, що «просто» — це тільки на папері.

Перша проблема: динамічні форми З першого погляду все просто. Я написав такий код:

Підписуйтеся на наші соцмережі

javascript

const forms = document.querySelectorAll(’form’); forms.forEach(form => { const input = document.createElement(’input’); input.type = ’hidden’; input.name = ’kinesec_token’; input.value = token; form.appendChild(input); }); І воно працювало. На звичайних сайтах.

А потім я вирішив потестити на реальних проєктах, де використовується React. І там форми рендеряться динамічно. Через п’ять секунд після того, як скрипт завантажився, форми ще немає в DOM. А через десять — вона вже є, але мій скрипт про це не знає.

Тобто токен просто не потрапляв у форму. І користувач отримував помилку.

Я сидів і думав: «Ну класно, Андрію, ти зробив захист, який ламає користувацький досвід ще до того, як з’явився спам».

Рішенням став MutationObserver — штука, яка стежить за змінами в DOM. Як тільки на сторінці з’являється нова форма — скрипт одразу додає туди токен.

Це вирішило проблему. Але додало трошки сивини.

Друга проблема: користувач не хоче проходити перевірку двічі Якщо ви думаєте, що користувачі люблять капчу — ви помиляєтесь. Якщо ви думаєте, що вони люблять проходити її двічі за п’ять хвилин — ви дуже помиляєтесь.

Уявіть: людина зайшла на сайт, пройшла перевірку, перейшла на іншу сторінку — і знову бачить той самий віджет. Це бісить. Я би сам пішов з такого сайту.

Тому я додав кешування через localStorage.

Після успішної перевірки токен зберігається на 10 годин. Якщо користувач повертається на сайт або переходить на іншу сторінку — віджет бачить, що є дійсний токен, і навіть не показує себе.

Інтерфейс розблоковується миттєво. Ніяких затримок, ніяких дратівливих вікон.

Але тут важливий нюанс: localStorage — це не про безпеку. Це про зручність. Сервер все одно перевіряє кожен токен незалежно. Якщо хтось спробує підробити кеш — нічого не вийде, бо токен підписаний сервером.

Третя проблема: боти теж не дурні Якщо ви думаєте, що достатньо просто видавати токени і сподіватися, що боти не здогадаються їх підробити — ви помиляєтесь.

Боти теж вчаться. Сучасні парсери — це вже не просто скрипти, які роблять HTTP-запити. Це повноцінні автоматизовані браузери, які вміють виконувати JavaScript, рухати мишку, скролити сторінку — тобто імітувати людину майже ідеально.

Тому однієї «криптографічної задачі» недостатньо.

Я додав кілька рівнів:

JWT з підписом — токен підписується на сервері, і його не можна підробити без секретного ключа. Прив’язка до домену та IP — токен, згенерований для одного сайту, не працює на іншому. Rate limiting — якщо з однієї IP надто багато запитів, сервер починає їх відхиляти. Аналіз поведінки — рух миші, скрол, тапи. Але це, чесно кажучи, палиця з двома кінцями. Чому палиця з двома кінцями? Тому що аналіз поведінки — це еврістика. Вона не дає 100% гарантії. Іноді реальна людина поводиться «як бот» — швидко рухає мишкою або взагалі не рухає (наприклад, на мобільному пристрої). А іноді бот настільки добре імітує поведінку, що його майже не відрізнити.

Тому я не роблю ставку на один «магічний» показник. Я просто додаю кілька шарів захисту, щоб зробити парсинг дорожчим. Не непробивним — просто невигідним.

Найдивніша фіча: Anti-Human Це взагалі вийшло випадково.

Спочатку я думав тільки про те, як захистити сайт від ботів. Але потім я задумався: а бувають ситуації, коли боти бажані, а люди — ні?

І зрозумів, що бувають.

Наприклад, якщо ви робите API для ШІ-агентів або для автоматизованих систем. Ви хочете, щоб ваш сервіс використовували машини, а не люди, які створюють зайве навантаження або просто не є вашою цільовою аудиторією.

Тоді я додав режим Anti-Human. У ньому все працює навпаки: якщо система бачить занадто «людську» поведінку — рух мишки, скрол, кліки — вона блокує запит.

Звучить трохи абсурдно, правда? «Ми блокуємо людей, бо вони поводяться як люди».

Але технічно це просто інший набір правил.

Я не впевнений, що цей режим буде масово використовуватись. Але мені було цікаво його зробити. Як експеримент.

Про що я шкодую, або що б зробив інакше Якщо чесно, я багато чого не передбачив на старті.

Наприклад, я думав, що позиціонування віджета на сторінці — це дрібниця. А виявилося, що це одна з найбільших проблем. Якщо віджет плаває в кутку екрана — він перекриває кнопки на мобільних пристроях. Якщо він блокує екран — це дратує користувачів.

В результаті я зробив так: під час перевірки віджет створює напівпрозорий фон, блокує скрол і центрує себе посередині екрана. А після успішної перевірки — плавно зникає.

Це виглядає акуратно. Але я перероблював це тричі.

Ще я недооцінив складність роботи з чужими сайтами. Коли ти вбудовуєш свій код на чужий сайт — ти не контролюєш нічого. Ти не знаєш, які там стилі, які скрипти вже завантажені, яка версія браузера. Тому доводиться писати код, який працює в будь-яких умовах. І це складно.

Що я виніс із цього досвіду Перше — Vanilla JS це круто. Серйозно. Я звик працювати з React, де все декларативно, але коли пишеш на чистому JavaScript без фреймворків — починаєш краще розуміти, як насправді працює браузер. Це дає глибше розуміння. І віджет важить буквально кілька кілобайт.

Друге — UX важливіший за захист. Якщо ваша система захисту дратує користувачів — вони підуть. І неважливо, наскільки технічно досконалий ваш алгоритм.

Третє — безпека це завжди компроміс. Немає ідеального захисту. Є захист, який достатньо дорого обійти. І це нормально.

Четверте — не бійтесь експериментувати. Ідея Anti-Human може здатися безглуздою, але вона народилася з реальної потреби, яку я помітив. Можливо, вона не стане мейнстрімом, але вона розширила моє розуміння того, як взагалі можна будувати системи захисту.

Що далі Зараз KineSec працює на кількох продакшн-сайтах. Я збираю статистику, дивлюся, як поводяться користувачі, і думаю, що ще можна покращити.

У планах:

Додати WebSocket-аналітику в реальному часі. Розширити типи криптографічних завдань. Спробувати використовувати WebGL для додаткової перевірки. Якщо вам цікаво спробувати або потестувати мій захист — KineSec — Reverse CAPTCHA відкритий для реєстрацій: kinesec.recvik.com.ua

Але головне — я хочу почути думку інших розробників.

Тому, якщо ви теж стикалися з проблемою захисту від ботів або маєте досвід із власними CAPTCHA-рішеннями — пишіть у коментарях.

Як ви зараз захищаєте свої форми? Чи використовуєте старі добрі капчі з картинками? Чи вже перейшли на щось нове?

Обговоримо. Буду радий подискутувати.