Proteger o código do lado do cliente e certificar a autenticidade da coleta de dados

David Senecal

escrito por

David Sénécal

July 08, 2025

David Senecal

escrito por

David Sénécal

David Sénécal trabalha para a Akamai como Diretor de engenharia — Fraude e abuso e é autor de O reinado dos botnets. Ele é apaixonado por melhorar a segurança na Internet e é um especialista em fraude on-line e detecção de bots. Ele tem mais de 25 anos de experiência no trabalho com web performance, segurança e tecnologias de rede empresarial em várias funções, incluindo suporte, integração, consultoria, desenvolvimento, gerenciamento de produtos, arquitetura e pesquisa.

Proteger o código do lado do cliente e sua carga útil requer uma estratégia complexa que envolve várias camadas de defesa e tecnologias.
Proteger o código do lado do cliente e sua carga útil requer uma estratégia complexa que envolve várias camadas de defesa e tecnologias.

Índice

Sabe-se que a proteção eficiente de aplicações e sites da Web requer o uso de JavaScript para coleta de dados do lado do cliente a partir do navegador. Isso geralmente consiste em caraterísticas do dispositivo e do navegador, preferências do usuário (impressão digital) e dados que refletem a interação do usuário com seus dispositivos, como movimentos do mouse, toque e pressionamentos de tecla (telemetria).

Profissionais e fornecedores de segurança da Web processam dados por meio de vários métodos de detecção (de regras simples a modelos avançados de IA) para verificar a probabilidade de uma solicitação legítima ter origem em um dispositivo legítimo controlado por um ser humano.

A combinação dos diferentes pontos de dados também ajuda a diferenciar os usuários e avaliar suas atividades ao longo do tempo. Este é o princípio fundamental que os produtos de gerenciamento de bots e de detecção de fraudes usam para ajudar a detectar ataques como preenchimento de credenciais, apropriação indevida de contas, abuso de abertura de conta e captura de conteúdo, entre outros.

Integridade da coleta de dados

Garantir a autenticidade e a integridade dos dados é fundamental para avaliar com precisão a interação do usuário com o site e sinalizar ameaças. Como alguém pode afirmar a autenticidade e a integridade dos dados enquanto sabe que algo executado no lado do cliente pode ser adulterado e manipulado?

Ao executar o código JavaScript do lado do cliente, há dois motivos para garantir que o código esteja bem protegido. 

1. O código JavaScript faz parte da propriedade intelectual de uma organização. Ele deve ser protegido o máximo possível contra agentes de ameaça e concorrentes. 

2. A integridade dos dados é fundamental para compreender adequadamente o ambiente e seus fatores de risco. O JavaScript protegido garante que os dados sejam confiáveis porque foram realmente coletados executando o script e não foram manipulados ou transformados.

Como proteger o código do lado do cliente e garantir a autenticidade dos dados

Assim como em qualquer outro aspecto da segurança, não há uma solução única que resolva o problema. Nesta publicação do blog, apresentamos uma coleção de métodos que a Akamai usa para proteger o código JavaScript, impor sua execução e garantir a autenticidade dos dados coletados, incluindo:

  • Ofuscação de código
  • Verificação da integridade dos dados
  • Ofuscação da VM
  • Inserção de código enganoso e desnecessário
  • Rotação de código JavaScript
  • Rotação dinâmica de campo
  • Pipeline de criação de JavaScript e validação de dados

Se você decidir seguir práticas semelhantes para proteger seu próprio código, recomendamos usar uma combinação desses métodos com base nas necessidades de sua equipe, organização e pilha técnica.

Ofuscação de código

A ofuscação é um dos métodos mais comuns usados para proteger o código JavaScript. A ofuscação torna mais difícil seguir e entender o código.

Práticas de desenvolvimento sólidas recomendam que as funções e variáveis de nomeação sejam as mais descritivas possíveis e que o código seja estruturado logicamente para facilitar a depuração e a manutenção. Embora essa seja uma prática valiosa que economiza tempo e esforço, o código limpo é um alvo fácil para a engenharia reversa.

Quando a ofuscação é aplicada, essas práticas de desenvolvimento sonoro são quebradas, e variáveis e funções com nomes descritivos são substituídas por outros aleatórios. Elas podem ser reordenadas e codificadas, e alguma lógica pode ser dividida. Um navegador da Web ainda pode executar o código sem problemas, e o resultado será o mesmo. No entanto, qualquer pessoa que tentar fazer engenharia reversa do código terá um desafio maior.

