Todos os posts
6 min de leituraglizzykingdreko

Desvendando uma Medida Anti-Bot Peculiar

Enquanto trabalhava na automação de buscas de carros para a DiscoverCars, esbarrei em uma medida de segurança fascinante — e um tanto incomum — projetada…

Desvendando uma Medida Anti-Bot Peculiar

Enquanto trabalhava na automação de buscas de carros para a DiscoverCars, esbarrei em uma medida de segurança fascinante — e um tanto incomum — projetada para verificar usuários legítimos. Este post detalha meu processo de debug, como identifiquei o mecanismo-chave em jogo e por que esse método é ao mesmo tempo criativo e falho.

O Desafio Inicial

Para recuperar as listagens de carros, o sistema exigia um fluxo de sessão de inicialização antes de fazer uma requisição-chave:

curl 'https://www.discovercars.com/en/search/get-result-list/{session_id}?gToken={recaptcha_token}' \
  -H 'accept: application/json, text/javascript, */*; q=0.01' \
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36' \
  -H 'x-requested-with: XMLHttpRequest'

Após várias tentativas, encontrei erros HTTP 400. Curiosamente, os tokens do reCAPTCHA v3 nem sequer eram validados. Isso me fez suspeitar que a verificação real estava acontecendo em outro lugar.

Encontrando o Verdadeiro Guardião

Comecei a analisar os cookies um por um e finalmente identifiquei a peça crítica: gtm_ga_ggbit. Diferente dos outros cookies, este era ativamente validado. No entanto, buscas online não retornaram quase nada sobre ele — exceto um post de um ano atrás no Freelancer, de um usuário chinês lutando com o mesmo problema.

Sem documentação disponível, mudei para uma abordagem debug-first para entender como esse cookie era definido.

Debug com Tampermonkey Salvando o Dia

Como eu suspeitava que o JavaScript era responsável por gerar o gtm_ga_ggbit, escrevi um script no Tampermonkey para dar hook em document.cookie e interromper a execução quando esse cookie específico fosse definido.

// ==UserScript==
// @name         Cookie gtm_ga_ggbit Debugger
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Break into debugger when gtm_ga_ggbit cookie is set by any method.
// @author       glizzykingdreko
// @match        *://*/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const TARGET_NAME = 'gtm_ga_ggbit';
    let lastCookieValue = document.cookie;  // store initial cookies

    // Helper: trigger debugger if target cookie present in document.cookie string
    function triggerDebuggerIfCookieFound(source) {
        // Check for the target cookie name in the cookie string
        const cookieStr = document.cookie;
        if (cookieStr && cookieStr.indexOf(TARGET_NAME + '=') !== -1) {
            console.log(`[Tampermonkey] Cookie "${TARGET_NAME}" detected via ${source}.`);
            alert(`Cookie ${TARGET_NAME} was set via ${source}!`);
            debugger;  // pause execution
        }
    }

    // Periodic check (polling) as fallback for any missed events (e.g., Set-Cookie headers)
    function startCookiePolling() {
        setInterval(() => {
            const current = document.cookie;
            if (current !== lastCookieValue) {  // cookies changed
                if (current.indexOf(TARGET_NAME + '=') !== -1 && lastCookieValue.indexOf(TARGET_NAME + '=') === -1) {
                    // target cookie appeared (wasn't in last value)
                    triggerDebuggerIfCookieFound('polling');
                }
                lastCookieValue = current;
            }
        }, 500);  // check every 500ms (adjust as needed)
    }

    // Hook document.cookie setter for JavaScript writes
    const docCookieDesc = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');
    if (docCookieDesc && docCookieDesc.configurable) {
        const nativeSetter = docCookieDesc.set;
        const nativeGetter = docCookieDesc.get;
        Object.defineProperty(Document.prototype, 'cookie', {
            configurable: true,
            enumerable: true,
            get() {
                return nativeGetter.call(this);
            },
            set(value) {
                if (typeof value === 'string' && value.indexOf(TARGET_NAME + '=') === 0) {
                    console.log(`[Tampermonkey] Cookie "${TARGET_NAME}" being set via document.cookie:`, value);
                    alert(`Cookie ${TARGET_NAME} is being set via script!`);
                    debugger;
                }
                // Actually set the cookie using native setter
                nativeSetter.call(this, value);
            }
        });
    }

    // Hook Fetch API
    if (window.fetch) {
        const origFetch = window.fetch;
        window.fetch = function(...args) {
            return origFetch.apply(this, args).then(response => {
                // After response, check cookies
                triggerDebuggerIfCookieFound('fetch/XHR response');
                return response;
            });
        };
    }

    // Hook XMLHttpRequest
    if (window.XMLHttpRequest) {
        const origOpen = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function(...args) {
            // (Optional: store URL or method if needed for debugging)
            this._debugUrl = args[1];
            return origOpen.apply(this, args);
        };
        const origSend = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.send = function(...args) {
            this.addEventListener('loadend', () => {
                triggerDebuggerIfCookieFound('XHR response');
            });
            return origSend.apply(this, args);
        };
    }

    // Hook WebSocket
    if (window.WebSocket) {
        const NativeWebSocket = WebSocket;
        // Override WebSocket constructor
        window.WebSocket = function(...args) {
            const ws = new NativeWebSocket(...args);
            ws.addEventListener('open', () => {
                triggerDebuggerIfCookieFound('WebSocket handshake');
            });
            return ws;
        };
        // Copy prototype to maintain instanceof checks
        window.WebSocket.prototype = NativeWebSocket.prototype;
        window.WebSocket.CONNECTING = NativeWebSocket.CONNECTING;
        window.WebSocket.OPEN = NativeWebSocket.OPEN;
        window.WebSocket.CLOSING = NativeWebSocket.CLOSING;
        window.WebSocket.CLOSED = NativeWebSocket.CLOSED;
    }

    // Use CookieStore API events if available (for modern browsers)
    if (window.cookieStore && window.cookieStore.addEventListener) {
        try {
            window.cookieStore.addEventListener('change', event => {
                // Check if our target cookie was added/changed
                for (const change of event.changed) {
                    if (change.name === TARGET_NAME) {
                        console.log(`[Tampermonkey] Cookie "${TARGET_NAME}" change detected via CookieStore API.`);
                        alert(`Cookie ${TARGET_NAME} was set (CookieStore API)!`);
                        debugger;
                    }
                }
            });
        } catch(e) {
            console.warn('CookieStore API event registration failed:', e);
        }
    } else {
        // Fallback to polling if CookieStore events not supported
        startCookiePolling();
    }
})();

