Escrever código seguro envolve a adoção de um conjunto de boas práticas de desenvolvimento de software, e uma mudança de postura e de cultura dentro dos times de desenvolvimento. Isso minimiza o risco de vulnerabilidades de segurança, e ajuda a proteger a aplicação contra atacantes mal-intencionados.
Ao incorporar essas práticas no processo de criação de um software, os times de desenvolvimento conseguem criar aplicações mais seguras que consigam resistir aos ataques cada vez mais sofisticados do ecossistema moderno de tecnologia.
Existem vários princípios-chave que desenvolvedores de software podem seguir para escrever um código mais seguro, ambos técnicos e culturais. Ao incorporar essas práticas em seu processo de desenvolvimento, times de desenvolvimento podem reduzir a probabilidade de vulnerabilidades de segurança e proteger suas aplicações contra ataques cibernéticos. Algumas das práticas recomendadas mais importantes incluem validação e sanitização de input, tratamento de erros, logging, output encoding, controle de acesso, criptografia e hashing.
Neste artigo, exploraremos cada uma dessas práticas com mais detalhes e forneceremos exemplos de como implementá-las em seu código. Ao seguir essas diretrizes, você pode garantir que seu código seja seguro e que suas aplicações estejam protegidas contra possíveis ameaças.
Entendendo o cenário de ameaças
Para escrever código de forma segura, é essencial entender o cenário de ameaças e os tipos de ataques aos quais a aplicação pode estar suscetível. Ameaças à segurança do software podem surgir de várias formas, uma bastante comum sendo a de agentes mal-intencionados que buscam roubar dados, interromper operações comerciais, explorar vulnerabilidades para obter ganhos financeiros, e diversos outros motivos. Uma atividade extremamente relevante, e que pode auxiliar no entendimento desse cenário, é a Modelagem de Ameaças. Não iremos falar de modelagem de ameaças diretamente neste artigo, mas já existem diversos conteúdos sobre o tema no blog da Conviso.
Problemas comuns, como SQL Injection, Information Disclosure e Cross-Site Scripting (XSS) podem ser evitados com a aplicação de boas práticas de codificação segura.
Falando de boas práticas para um código seguro
Validação e Sanitização de Input
A validação e sanitização de inputs são componentes críticos de práticas de codificação segura. A validação de input refere-se ao processo de verificação se o input do usuário é válido e atende ao formato e restrições esperadas. Em contrapartida, a sanitização envolve a limpeza e filtragem do input do usuário para remover dados potencialmente nocivos ou maliciosos. Ambas as técnicas são importantes para prevenir ataques de Injection, que envolvem a exploração de campos de entrada para injetar conteúdo malicioso em uma aplicação.
Para ilustrar como a validação e sanitização de inputs podem ser aplicadas na prática, vamos considerar um exemplo:
O trecho de código acima apresenta uma vulnerabilidade crítica de Command Injection, que pode permitir que invasores executem comandos maliciosos no sistema operacional do host. A vulnerabilidade ocorre devido ao uso direto do input do usuário na construção de um comando do sistema, sem qualquer validação ou sanitização adequada. Neste exemplo, um atacante consegue concatenar um comando, como ‘; rm -rf /;, e deletar todos os arquivos no servidor.
Uma abordagem comum para a sanitização de inputs é remover quaisquer caracteres que possam ser usados para injetar comandos adicionais ou modificar o comportamento do comando original. No exemplo a seguir, é utilizada uma expressão regular para identificar alguns metacaracteres do shell ( ; , &&, | ) dentro do input do usuário e, em seguida, substituí-los com uma string vazia.
Essa técnica ajuda a garantir que o comando executado seja o esperado e reduz a probabilidade de um ataque de Command Injection ser bem-sucedido. No entanto, é importante notar que a sanitização de inputs é apenas uma parte da estratégia de segurança e outras medidas, como a utilização de métodos e funções seguras, também devem ser implementadas para proteger a aplicação contra ameaças potenciais.
Além disso, é fundamental aplicar a validação e sanitização de inputs não apenas a campos de input do usuário, mas também a quaisquer dados de entrada recebidos de fontes externas, como APIs ou bancos de dados.