Os desenvolvedores ainda usam código bem estruturado para manutenção e aprimoramentos. Quando uma nova versão estiver pronta, o código será executado por um mecanismo de ofuscação antes de ser liberado. Vários produtos comerciais e de código-fonte aberto, como Code Beautify, JScrambler e Digital.ai, estão disponíveis para ofuscar de forma rápida e fácil o código JavaScript.

A Figura 1 é um exemplo de uma função JavaScript simples comumente usada na impressão digital, projetada para extrair várias caraterísticas do dispositivo, mostradas antes da ofuscação.

  function getDeviceInfo() {
 return {
   userAgent: navigator.userAgent,
   hardwareConcurrency: navigator.hardwareConcurrency || "unknown",
   screenOrientation: screen.orientation.type,
 };
}

Fig. 1: Código original antes da ofuscação

Você pode ver como é simples entender o código em seu estado original. Até mesmo alguém com conhecimento limitado de codificação pode entender o propósito pretendido e como ele atinge seu objetivo.

A Figura 2 é a mesma função JavaScript depois de ser executada através da ferramenta online Code Beautify.

  (function(_0xbf521e,_0x43c80b){var _0x4ad763=_0x3e09,_0x18fc85=_0xbf521e();while(!![]){try{var_0x40d2a7=parseInt(_0x4ad763(0xfc))/(0x18d1+-0xe6d+-0xa63)+-parseInt(_0x4ad763(0xf6))/(0x2*-0x7e4+0x171a+-0x750)+-parseInt(_0x4ad763(0xfb))/(-0x2e7*-0xb+0x6b*0x1f+-0x2cdf)*(parseInt(_0x4ad763(0xef))/(0x40f*-0x4+-0x897+0x18d7))+-parseInt(_0x4ad763(0xf3))/(0x3*-0xb5f+0x462+0x1dc*0x10)*(parseInt(_0x4ad763(0xf0))/(-0xb87*-0x1+0x18e8+-0x3*0xc23))+-parseInt(_0x4ad763(0xfa))/(0x2258+0x8f7+-0x2b48)*(-parseInt(_0x4ad763(0xee))/(0x3e9+-0xe93+0xab2))+parseInt(_0x4ad763(0xf1))/(0x1*-0x81e+0x525*-0x5+0x4*0x878)+parseInt(_0x4ad763(0xed))/(-0x59*-0x1f+0x779+-0x6f*0x2a);if(_0x40d2a7===_0x43c80b)break;else _0x18fc85['push'](_0x18fc85['shift']());}catch(_0x4460fc){_0x18fc85['push'](_0x18fc85['shift']());}}}(_0x1950,-0x1f*-0x38cb+0x17f2fa+-0x10aebf));function getDeviceInfo(){var _0x7a196=_0x3e09,_0x52340e={'VEDsL':_0x7a196(0xf8)};return{'userAgent':navigator[_0x7a196(0xf4)],'hardwareConcurrency':navigator[_0x7a196(0xf2)+_0x7a196(0xfd)]||_0x52340e[_0x7a196(0xf5)],'screenOrientation':screen[_0x7a196(0xf9)+'n'][_0x7a196(0xf7)]};}function _0x3e09(_0x56cbb3,_0x1167d0){var _0xddc250=_0x1950();return _0x3e09=function(_0x363b57,_0x27d74c){_0x363b57=_0x363b57-(-0x6d9+0x1316*0x1+-0xb50);var _0x1b2eec=_0xddc250[_0x363b57];return _0x1b2eec;},_0x3e09(_0x56cbb3,_0x1167d0);}function _0x1950(){var _0x1d7105=['ncurrency','20162890GviEyp','2488DLGTpn','4rCTHCm','65154TKsGUe','7673175smCphy','hardwareCo','670lOXWEG','userAgent','VEDsL','1749116JlgXKK','type','unknown','orientatio','12971xihUJr','2027775PnQRTc','487370FufNiT'];_0x1950=function(){return _0x1d7105;};return _0x1950();}

Fig. 2: O código ofuscado (através de Code Beautify)

Mesmo que apenas por sua extensão, o código ofuscado é claramente mais difícil de entender. O código pode parecer complexo, mas os métodos para reverter essas técnicas de ofuscação mais simples existem e são bem compreendidos pelos agentes de ameaça. Mas, pelo menos, isso eleva o nível para deter os agentes de ameaças menos sofisticados e com menos conhecimento.

