Segurança de código do GitHub Copilot: XSS no React
19 de outubro de 2023
0 minutos de leituraNa era da evolução de inteligência Artificial (IA) e dos grandes modelos de linguagem (LLMs), ferramentas inovadoras como o GitHub Copilot estão transformando o cenário do desenvolvimento de software. Em um artigo anterior, falei sobre as implicações dessa transformação e como ela abrange tanto a conveniência oferecida por essas ferramentas automatizadas com inteligência quanto o novo conjunto de desafios que elas introduzem na manutenção de uma segurança robusta nas práticas de codificação. A Snyk também publicou um estudo de caso sobre os riscos de segurança relacionados à codificação com IA.
Neste artigo, vamos explorar os aspectos de segurança do GitHub Copilot quando usado em uma base de código React e onde ele completa automaticamente o código para desenvolvedores de front-end em arquivos JSX de componentes React. Nosso objetivo é examinar se o código proposto pelo GitHub Copilot adere aos princípios de codificação segura, ajudando os desenvolvedores a criar código que evite possíveis vulnerabilidades de scripting entre sites (XSS), particularmente no contexto do desenvolvimento em React.
Para quem não conhece as severas implicações do XSS: essencialmente, o XSS permite que invasores injetem scripts no lado do cliente em páginas web visualizadas por outros usuários.
Os desenvolvedores adotam o GitHub Copilot
No espaço da influência transformadora da IA em diversos setores, o desenvolvimento de software ocupa um lugar de destaque. Uma das inovações recentes nessa área é o GitHub Copilot, uma ferramenta para desenvolvedores integrada a IDEs, como o VS Code, baseada no Codex da OpenAI.
O GitHub Copilot foi criado para funcionar como seu parceiro de programação de IA. Ele é capaz de oferecer sugestões de código, escrever linhas ou blocos inteiros de código e é compatível com várias linguagens, uma funcionalidade que se estende a bibliotecas e estruturas populares de front-end, como o React.
O Copilot está se tornando uma ferramenta cada vez mais confiável no cinto de utilidades dos desenvolvedores. Além de podermos confiar na sintaxe do código que ele sugere, precisamos garantir que esse código seja seguro o suficiente para trabalhar com os padrões utilizados e não colocar os usuários em risco.
Usando exemplos de código JavaScript e uma análise detalhada das práticas de segurança específicas do JSX e do React, este artigo visa não apenas informar, mas também capacitar os desenvolvedores a avaliar e utilizar essas ferramentas de desenvolvimento aumentadas por IA de forma responsável, sem comprometer a segurança dos aplicativos.
A zona de perigo: uso da função dangerouslySetInnerHTML do React
O React oferece uma API que permite aos desenvolvedores definir HTML diretamente de um componente React: a função `dangerouslySetInnerHTML`.
Como sugerido pelo nome, o uso dessa função pode ser perigoso. Ela funciona ignorando os controles de segurança do React que codificam a saída para ajudar a proteger contra o scripting entre sites, permitindo a inserção manual de HTML em um componente. Essa funcionalidade é útil em algumas situações, como lidar com conteúdo rich text de fontes confiáveis. Por exemplo:
1function MyComponent() {
2 return <div dangerouslySetInnerHTML={{__html: '<h1>Hello World</h1>'}} />;
3}
A função oferece uma maneira rápida e direta de lidar com conteúdo HTML, mas também expõe os aplicativos ao risco de ataques de scripting entre sites (XSS). Vamos supor que os dados fornecidos pelo usuário façam parte do conteúdo HTML definido por `dangerouslySetInnerHTML`. Um invasor poderia injetar um script arbitrário que seria executado com consequências possivelmente de longo alcance.
1function MyComponent({userInput}) {
2 // This can expose the application to XSS risks if userInput contains a malicious script.
3 return <div dangerouslySetInnerHTML={{__html: userInput}} />;
4}
Portanto, é preciso tomar cuidado para usar `dangerouslySetInnerHTML` nos aplicativos.
O GitHub Copilot sugere código seguro?
O GitHub Copilot pode ajudar os desenvolvedores a implementar alguns desses controles de segurança para mitigar a ocorrência de XSS em `dangerouslySetInnerHTML`?
Vamos considerar este código de componente React que usa a diretiva `dangerouslySetInnerHTML`:
1 <Row className="justify-content-between">
2 <Col md="6">
3 <Row className="justify-content-between align-items-center">
4 <div
5 dangerouslySetInnerHTML={{
6 __html: `
7 <img src=${database.authorScreenshotURL}
8 alt=${
9 authorScreenshotDescription
10 } />
11 `,
12 }}
13 />
14 </Row>
A variável `authorScreenshotDescription` que entra nesse componente é controlada pelo usuário e serve para especificar o texto da descrição da imagem.
Um invasor poderia explorar essa vulnerabilidade de scripting entre sites para executar código JavaScript no navegador definindo o valor da variável `authorScreenshotDescription` como `\"
Tá, mas você tem experiência de sobra e sabe que:
Na verdade, não deveria usar `dangerouslySetInnerHTML`. Como o nome sugere, é perigoso fazer isso. Mas há um caso de uso específico que exige o uso desse recurso. Então...
Você sabe que, se precisar usar essa API do React, precisaria implementar uma codificação de saída segura para ignorar caracteres perigosos, como `<`, que permitem criar elementos HTML.
Qual é a melhor solução? Você começa a criar uma função rápida para ignorar essas tentativas no componente do React. Naturalmente, o GitHub Copilot tenta ajudar:
Você digita o nome e os argumentos da função e, pouco antes de começar a escrever o corpo, o GitHub Copilot sugere automaticamente o seguinte código. Parece ótimo. É só pressionar TAB, e pronto. Obviamente, não vamos esquecer de atualizar o uso da API `dangerouslySetInnerHTML` no componente para utilizar esta função que ignora caracteres perigosos para preservar a segurança:
Ao tentar o mesmo exploit de XSS que funcionou para o invasor, descobrimos que agora ele falha:
Aparentemente, a complementação de código sugerida pelo GitHub Copilot para a função `escapeCrossSiteScripting` funciona muito bem e realmente ignora os colchetes angulares que criavam um elemento HTML `<img />` e executavam código JavaScript.
Mas ainda não terminamos.
Os invasores são persistentes e praticamente não têm custo com a automação de conteúdo de ataque. Eles podem iterar milhares de permutações de strings (uma técnica conhecida como fuzzing) para encontrar uma que funcione. É aqui que a arte do hacking informático se manifesta: os desenvolvedores precisam proteger todos os pontos de falha, mas os invasores só precisam encontrar uma brecha, por menor que seja.
Um invasor pode tentar o seguinte conteúdo:
1s \"<img src=x onLoad=alert(1)
Acima, mudamos o manipulador de atributos `onError` para um manipulador `onLoad`. Agora, se carregarmos a página web novamente, veremos que o ataque foi bem-sucedido e um pop-up apareceu:
Como desenvolvedor, você imaginará como essa vulnerabilidade de segurança surgiu e como ela pode ser evitada. Desenvolvedores experientes de React podem dizer que colocar o valor de um atributo entre aspas é uma convenção de codificação bastante superior em termos de segurança do código. Vamos fazer isso e aplicar a alteração no seguinte valor do atributo `alt=`:
1 <div
2 dangerouslySetInnerHTML={{
3 __html: `
4 <img src=${database.authorScreenshotURL}
5 alt="${escapeCrossSiteScripting(
6 authorScreenshotDescription
7 )}" />
8 `,
9 }}
10 />
Fazendo isso e aplicando o conteúdo de ataque que funcionou antes (`s \"
Parece que está tudo resolvido.
Até que…
Um invasor descobre uma maneira criativa de fugir dessa situação usando um conteúdo mais curto que preserva o abuso do atributo especial `onLoad` e acrescenta um ponto e vírgula e uma `//`string para simbolizar que qualquer string subsequente deve ser tratada como comentário.
Este é o conteúdo usado:
1s \" onLoad=alert(1); //
E agora temos mais uma execução bem-sucedida de scripting entre sites:
Mas e se tentássemos uma abordagem diferente?
E se, em vez de colocar o atributo `alt=` entre aspas duplas dentro da seção `dangerouslySetInnerHTML`, tentássemos refatorar inteiramente a função de ignorar caracteres?
Vamos tentar fazer isso. O código JSX do componente React permanece da seguinte forma:
1 <div
2 dangerouslySetInnerHTML={{
3 __html: `
4 <img src=${database.authorScreenshotURL}
5 alt=${escapeHTML(
6 authorScreenshotDescription
7 )} />
8 `,
9 }}
10 />
Queremos refatorar a função `escapeCrossSiteScripting` existente para ser uma função para ignorar HTML adequada e mais segura e que codifique mais caracteres além de `<`, `>` e `&`.
Começamos a digitar o código, e, como esperado, o GitHub Copilot acorda e oferece a seguinte sugestão:
Ele sugeriu o código completo do corpo da função, e eu só pedi para ele continuar complementando as sugestões de código até terminar a sanitização. Parece ser uma lógica de codificação de saída melhor que considera outros caracteres perigosos, como aspas simples e aspas duplas.
Se enviarmos ao aplicativo o conteúdo original:
1s \"<img src=x onError=alert(1)
O código sugerido pelo GitHub Copilot teria realmente codificado todos os caracteres possivelmente perigosos, incluindo as aspas duplas que o conteúdo de ataque tentou usar para burlar a codificação:
Ainda assim, estaríamos errados mais uma vez. Um conteúdo de ataque simples como o mostrado a seguir acionaria o scripting entre sites, permitindo a execução de qualquer código JavaScript:
1s onLoad=alert(1)
Mas por que isso acontece?
Destaque dos problemas de segurança de XSS no React
Por mais abrangente que seja essa última tentativa de uma função que ignora HTML, ela não considera um princípio fundamental da lógica de segurança de ignorar caracteres de saída e sanitização geral: o contexto é fundamental.
O contexto do fluxo de dados para a API sensível (neste caso, o valor do atributo) é o de um formulário de atributo HTML e não de um elemento HTML. O fato de podermos simplesmente usar um caractere de espaço para comunicar o fim do atributo `alt` e o início do próximo atributo `onLoad` é a raiz do problema.
Proteção contra scripting entre sites em aplicativos do React
Por mais que ferramentas como o GitHub Copilot revolucionem o desenvolvimento de software, é fundamental lembrar que esses modelos de IA podem ocasionalmente sugerir código inseguro. Portanto, é essencial contar com proteções de segurança robustas para proteger os aplicativos. É exatamente aqui que entram as ferramentas de segurança para desenvolvedores, como a Snyk.
A Snyk, um nome altamente respeitado no mundo das ferramentas de segurança com foco no desenvolvedor, oferece uma prática extensão de IDE. Conforme você escreve código React, a Snyk pode detectar possíveis vulnerabilidades de segurança, como as apresentadas pelo uso inseguro de `dangerouslySetInnerHTML`, que nem sempre é detectado pelo Copilot. Essa proteção em tempo real pode ser fundamental para ajudar a escrever aplicativos React seguros e prevenir ataques de scripting entre sites.
Um dos principais recursos que distingue a Snyk é o DeepCode AI. O DeepCode usa descobertas baseadas em IA para identificar problemas de segurança, desempenho e lógica no código à medida que você o escreve. Mas ele não se limita à identificação de possíveis problemas. O DeepCode AI também oferece o AI Fix, que oferece correções geradas automaticamente para os problemas identificados, facilitando ainda mais a codificação segura e eficiente.
A Snyk é uma ferramenta gratuita projetada para auxiliar desenvolvedores a criar um código mais seguro a partir da conveniência do IDE. Enquanto você explora o mundo da assistência de codificação baseada em IA com ferramentas como o GitHub Copilot, a proteção complementar de ferramentas como a Snyk pode oferecer um benefício inestimável.
Além de usar a Snyk, as seguintes práticas de segurança de aplicativos web também devem ser seguidas. Um desses controles de segurança está relacionado a uma codificação de saída segura.
A mitigação desses riscos de segurança em aplicativos web de front-end envolve a sanitização detalhada da entrada do usuário que flui para APIs sensíveis, como `dangerouslySetInnerHTML`. As entradas do usuário devem sempre ser tratadas como não confiáveis, ou seja, todo conteúdo HTML vindo do usuário precisa passar por um sólido processo de sanitização de HTML. Para alcançar esse objetivo, várias bibliotecas de terceiros, como DOMPurify, podem ser utilizadas:
1import DOMPurify from "dompurify";
2
3function MyComponent({userInput}) {
4 const cleanInput = DOMPurify.sanitize(userInput);
5 return <div dangerouslySetInnerHTML={{__html: cleanInput}} />;
6}
Além disso, se você quer apenas renderizar conteúdo de texto, considere alternativas ao `dangerouslySetInnerHTML`, como usar o tratamento de conteúdo de texto do React usando chaves `{}`.
Conclusão
As sugestões de IA podem acelerar o processo de codificação, mas vigilância humana e ferramentas de segurança robustas continuam sendo essenciais para criar aplicativos seguros.
Lembre-se de que, embora `dangerouslySetInnerHTML` ofereça uma forma de tratar HTML no React, é importante entender suas possíveis implicações de segurança. O uso cuidadoso e a sanitização dos dados que fluem para essa API podem ajudar a manter os aplicativos React seguros contra vulnerabilidades de XSS.
Recomendo que você experimente a Snyk e veja como suas avaliações de vulnerabilidade e correções automáticas em tempo real podem melhorar a segurança de aplicativos React, bem como aumentar sua produtividade como profissional de engenharia que valoriza a criação de software seguro. Como diz o ditado, é melhor prevenir do que remediar, principalmente quando se trata de codificação segura.
Primeiros passos com Capture the Flag
Saiba como resolver desafios de Capture the Flag assistindo ao nosso workshop virtual de conceitos básicos sob demanda.