Com esse script, consegui identificar exatamente onde o gtm_ga_ggbit estava sendo definido.

Descobrindo a Ofuscação Baseada em SVG

A grande revelação veio ao observar uma função JavaScript estranha que gerava o gtm_ga_ggbit usando atributos de cor de elementos SVG embutidos no frontend.

Lógica em JavaScript

var svgConvertor = [
    [109, 97, 105, 110, 45, 119, 114, 97, 112, 112, 101, 114, 45, 98, 111, 100, 121],
    [102, 97, 118, 105, 99, 111, 110, 95, 49, 54, 120, 49, 54],
    [102, 97, 118, 105, 99, 111, 110, 95, 51, 50, 120, 51, 50],
    [97, 112, 112, 108, 101, 95, 116, 111, 117, 99, 104, 95, 105, 99, 111, 110, 95, 49, 56, 48, 120, 49, 56, 48]
];

var svgContent = '';
for (let i in svgConvertor) {
    let s = '';
    for (let j in svgConvertor[i]) {
        s += String.fromCharCode(svgConvertor[i][j]);
    }
    s = $('#' + s).attr('colour');
    svgContent += s.split('').reverse().join('');
}

if (typeof myCookie !== 'undefined') {
    myCookie.setCookie('gtm_ga_ggbit', svgContent);
}

Detalhando

  • O array svgConvertor decifra para os IDs de quatro elementos do frontend: main-wrapper-body, favicon_16x16, favicon_32x32, apple_touch_icon_180x180
  • Cada um desses elementos tem um atributo colour contendo uma string hex aparentemente aleatória.
  • O script **extrai esses valores, os inverte, os concatena e os atribui a **gtm_ga_ggbit.

Exemplo:

<div class="main-wrapper" id="main-wrapper-body" colour="#d61805d3"></div>
<link id="favicon_16x16" colour="#8585e694">
<link id="favicon_32x32" colour="#7b589a35">
<link id="apple_touch_icon_180x180" colour="#5dae22af">

Essa abordagem é peculiar porque ofusca dinamicamente o valor do cookie com base em elementos renderizados no frontend, provavelmente tornando mais difícil para ferramentas automatizadas preverem o valor.

Contornando em Python

Apesar da criatividade, esse método é fácil de fazer engenharia reversa. Podemos simplesmente fazer o parse do frontend, extrair os valores necessários e reconstruir o cookie.

soup = BeautifulSoup(frontend_response.text, 'html.parser')
svg_content = ''
for element in [
    "main-wrapper-body",
    "favicon_16x16",
    "favicon_32x32",
    "apple_touch_icon_180x180"
]:
    data = soup.find(id=element).get('colour')
    svg_content += ''.join(reversed(data))
svg_content = svg_content.replace('#', '')
session.cookies.set("gtm_ga_ggbit", svg_content)

Considerações Finais

Essa foi uma das técnicas anti-bot mais criativas que já encontrei. Embora seja um jeito engenhoso de ofuscar a autenticação, no fim das contas ela é ineficaz porque:

  1. Os valores estão no lado do cliente e podem ser extraídos sem esforço.
  2. A lógica é previsível uma vez analisada.
  3. Adiciona complexidade sem melhorar significativamente a segurança.

Ainda assim, essa abordagem poderia enganar devs menos experientes e fazê-los perder tempo debugando outros fatores, tornando-a um estudo de caso divertido de segurança por obscuridade.

Adoraria saber se alguém mais já encontrou técnicas parecidas!

Me Siga e Entre em Contato

Se você gostou deste detalhamento, fique à vontade para me seguir em:

Ou fale comigo diretamente:

anti-botweb-scrapingreverse-engineeringweb-automationjavascript

Pule a engenharia reversa.

O Takion retorna cookies, headers e tokens novos para cada grande muro antibot. Um POST, sem navegador, primeira chamada em menos de uma hora.