Metade da batalha em segurança está esgotando o agente de ameaças e/ou tornando a perspectiva de direcionar sua organização pouco atraente com base no esforço percebido ou real necessário para orquestrar um ataque bem-sucedido.

Verificação da integridade dos dados

Como vimos, a ofuscação de código é um bom ponto de partida, mas não é suficiente para impedir agentes de ameaças motivadas, pois existem métodos e ferramentas de desofuscação para reverter o código para seu formato original. Além dos métodos de ofuscação, a implementação de funções extras de verificação de integridade de código e dados pode proteger ainda mais a integridade das informações coletadas.

As verificações de integridade de código e dados são pequenas funções adicionadas em vários locais em todo o código para verificar se a saída produzida pelo script é realmente legítima. As verificações geralmente usam várias variáveis, incluindo a saída das funções JavaScript principais existentes, juntamente com uma semente exclusiva específica de uma sessão do usuário para produzir uma saída secundária.

A Figura 3 é um exemplo de uma função que usa três variáveis como entrada, usa as variáveis em uma fórmula matemática simples e uma função hash e retorna o resultado. As variáveis 'a' e 'b' poderiam corresponder à saída de duas funções principais, e a variável 'c' poderia ser uma semente exclusiva. Neste exemplo, todas as propriedades devem ser valores numéricos.

  function IntegrityCheck(a, b, c) {
   const mathResult = a + b * c;
   const stringResult = String(mathResult);
   let hash = 0;
   for (let i = 0; i < stringResult.length; i++) {
    hash = (hash * 31 + stringResult.charCodeAt(i)) >>> 0; 
 }
   return hash;
}

Fig. 3: Exemplo de código com várias variáveis para integridade de dados

Mais concretamente, as propriedades screen.colorDepth e navigator.hardwareConcurrency que retornam valores numéricos podem ser usadas como as variáveis 'a' e 'b' na função simples na Figura 3. Essa função não está limitada a propriedades que retornam um valor numérico, pois qualquer valor pode ser convertido em um número inteiro por meio de hash e, em seguida, ser passado para a função de verificação de integridade. Isso foi feito dessa maneira apenas por causa de nosso exemplo simples.

Para diversidade, alguma função de verificação de integridade pode gerar um hash na saída da função principal, conforme mostrado no exemplo na Figura 4.

  import { createHash } from 'crypto';

function hashTwoVariables(a, b) {
 const concatenatedString = String(a) + String(b); 
 const hash = createHash('sha256').update(concatenatedString).digest('hex');
 return hash;
}

Fig. 4: Exemplo de hash na saída

Pode haver dezenas dessas pequenas funções, cada uma realizando diferentes operações e consumindo diferentes resultados das principais funções espalhadas por todo o código para proteger os principais pontos de dados. Como uma verificação final, você também pode "assinar" toda a carga, incluindo todos os dados de impressão digital e comportamentais, bem como os resultados das funções de verificação de integridade individuais. Uma maneira de fazer isso é por meio do hash de toda a carga útil e da comparação da saída inicial. Se os hashes corresponderem nos lados do remetente e do destinatário, a carga útil será considerada segura e inalterada.

Ofuscação da VM

Essas funções simples de verificação de integridade não podem ser deixadas abertas ou ocultadas usando métodos simples de ofuscação. É aqui que entra em jogo a técnica de ofuscação de máquinas virtuais (VM) mais avançada, tornando mais difícil para o agente de ameaças entender o que está acontecendo nos bastidores e como produzir uma carga útil válida.

A ofuscação da VM transforma o código em bytecode de máquina virtual: algo que uma máquina pode interpretar, mas muito mais desafiador para os agentes de ameaça reverterem o processo de engenharia.

Vários fornecedores oferecem métodos de ofuscação da VM, mas a ofuscação da VM nem sempre suporta todos os tipos de lógica de função. Ao usar a ofuscação da VM, siga as diretrizes do fornecedor e faça um teste de regressão completo do seu código.

O teste de regressão é uma prática recomendada em geral, não apenas para a ofuscação da VM, e é recomendável implementá-lo como parte de sua rotina de segurança. No entanto, é particularmente útil em conjunto com a ofuscação da VM, considerando a complexa saída de código do método.

Inserção de código enganoso e desnecessário

Para tornar as coisas mais desafiadoras para o agente de ameaças que tenta reverter a engenharia do código, uma camada adicional envolve a adição de código que não serve a um propósito real da lógica principal. Isso foi projetado para tirar os agentes de ameaça do caminho certo, frustrá-los e obrigá-los a abandonar seus esforços.