Tratamento de Erros
Ter uma estratégia eficaz de tratamento de erros é uma boa prática que traz diversos benefícios para a segurança de uma aplicação. Além de ajudar a evitar falhas e vulnerabilidades de segurança, um tratamento adequado de erros garante uma experiência tranquila e contínua aos usuários, mesmo diante de situações inesperadas.
Por outro lado, um tratamento inadequado de erros pode trazer uma série de problemas de segurança, pois pode expor informações internas sensíveis sobre a aplicação. Um dos riscos mais comuns relacionados ao tratamento inadequado de erros é o Information Disclosure, que pode revelar informações confidenciais para usuários não autorizados.
A seguir, temos um exemplo de uma mensagem de erro que não recebeu o tratamento adequado e foi exibida ao usuário com diversas informações internas do sistema. A mensagem de erro contém detalhes técnicos sobre o banco de dados e a linguagem utilizada pela aplicação.
Para evitar esse tipo de problema, é crucial garantir que a aplicação trate todos os tipos de erros possíveis. Isso pode ser feito por meio da criação de um tipo customizado e genérico de erros, que irá substituir as mensagens de erro padrão.
Ao criar um tipo de erro customizado, é importante considerar os possíveis tipos de erros que a aplicação pode encontrar e criar uma hierarquia que reflita esses tipos. Por exemplo, uma aplicação de e-commerce pode ter uma classe de erro genérica chamada AppException, que é estendida por classes mais específicas, como PaymentErrorException e AuthenticationErrorException, e cada tipo de erro diferente pode trazer informações diferentes, ajudando a proteger os dados sensíveis envolvidos nas operações da aplicação.
Logging
Logging é um aspecto importante da escrita de código seguro, pois ajuda os desenvolvedores a monitorar e detectar incidentes de segurança e vulnerabilidades em suas aplicações. Práticas adequadas de logging envolvem a captura de informações relevantes sobre as interações do usuário, eventos do sistema e erros que ocorrem na aplicação. Ao registrar essas informações, os desenvolvedores podem obter informações sobre como sua aplicação está sendo usada e detectar comportamentos incomuns que possam indicar um ataque. Além disso, os logs podem ser usados para rastrear a causa raiz dos erros e ajudar os desenvolvedores a diagnosticar e corrigir bugs na aplicação.
Security Logging and Monitoring Failures é uma categoria que está presente no OWASP Top 10, ou seja, falhas relacionadas a logging e monitoramento são altamente comuns. Além da presença no OWASP Top 10, essa prática também está listada em outro projeto de grande relevância, o OWASP ASVS. No ASVS, são levantados diversos requisitos de segurança que ajudam a guiar desenvolvedores durante a implementação de logs no sistema. A presença da prática de Logging nesses projetos ajuda a demonstrar a relevância que a prática tem para a segurança de aplicações.
Ao registrar logs, é importante seguir as práticas recomendadas, como definir uma estrutura de logs clara, limitar a quantidade de informações confidenciais armazenadas nos logs e garantir que os logs sejam criptografados e protegidos contra acesso não autorizado.
Abaixo, temos um exemplo de uma mensagem de log que possui uma estrutura bem definida.
Ao usar logs estruturados, desenvolvedores podem facilmente buscar e analisar os dados pertinentes, e fazendo integrações com diversos tipos de ferramentas, facilitando a detecção de incidentes de segurança e o diagnóstico de problemas na aplicação.
Output Encoding
Output encoding é a prática de converter dados para um formato seguro antes de exibi-los aos usuários. Isso é importante porque atacantes podem injetar código malicioso em uma aplicação e potencialmente obter o controle do navegador do usuário. Ao codificar os dados de saída, podemos evitar esse tipo de ataque, conhecido como cross-site scripting (XSS).
Existem diversos tipos de output encoding, porque os navegadores lidam com HTML, JS e CSS de maneiras diferentes. O uso do método de encoding errado pode introduzir pontos de vulnerabilidade na aplicação.
Neste exemplo, a função encode() é usada para codificar a string de saída, substituindo os caracteres especiais por seus códigos de entidade HTML correspondentes.
Ao codificar os dados fornecidos pelo usuário antes de exibi-los na aplicação, desenvolvedores podem certificar que qualquer código malicioso se torne inofensivo.
Criptografia e Hashing
Criptografia e hashing são técnicas importantes usadas na codificação segura para proteger informações confidenciais, como senhas, números de cartão de crédito e outros tipos de dados pessoais. A criptografia envolve o uso de algoritmos matemáticos para embaralhar os dados de forma que só possam ser lidos por alguém que tenha a chave para decifrá-los. Hashing, por outro lado, é um processo unidirecional que pega dados e gera uma string de tamanho fixo de caracteres que não pode ser revertida para obter os dados originais.
A criptografia é uma técnica fundamental para garantir a confidencialidade dos dados, ou seja, a capacidade de manter informações sensíveis privadas e acessíveis somente por pessoas autorizadas. Por outro lado, o hashing é uma técnica importante para garantir a integridade dos dados, ou seja, a capacidade de garantir que os dados não foram modificados ou corrompidos durante o processo de transmissão ou armazenamento.
Para garantir a maior segurança possível para os dados, desenvolvedores devem usar algoritmos fortes e confiáveis, como AES e SHA-256. É muito importante evitar a implementação de algoritmos personalizados, pois eles podem introduzir pontos fracos e vulnerabilidades desconhecidas.
Além disso, gerenciar com cuidado as chaves criptográficas é crucial para garantir a segurança dos dados criptografados. As chaves devem ser armazenadas com segurança e devem ser alteradas regularmente para evitar que invasores obtenham acesso a informações confidenciais.
No geral, criptografia e hashing são ferramentas poderosas para proteger informações confidenciais e devem ser implementadas com bastante cuidado e consideração em aplicações.
Controle de Acesso
Controle de Acesso é o processo de garantir que usuários ou entidades recebam apenas o nível apropriado de acesso aos recursos em um sistema. Isso inclui itens como autenticação e autorização, bem como mecanismos para impor permissões e gerenciar funções. Quando implementado de maneira adequada, o controle de acesso pode ajudar a impedir o acesso não autorizado a informações e ações confidenciais, como visualizar ou modificar dados, excluir arquivos ou executar códigos.
Existem diversas boas práticas para implementar controle de acesso, como acesso baseado em função (RBAC) e princípio do menor privilégio. Menor privilégio significa dar aos usuários apenas os direitos de acesso necessários para executar suas tarefas e nada mais. Já o RBAC envolve a atribuição de permissões aos usuários com base em suas funções e responsabilidades dentro do sistema, com cada função tendo permissões de acesso específicas.
A implementação do controle de acesso requer um entendimento completo do fluxo de dados, recursos e funções do usuário dentro da aplicação. Também é importante revisar e atualizar regularmente as políticas de controle de acesso para garantir que permaneçam eficazes e alinhadas com as necessidades da organização.
Ao implementar medidas robustas de controle de acesso, desenvolvedores podem impedir o acesso não autorizado a informações confidenciais, proteger contra ataques mal-intencionados e manter a confidencialidade, integridade e disponibilidade de suas aplicações.
Código seguro: um processo contínuo
Embora não haja uma solução única que garanta a segurança total, a aplicação de um conjunto de práticas recomendadas pode reduzir bastante o risco de vulnerabilidades e fortalecer a resiliência de aplicações.
Escrever código seguro é um processo contínuo. É essencial seguir minimamente boas práticas durante todo o processo de desenvolvimento,como as que foram citadas nesse artigo, para garantir que o código entregue para os usuários esteja seguro.
Auditorias regulares de segurança, assessments de vulnerabilidades e testes de intrusão podem ajudar a identificar e mitigar possíveis pontos fracos na base de código. Em última análise, apesar de a responsabilidade de escrever um código seguro ser do desenvolvedor, a segurança da aplicação como um todo é de responsabilidade compartilhada, e é essencial enxergar a segurança como um aspecto fundamental do processo de desenvolvimento de software.
