Texto de: Arthur Brito
Introdução
Em um mundo onde as aplicações web vem ganhando força, a segurança desses serviços nunca foi tão importante. Cada linha de código, cada endpoint que expomos ao público e cada configuração de servidor podem acabar se tornando potenciais vetores de ataque que podem comprometer dados sensíveis dos usuários da nossa aplicação, minar a reputação da empresa para qual trabalhamos e gerar prejuízos financeiros irreparáveis.
Para desenvolvedores que buscam excelência, entender como as falhas de segurança mais recorrentes funcionam e como podemos adotar estratégias sólidas de defesa é essencial, não apenas uma boa prática.
Neste artigo, exploraremos as vulnerabilidades Cross-Site Scripting (XSS), SQL Injection, ataques via configurações inseguras de CORS e ataques de Cross-Site Request Forgery (CSRF), oferecendo exemplos práticos de código, cenários reais de exploração e contramedidas eficazes. Esses são os ataques mais recorrentes em aplicações web modernas, principalmente em casos de aplicações menores, onde, geralmente, a segurança não é tratada da forma mais imperativa possível.
Além disso, apresentaremos um panorama de medidas gerais de segurança web que devem estar presente em todo o ciclo de vida do desenvolvimento, desde a arquitetura até a monitoração em produção.

O panorama da segurança na web
Antes de falarmos sobre os detalhes de cada vulnerabilidade em si, é importante entender o contexto atual de ameaças. A cada ano, o relatório OWASP (Open Web Application Security Project) Top 10 elenca um conjunto de riscos críticos que afetam aplicações web, variando desde falhas de controle de acesso até exposição de dados sensíveis.
Essas ameaças costumam explorar aspectos como ausência de validações de input, erros de configuração de infraestrutura, dependências desatualizadas e práticas de desenvolvimento que não consideram o Least Privilege Principle (princípio que diz que os usuários de uma aplicação só deveriam ter o acesso mínimo necessário para performar suas tarefas no sistema).
Para os curiosos, vale a pena dar uma olhada na lista e tentar entender algumas dessas vulnerabilidades. Também temos o CVE (Common Vulnerabilities and Exposures), que elenca diversas vulnerabilidades publicamente conhecidas e as avalia por nível de criticidade. É uma base de dados bem conhecida, e ajuda bastante no dia-a-dia de desenvolvedores e profissionais da área da segurança.

Cross-Site Scripting (XSS)
Cross-Site Scripting (ou XSS) é um ataque que permite que um invasor injete scripts maliciosos em páginas vistas por outros usuários, aproveitando de falhas na validação de dados de input.
Existem três categorias principais: refletido (Reflected XSS), armazenado (Stored XSS) e baseado em DOM (DOM XSS).
- No XSS refletido, o payload é enviado via parâmetros de URL ou formulários e imediatamente refletido na resposta HTTP. Por exemplo, quando em uma barra de pesquisa digitamos um texto e ele aparece em um parágrafo, um atacante poderia usar desse artifício para construir uma URL contendo um script JavaScript (<script>codigoMalicioso()</script>, por exemplo), e o navegador da vítima executará o código injetado assim que a página for carregada.
- O XSS armazenado vai além, pois o script fica persistido no banco de dados ou em algum repositório de conteúdo da página (como um comentário em um blog, por exemplo), afetando todos os visitantes daquele recurso, o que o torna ainda mais perigoso.
- Já o XSS baseado em DOM acontece inteiramente no lado do cliente, quando funções manipulam de forma insegura o DOM, sem envolver o servidor após a página carregar pela primeira vez.
Para ilustrar um caso de Reflected XSS, vamos considerar o código abaixo:
<form action="/search" method="get">
<input name="q">
<button>Pesquisar</button>
</form>
<p>Você buscou por: <span id="output"></span></p>
<script>
const params = new URLSearchParams(window.location.search);
document.getElementById('output').innerHTML = params.get('q');
</script>
Se um invasor encaminhar a URL https://sitevulneravel.com/search?q=<script>alert('XSS')</script>
, a vítima verá o alerta ao carregar a página. E lembrando, poderíamos executar qualquer script malicioso aqui, o alerta é só um exemplo.
Para nos prevenirmos do Reflected XSS, devemos codificar os dados do input para que eles não sejam executados na página como um script, usando bibliotecas consolidadas de encoding, como OWASP Java Encoder.
Inclusive, foi encontrada uma falha no router.push do Next.js, que permitia a aplicação do ataque XSS a partir da injeção de javascript:{"codigo_javascript"} na aplicação. Segue aqui o link do artigo que trata sobre o caso.