Da mesma forma, você pode considerar variar a estrutura das funções de verificação de integridade para tornar a desofuscação e a engenharia reversa mais desafiadoras. Uma maneira de conseguir isso é desenvolvendo várias funções estruturalmente distintas, mas equivalentes, que produzem a mesma saída.

Uma função funcionalmente idêntica, mas estruturalmente diferente, resultará em uma codificação diferente da função depois de ter sido submetida à ofuscação da VM, tornando o código muito mais complexo para a engenharia reversa.

A Figura 5 é um exemplo de três funções que sempre retornam a mesma saída, mas que são ligeiramente diferentes.

  function IntegrityCheck_1(a, b) {
 return a + b * 1; 
}

function IntegrityCheck_2(a, b) {
 return a + 0 + b; 
}

function IntegrityCheck_3(a, b, c) {
 return a + b + c * 0; 
}

Fig. 5: Três exemplos de códigos diferentes que alcançam a mesma saída

Rotação de código JavaScript

Ter código enganoso, ofuscação avançada e verificações de integridade em vigor é bom, mas os agentes de ameaça podem ser muito persistentes e nenhum código estagnado é impossível de ser submetido à engenharia reversa, dados o tempo, o esforço e a habilidade. Isto é, a menos que limitemos a validade do script.

Imagine gerar 1.000s de iterações exclusivas do mesmo código funcionalmente equivalente, cada uma com diferentes funções de verificação de integridade em vigor para cada nova versão de código JavaScript. Cada iteração é usada e válida somente por 10 a 20 minutos, e controles estão em vigor para forçar o cliente a recarregar uma nova iteração regularmente, tornando as iterações mais antigas rapidamente obsoletas e inválidas.

O objetivo deste método é sobrecarregar o agente de ameaças com complexidade e superar sua eficiência, de modo que ele não tenha outra opção a não ser executar o JavaScript por meio de um navegador e continue sem saber o que o código faz.

Rotação dinâmica de campo

O código pode ser difícil de ler e decifrar, mas muitas vezes é possível inferir seu propósito examinando a saída e os dados coletados e enviados. Algumas das informações enviadas ao servidor podem ser óbvias, especialmente em relação a detalhes como caraterísticas do dispositivo e do navegador.

No entanto, seria mais difícil inferir a intenção para funções que retornam apenas um booleano, ou para uma função de verificação de integridade que retorna um valor numérico.

Uma maneira de tornar a estrutura da carga útil menos previsível e mais confusa para os agentes de ameaça é mudar os nomes dos campos usados para relatar cada ponto de dados coletado, bem como sua posição relativa na carga útil para cada iteração.

Como já discutimos, cada iteração JavaScript tem um conjunto exclusivo de verificações de integridade de código. Além disso, a carga útil usará diferentes nomes de campo e a posição de um determinado ponto de dados será alterada a cada iteração.

Os nomes de campo e suas posições são definidos no tempo de compilação do JavaScript com base em um algoritmo predefinido que o servidor que processa os dados também pode executar para recuperar as várias informações que são críticas para a detecção precisa de bots e fraudes no local correto.

A Figura 6 ilustra como cada campo e sua posição podem variar de uma iteração para outra. Os nomes dos campos devem ser não descritivos para torná-los menos óbvios.

  Payload Iteration #1

  mx01: [user-agent]
  mx02: [display-mode]
  mx03: [hardconcur]
  mx04: [pixelDepth]
  mx05: [language]
  mx06: [WebGL_Rend]
  mx07: [intg_chck_1]
 
  Payload Iteration #2

  yw01: [display-mode]
  yw02: [intg_chck_1]
  yw03: [user-agent]
  yw04: [pixelDepth]
  yw05: [hardconcur]
  yw06: [WebGL_Rend]
  yw07: [language]
  
  Payload Iteration #3

  za01: [language]
  za02: [WebGL_Rend]
  za03: [hardconcur]
  za04: [pixelDepth]
  za05: [intg_chck_1]
  za06: [user-agent]
  za07: [display-mode]

Fig. 6: Exemplos de iterações de nome de campo

Com apenas sete campos na saída (como no exemplo acima), é fácil identificar a alteração de uma iteração para outra, mas imagine fazer isso quando centenas de pontos de dados são coletados e retornados.

Pipeline de criação de JavaScript e validação de dados

