Code Review de Segurança com exemplos reais
Guia prático de análise de código inseguro em PHP, Python, C/C++ e PL/SQL
Prevenção sendo a arte de encontrar potencial perigo no código
Se você trabalha ou quer trabalhar com segurança da informação, uma hora ou outra vai precisar olhar código e identificar problemas. Pode ser num pentest, numa auditoria, num bug bounty ou até desenvolvendo sua própria aplicação. A questão é: você sabe reconhecer código vulnerável quando vê um?
Esse documento nasceu de uma série de exercícios de code review que eu resolvi durante meus estudos. A ideia aqui não é só dar as respostas, mas explicar o raciocínio por trás de cada vulnerabilidade - porque entender o “porquê” é muito mais útil do que decorar padrões.
Lembro das aulas de segurança da informação na UFRJ com o professor Claudio Miceli (professor muito bom, diga-se de passagem), ele chegou a passar diversos trechos de códigos em sala para tentarmos na hora falar que vulnerabilidade potencialmente teríamos ali nos exemplos… e quase nunca os alunos acertavam de cara. Eu mesmo nunca tinha feito isso e tive dificuldades (até que ainda tenho). Aquele dia tive um alerta de precisar melhorar meu faro, meu repertório, minha lógica em si a ponto de identificar métodos de burlar mais rapidamente, mesmo que apenas lendo um trecho de código.
Uma observação importante: as análises que apresento aqui foram feitas de forma individual, como exercício de estudo. Não tenho gabarito oficial desses exercícios - as respostas são minha interpretação baseada no que estudei. Não sou especialista em code review de segurança e ainda estou me aprimorando nessa área. Pode haver imprecisões ou interpretações incompletas em alguns casos - se você identificar algo errado ou quiser complementar alguma análise, fico feliz em aprender junto. O objetivo aqui é compartilhar o processo de raciocínio, não entregar verdades absolutas. Podemos discutir sobre isso no chat (entrando via GitHub) que disponibilizo sempre ao final de cada postagem.
Dica: antes de ler minha análise de cada caso, tente identificar a vulnerabilidade por conta própria. Coloquei as opções de resposta logo após cada código pra você testar seu conhecimento.
O que é Code Review de Segurança?
Code review de segurança é basicamente ler código procurando problemas que possam ser explorados. É diferente de code review normal (que foca em qualidade, legibilidade, performance) porque aqui a gente pensa como atacante: “o que eu consigo fazer de errado com isso?”.
Existem duas abordagens principais. A primeira é a análise estática (SAST - Static Application Security Testing), onde você analisa o código sem executar, procurando padrões conhecidos de vulnerabilidade. Ferramentas como Semgrep, SonarQube e Bandit automatizam parte disso. A segunda é a análise manual, onde você lê o código entendendo a lógica, seguindo o fluxo de dados do input do usuário até onde ele é usado. Essa é mais trabalhosa mas pega coisas que ferramentas automatizadas perdem.
Na prática, você usa as duas. Ferramentas achando o óbvio, você achando o sutil.
A Regra de Ouro: Siga o Input do Usuário
A maioria das vulnerabilidades acontece quando dados controlados pelo usuário chegam em lugares perigosos sem tratamento adequado. Então a primeira coisa que faço quando analiso código é identificar onde entram dados externos e rastrear pra onde eles vão.
Entradas comuns incluem $_GET, $_POST, $_REQUEST e $_COOKIE em PHP, request.GET, request.POST e request.FILES em Django/Python, argv, getenv() e stdin em C/C++, além de headers HTTP, uploads de arquivo e qualquer coisa que venha de fora do sistema.
Destinos perigosos são queries SQL, comandos do sistema, includes/requires de arquivo, outputs HTML, headers HTTP e operações de arquivo.
Se dado do usuário chega num destino perigoso sem sanitização, você provavelmente tem uma vulnerabilidade.
Análise Prática: Dissecando Código Vulnerável
Vamos analisar alguns trechos de código reais. Pra cada um, vou explicar o que está acontecendo, qual a vulnerabilidade e como ela poderia ser explorada.
Caso 1: O Redirect Traiçoeiro (PHP)
1
2
3
4
5
6
7
<?php
$host = $_SERVER['HTTP_HOST'];
$uri = rtrim(dirname($_SERVER['PHP_SELF']), '/');
$extra = $_GET['page'];
header("Location: http://$host$uri/$extra");
exit;
?>
Qual vulnerabilidade está exposta neste código?
a) HTTP Response Splitting
b) Reflected XSS
c) Path Traversal
d) Todas as anteriores
Ver minha análise
Esse código pega um parâmetro `page` da URL e usa pra montar um redirect. Parece inofensivo, né? Só que tem problemas sérios aqui. O primeiro é **HTTP Response Splitting** (CWE-113). A função `header()` em PHP (em versões antigas, anteriores à 5.1.2) pode aceitar quebras de linha. Se o atacante passar algo como `page=x%0d%0aSet-Cookie:%20admin=true`, ele consegue injetar headers HTTP extras na resposta. O `%0d%0a` é um CRLF (Carriage Return + Line Feed) que separa headers. Isso permite desde setar cookies arbitrários até injetar conteúdo HTML completo após um duplo CRLF. Versões modernas do PHP (5.1.2+) têm proteção contra isso e lançam um warning, mas código legado ainda é vulnerável. Sobre o **Reflected XSS**: a ideia seria usar `javascript:alert(1)` no parâmetro `page` pra executar código. Tecnicamente isso não funciona em navegadores modernos porque eles não executam `javascript:` em headers Location - o redirect simplesmente falha ou vai pra URL literal. Porém, se combinado com HTTP Response Splitting (injetando um body HTML após duplo CRLF), você consegue XSS sim. Então depende do cenário e da versão do PHP. O terceiro problema é **Open Redirect** (CWE-601), que facilita ataques de phishing. Um atacante pode passar `page=//site-malicioso.com` ou `page=https://site-malicioso.com` e a vítima será redirecionada pra fora do domínio legítimo. O `//` no início faz o browser usar o mesmo protocolo do site atual. Isso é muito usado em campanhas de phishing porque a URL inicial parece confiável. E quanto ao **Path Traversal**? Essa é uma questão interessante. No contexto de redirects HTTP, o servidor não está acessando arquivos locais - ele só monta uma URL e manda pro navegador. Então tecnicamente não é Path Traversal no sentido clássico (CWE-22). Porém, se o atacante passar `page=../../../admin/` e a aplicação tiver um endpoint sensível nesse caminho, ele consegue redirecionar usuários pra lá. É mais uma manipulação de URL do que path traversal propriamente dito, mas o efeito prático pode ser similar. **Testando na prática com Burp Suite:** ```http GET /redirect.php?page=x%0d%0aSet-Cookie:%20pwned=true HTTP/1.1 Host: alvo.com ``` No Burp, você intercepta a requisição, modifica o parâmetro `page` e observa a resposta. Se aparecer o header `Set-Cookie: pwned=true` na resposta, confirmou o Response Splitting. Pra testar Open Redirect, basta passar uma URL externa e ver se o Location header aponta pra ela. Considerando que HTTP Response Splitting permite injetar HTML (e por consequência XSS), que Open Redirect funciona, e que a manipulação de path é possível (mesmo que não seja path traversal no sentido clássico de acesso a arquivos), a resposta que faz mais sentido é que **todas as vulnerabilidades acima** estão presentes em algum grau. O importante é entender que cada uma depende do contexto: versão do PHP, configuração do servidor, e comportamento do navegador. A correção ideal seria usar uma whitelist de páginas/caminhos permitidos. Se precisar aceitar URLs dinâmicas, valide que o destino está no mesmo domínio usando `parse_url()` e comparando o host. Nunca confie em input do usuário pra construir headers HTTP. **Minha resposta: d) Todas as anteriores**Caso 2: A Busca que Entrega Tudo (PHP + LDAP)
1
2
3
4
5
6
7
8
<?php
$dn = $_GET['host'];
$filter = "(|(sn=$person*)(givenname=$person*))";
$justthese = array("ou", "sn", "givenname", "mail");
$sr = ldap_search($ds, $dn, $dn, $justthese);
$info = ldap_get_entries($ds, $sr);
echo $info["count"]." entries returned";
?>
Qual vulnerabilidade este código contém?
a) LDAP Injection
b) CGI Reflected XSS
c) Connection String Injection
d) Reflected XSS
Ver minha análise
Aqui temos **LDAP Injection** (CWE-90). O parâmetro `host` vai direto pro `ldap_search()` como Distinguished Name (DN) sem nenhum tratamento. LDAP tem sua própria sintaxe de queries, similar a SQL, e um atacante pode manipular a busca injetando caracteres especiais. Olhando o código mais de perto, o `$dn` (que vem do usuário) é usado tanto como base DN quanto como filtro no `ldap_search()`. Isso é duplamente problemático. Um atacante pode passar valores como `host=*))(|(objectClass=*` pra modificar a lógica da query. Caracteres especiais em LDAP que devem ser escapados incluem: `*`, `(`, `)`, `\`, `NUL`. Por exemplo, `*` é um wildcard que retorna todos os registros. Um payload como `host=*)(uid=*))(|(uid=*` pode bypassar filtros de autenticação ou extrair dados que não deveriam ser acessíveis. **Explorando na prática:** Se esse código faz parte de um sistema de autenticação ou busca de usuários, você pode testar com: ```http GET /search.php?host=*)(objectClass=* HTTP/1.1 Host: alvo.com ``` O `*` como DN base combinado com um filtro modificado pode retornar todos os objetos do diretório LDAP. Se o sistema mostra quantos registros retornaram (como faz esse código com o `echo $info["count"]`), você consegue inferir informações mesmo sem ver os dados diretamente - isso é LDAP Injection cego. Por exemplo, testando `host=*)(userPassword=a*` vs `host=*)(userPassword=b*` e comparando o número de resultados, dá pra enumerar senhas caractere por caractere. A correção seria usar `ldap_escape()` (PHP 5.6+) no input antes de usar na query, ou implementar sanitização manual removendo/escapando os caracteres especiais do LDAP. **Minha resposta: a) LDAP Injection**Caso 3: Escrevendo Onde Não Deve (PHP)
1
2
3
4
5
<?php
$file = $_GET['file'];
$content = $_GET['content'];
file_put_contents("/some/path/$file", $content);
?>
Qual vulnerabilidade está exposta neste código?
a) Path Traversal
b) Stored XSS
c) Privacy Violation
d) Todas as anteriores
Ver minha análise
Esse é **Path Traversal** (CWE-22) clássico combinado com **Arbitrary File Write** (CWE-73). O atacante controla tanto o nome do arquivo quanto o conteúdo - isso é extremamente perigoso. Com `file=../../../var/www/html/shell.php` e `content=`, o atacante acabou de criar uma webshell no servidor. Game over. **Explorando na prática:** ```http GET /upload.php?file=../../../var/www/html/backdoor.php&content=%3C%3Fphp%20system%28%24_GET%5B%27c%27%5D%29%3B%3F%3E HTTP/1.1 Host: alvo.com ``` Depois de criar o arquivo, o atacante acessa `http://alvo.com/backdoor.php?c=whoami` e tem execução de comando. O `%3C%3Fphp...` é só o payload PHP URL-encoded. Mas não para por aí. Dependendo do contexto, os impactos vão além: se o servidor interpreta PHP, você tem execução de código diretamente (RCE). Dá pra causar Denial of Service sobrescrevendo arquivos críticos como `index.php` ou configs. Defacement é trivial se você modificar páginas visíveis ao público. E o pior: dá pra criar backdoors que sobrevivem a updates da aplicação. A opção "Stored XSS" das alternativas também é válida se você escrever HTML/JS num arquivo que será servido depois, mas o impacto mais severo é definitivamente o RCE via webshell. **Minha resposta: a) Path Traversal** (com impacto de RCE via Arbitrary File Write)Caso 4: O Sleep Inocente (C++)
1
2
3
4
5
int i;
char inLine[64];
cin >> inLine;
i = atoi(inLine);
sleep(i);
Quais são as vulnerabilidades de segurança neste código?
a) Denial of Service
b) Environment Injection
c) Integer Overflow
d) Dangerous Functions
Ver minha análise
À primeira vista parece inofensivo - lê um número e dorme por aquele tempo. Mas tem mais de um problema aqui. O mais óbvio é **Denial of Service (DoS)** (CWE-400). Se o usuário passar `999999999`, o programa vai ficar travado por uns 31 anos. Em aplicações web ou serviços que criam uma thread/processo por requisição, um atacante pode mandar várias requisições com valores altos e esgotar os recursos do servidor. Mesmo valores menores como `3600` (1 hora) já causariam problemas. Mas tem outro problema que passa despercebido: **Buffer Overflow** (CWE-120). O `char inLine[64]` aloca 64 bytes, mas o `cin >> inLine` não verifica o tamanho do input. Se alguém passar uma string com mais de 63 caracteres, estoura o buffer. Em C/C++, isso pode levar desde crash até execução de código arbitrário dependendo de como a memória está organizada e das proteções do sistema (ASLR, stack canaries, etc). Sobre as opções: a opção "d) Dangerous Functions" também tem mérito - `atoi()` é considerada insegura porque não faz validação de erro (retorna 0 tanto pra "0" quanto pra "abc"). O ideal seria usar `strtol()` com verificação de erro. A opção "c) Integer Overflow" é menos aplicável aqui porque o valor vai pra `sleep()`, não pra operações aritméticas. **Explorando na prática:** Pro DoS, se esse código roda como CGI ou serviço, você manda múltiplas requisições com valores altos: ```bash # Manda 100 requisições paralelas, cada uma travando um processo por 999999 segundos for i in {1..100}; do echo "999999999" | nc alvo.com 8080 & done ``` Pro Buffer Overflow, você precisa mandar mais de 64 caracteres. Se o binário não tem proteções modernas (compilado sem `-fstack-protector`, sem ASLR), dá pra sobrescrever o return address e redirecionar execução: ```bash # Payload básico pra testar crash (ajuste o tamanho conforme necessário) python -c "print('A'*100)" | ./programa_vulneravel ``` Se crashar com "Segmentation fault", você confirmou o overflow. A partir daí, com ferramentas como `gdb` e `pwntools`, dá pra desenvolver um exploit mais sofisticado. A correção envolve duas coisas: usar `cin.getline(inLine, 64)` ou `std::string` pra evitar o overflow, e validar o range do valor antes de usar no sleep. Algo como `if (i > 0 && i < 60) sleep(i);` resolve o DoS. **Minha resposta: a) Denial of Service** (com Buffer Overflow adicional e uso de função insegura)Caso 5: Manipulando Arquivos Arbitrários (Python/Django)
1
2
3
4
5
6
7
def readFile(request):
result = HttpResponse()
file = request.GET.get('currentFile')
f = open(file, 'w')
for index, line in enumerate(f):
result.write(str(index) + " " + line + " ")
return result
Qual vulnerabilidade este código contém?
a) Resource Injection
b) Path Traversal
c) Code Injection
d) Este código parece seguro
Ver minha análise
**Path Traversal** (CWE-22) clássico. O parâmetro `currentFile` vai direto pro `open()` sem validação. Um atacante pode passar `currentFile=../../../etc/qualquer_coisa` e acessar arquivos fora do diretório esperado. Mas olha o detalhe cruel: o código abre com modo `'w'` (write). Isso significa que o arquivo é **truncado** (zerado) ou criado se não existir. O código depois tenta iterar sobre ele como se fosse leitura, o que nem funciona direito - um arquivo aberto com `'w'` está vazio e não retorna nenhuma linha. Provavelmente deveria ser `'r'`. Mas do ponto de vista de segurança, o modo `'w'` é ainda pior que só leitura. Com path traversal + modo write, um atacante pode **destruir arquivos críticos** do sistema simplesmente acessando a URL. Imagina passar `currentFile=../../../var/www/html/index.php` - você acabou de zerar o arquivo principal da aplicação. Ou pior, dependendo das permissões do processo, dá pra sobrescrever configs, crontabs, arquivos de log (destruindo evidências), ou qualquer coisa que o usuário do servidor tenha acesso de escrita. Isso também pode ser classificado como **Resource Injection** (CWE-99) já que o atacante controla qual recurso (arquivo) será manipulado. A diferença é sutil: Path Traversal foca no escape do diretório, Resource Injection foca no controle do recurso em si. A correção seria usar `os.path.basename()` pra extrair só o nome do arquivo, validar contra uma whitelist de arquivos permitidos, ou usar um diretório base fixo e garantir que o caminho final não escape dele com `os.path.realpath()` e verificando que o resultado começa com o diretório base. **Minha resposta: b) Path Traversal** (com impacto de destruição de arquivos)Caso 6: Vazando o Ambiente (C++)
1
2
3
4
5
6
7
int main() {
cout << "Content-type: text/html" << endl;
char* pPath;
pPath = getenv("PATH");
printf("First line of text file is: %s\n", pPath);
return 0;
}
Qual vulnerabilidade este código contém?
a) Não há vulnerabilidades
b) CGI Reflected XSS
c) Connection String Injection
d) Reflected XSS
Ver minha análise
Esse código é um CGI que imprime a variável de ambiente PATH direto na resposta HTTP. O header `Content-type: text/html` faz o navegador interpretar a saída como HTML. O problema mais óbvio aqui é **Information Disclosure** (CWE-200) - vazar o PATH do servidor revela informações sobre a estrutura do sistema, quais diretórios existem, onde estão os binários, às vezes até versões de software. Isso ajuda atacantes na fase de reconhecimento. Mas olhando as opções da questão, o foco parece ser em **CGI Reflected XSS**. A lógica seria: se um atacante conseguir de alguma forma manipular a variável de ambiente PATH antes da execução do CGI (cenário raro mas possível em hospedagem compartilhada, containers mal configurados, ou via outras vulnerabilidades), ele poderia injetar `` no PATH que seria renderizado como HTML. É um vetor bem específico e incomum, mas tecnicamente possível. A diferença entre "CGI Reflected XSS" e "Reflected XSS" normal é sutil. No XSS refletido tradicional, o input vem direto do usuário via parâmetros HTTP. No caso de CGI com variáveis de ambiente, o vetor é diferente - depende de como o ambiente do CGI é configurado. Por isso a classificação como "CGI Reflected XSS" faz sentido pra diferenciar. Na prática, o information disclosure já é problemático por si só. A correção é simples: não exponha variáveis de ambiente em respostas HTTP. Não há razão legítima pra fazer isso em produção. **Minha resposta: b) CGI Reflected XSS**Caso 7: Conexão com Credenciais Expostas (C++)
1
2
3
4
int main(int argc, char *argv[]) {
rc = SQLConnect(Example.ConHandle, argv[0], SQL_NTS,
(SQLCHAR *) "", SQL_NTS, (SQLCHAR *) "", SQL_NTS);
}
Qual vulnerabilidade este código contém?
a) Não há vulnerabilidades
b) SQL Injection
c) Connection String Injection
d) Reflected XSS