SQL Injection
O ataque SQL Injection explora o fato de alguns sites fazerem queries SQL de forma dinâmica, a partir de dados fornecidos pelo usuário. Ou seja, em alguns casos, sites fazem consultas SQL pegando o input do usuário e concatenando/integrando a uma query base.
Isso permite que um atacante modifique a lógica do banco de dados, obtenha acesso não autorizado, modifique ou até exclua dados.
Em muitas linguagens, a prática insegura de concatenar strings para montar uma query SQL abre espaço para um hacker inserir caracteres especiais e achar um jeito de modificar a query ao seu favor. Por exemplo, um pseudocódigo em Python poderia ser assim:
username = request.POST['username']
password = request.POST['password']
query = "SELECT * FROM users WHERE username = '%s' AND password = '%s'" % (username, password)
cursor.execute(query)
Com o valor de username igual a “admin’—” e qualquer valor em password, essa query retorna os dados do admin, pois os dois traços após admin comentam o restante da query, ignorando a parte da cláusula WHERE que procura por uma senha específica. O atacante, então, se loga como admin sem conhecer a senha de verdade.
A prevenção contra SQL Injection passa pelo uso de comandos parametrizados. Em Python, por exemplo, poderíamos usar o módulo psycopg2, e a abordagem segura seria algo como:
cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
Nesse exemplo, cada parâmetro será tratado como dado, e não como parte da instrução SQL propriamente dita. Quando usamos ORMs, como Prisma ou Hibernate, também ocorre essa abstração da montagem de queries, mas ainda assim é importante revisarmos as queries dinâmicas e evitar execuções de SQL puro.

CORS (Cross-Origin Resource Sharing)
CORS (Cross-Origin Resource Sharing) é um mecanismo que define como os navegadores devem tratar requisições HTTP entre origens distintas. Um CORS mal configurado (por exemplo, permitindo “Access-Control-Allow-Origin: *” através do wildcard *) abre a porta para que qualquer site faça requisições à sua API usando as credenciais do usuário (cookies, tokens de sessão).
Imaginemos uma API que retorna dados de perfil de usuários. Se um invasor criar uma página em um domínio que ele controla e que faz uma requisição AJAX a suaapi.com/perfil, aproveitando o cookie de sessão armazenado, os dados sensíveis do usuário podem ser expostos.
Para evitar esse problema, devemos configurar o CORS para listar explicitamente as origens confiáveis. Em um server Express.js, por exemplo, a biblioteca cors permite configurarmos isso da seguinte forma:
const cors = require('cors');
app.use(cors({
origin: ['https://app.seudominio.com', 'https://painel.seudominio.com'],
credentials: true
}));
Ataques de CSRF (Cross-Site Request Forgery)
No CSRF, o atacante induz o navegador autenticado da vítima a enviar requisições a um serviço no qual ela já possui sessão autenticada (sem consentimento). Explorações simples podem usar tags HTML (como <img> ou <form>) ocultas para acionar endpoints que alteram dados (como uma mudança de senha, por exemplo) aproveitando que o navegador inclui cookies automaticamente.
Por exemplo, um snippet HTML inserido em um e-mail ou página maliciosa pode disparar:
<form action="https://suaapi.com/transferir" method="post" style="display:none">
<input type="hidden" name="valor" value="1000">
<input type="hidden" name="destino" value="conta12345">
</form>
<script>document.forms[0].submit();</script>
Caso o usuário dessa aplicação visite essa página após ter se logado, a transferência vai acontecer sem que ele perceba.
A técnica para se proteger contra CSRF é, basicamente, pedir para o servidor gerar tokens e inserir em campos escondidos no formulário (ou nos headers das requisições AJAX).
O servidor, então, vai validar a integridade desse token antes de processar qualquer ação que altere estado. Esse processo é feito de forma automática em alguns frameworks, como Django e ASP.NET.
Ciclo de vida seguro de desenvolvimento
Garantir a segurança de um site não é só ajustar algumas medidas após o final do processo de desenvolvimento. Precisamos integrar práticas de defesa em todas as fases do desenvolvimento, seja no seu projeto pessoal ou até em um ciclo de vida de um software feito por uma grande empresa.
Desde o levantamento de requisitos, estabelecer objetivos de segurança acompanhados de análises de risco pode parecer básico, mas é extremamente importante para incorporar boas práticas de desenvolvimento desde o começo. Durante a fase da escolha da arquitetura, é sempre importar adotar princípios de arquitetura segura, como separar responsabilidades, controles de defesa no máximo de camadas possíveis da aplicação e privilégio mínimo.
No desenvolvimento, é sempre uma boa fazer revisões de código focadas em segurança, como usando SonarQube para identificar code smells (más práticas de código).
Conclusão
A segurança na web é algo bastante complexo de se lidar que está em constante evolução, o que exige dos desenvolvedores não apenas conhecimento técnico sobre vulnerabilidades como XSS, SQLi, CORS mal configurado e CSRF, mas também uma postura de gestão de riscos e boas práticas de desenvolvimento.
É importante lembrar que uma única vulnerabilidade no seu site pode levar a consequências catastróficas para você ou para sua empresa, manchando sua reputação e acarretando prejuízos financeiros/jurídicos graves.