Os vários métodos usados para proteger o código JavaScript e garantir a integridade dos dados coletados exigem o desenvolvimento de um pipeline de construção complexo e um processo de lançamento. Primeiro, os desenvolvedores atualizarão o arquivo JavaScript bruto e bem-formatado, testarão a funcionalidade e executarão testes de regressão.

Em seguida, os desenvolvedores usarão um algoritmo para gerar as milhares de iterações, que produzirão versões exclusivas cada uma com diferentes:

  • Funções de verificação de integridade de dados que variam os pontos de dados do JavaScript principal, das funções matemáticas/hash usadas e de sua posição relativa na lógica geral 
  • Conjuntos de códigos enganosos ou não utilizados
  • Nomes dos campos de saída da carga útil
  • Ordens de campo de saída da carga útil

Depois que esses componentes exclusivos são gerados, a iteração do arquivo JavaScript passa pelos seguintes processos:

  • Ofuscar a verificação de integridade dos dados e outras funções críticas via VM
  • Ofuscar o código geral
  • Carregar a iteração ao servidor da Web

Depois que todas as iterações tiverem sido geradas e carregadas, o novo conjunto de JavaScript deverá ser ativado na produção. Essa mudança é coordenada com o servidor que executa o bot e o motor de detecção de fraude que recebe os dados. Ele deve executar parte do algoritmo usado no sistema de compilação JavaScript para poder:

  • Validar se o cliente está enviando a carga útil da iteração JavaScript atual e não de uma iteração desatualizada
  • Analisar os diferentes campos da carga útil de acordo com a iteração do JavaScript com a qual ele foi gerado
  • Validar os valores de verificação de integridade do código executando funções equivalentes

O produto final, com a ofuscação final, deve ser completamente testado de ponta a ponta na pré-produção antes da liberação para garantir que todos os componentes estejam sincronizados e produzam o resultado esperado. Isso requer a criação de um fluxo de trabalho de compilação um pouco complexo para JavaScript.

Ainda assim, quando seu conteúdo precisa ser preservado de concorrentes curiosos e agentes de ameaça, e sua saída afeta a segurança dos usuários na Internet e dos sites que visitam, isso se torna um esforço válido.

Conclusão

O código JavaScript executado no lado do cliente, usado para coletar impressões digitais e telemetria, e a lógica personalizada projetada para detectar bots e fraudes, devem ser protegidos. Existem várias estratégias para proteger o código e os dados, mas a implementação de uma ou duas só fornecerá proteção marginal contra os agentes de ameaça mais sofisticados.

Proteger o código do lado do cliente e sua carga útil requer uma estratégia complexa que envolve várias camadas de defesa e tecnologias, incluindo ofuscação, código enganoso ou não utilizado, funções de verificação de integridade combinadas com ofuscação de VM, randomizando a estrutura da carga útil para torná-la menos previsível e atualizando regularmente o código.

A equação na Figura 7 resume a complexidade da combinação geral de estratégias a serem desenvolvidas para garantir uma proteção eficiente.

  [JS Code obfuscation[
  + Misleading code 
  + unused code
  + VM Obfuscation [code integrity check] 
  + unique field names
  + field relative position shift] 
  x  [Number of unique iterations] 
  + Limited version validity (10 minutes)
  + Force JS reload]

Fig. 7: Equação das estratégias de proteção do JavaScript

Em última análise, essa combinação força o cliente a executar o JavaScript, reduzindo sua oportunidade de adulterar os dados e derrotar o mecanismo de detecção. Para limitar o esforço de desenvolvimento, soluções comerciais são  altamente recomendadas para algumas das etapas mais complexas, como ofuscação da VM. No entanto, algumas estratégias, como verificações de integridade de código, fragmentos de código enganosos e múltiplas iterações, devem ser criadas e mantidas internamente para oferecer proteção caso um desofuscador criado por agentes de ameaça fique disponível.



David Senecal

escrito por

David Sénécal

July 08, 2025

David Senecal

escrito por

David Sénécal

David Sénécal trabalha para a Akamai como Diretor de engenharia — Fraude e abuso e é autor de O reinado dos botnets. Ele é apaixonado por melhorar a segurança na Internet e é um especialista em fraude on-line e detecção de bots. Ele tem mais de 25 anos de experiência no trabalho com web performance, segurança e tecnologias de rede empresarial em várias funções, incluindo suporte, integração, consultoria, desenvolvimento, gerenciamento de produtos, arquitetura e pesquisa.