CodeGym /Blogue Java /Random-PT /Segurança em Java: melhores práticas
John Squirrels
Nível 41
San Francisco

Segurança em Java: melhores práticas

Publicado no grupo Random-PT
Uma das métricas mais importantes em aplicativos de servidor é a segurança. Este é um tipo de requisito não funcional . Segurança em Java: melhores práticas - 1A segurança inclui muitos componentes. É claro que seria necessário mais de um artigo para cobrir totalmente todos os princípios e medidas de segurança conhecidos, portanto, vamos nos debruçar sobre os mais importantes. Uma pessoa bem versada neste tópico pode configurar todos os processos relevantes, evitar a criação de novas brechas de segurança e será necessária em qualquer equipe. Claro, você não deve pensar que seu aplicativo será 100% seguro se você seguir essas práticas. Não! Mas com certeza será mais seguro com eles. Vamos.

1. Fornecer segurança ao nível da linguagem Java

Em primeiro lugar, a segurança em Java começa no nível dos recursos da linguagem. O que faríamos se não houvesse modificadores de acesso? Não haveria nada além de anarquia. A linguagem de programação nos ajuda a escrever código seguro e também faz uso de muitos recursos de segurança implícitos:
  1. Digitação forte. Java é uma linguagem tipada estaticamente. Isso torna possível detectar erros relacionados ao tipo em tempo de execução.
  2. Modificadores de acesso. Isso nos permite personalizar o acesso a classes, métodos e campos conforme necessário.
  3. Gerenciamento automático de memória. Para isso, os desenvolvedores Java contam com um coletor de lixo que nos livra de ter que configurar tudo manualmente. Sim, às vezes surgem problemas.
  4. Verificação de bytecode : Java é compilado em bytecode, que é verificado pelo tempo de execução antes de ser executado.
Além disso, existem recomendações de segurança da Oracle . Claro, não está escrito em linguagem pomposa e você pode cair no sono várias vezes enquanto o lê, mas vale a pena. Em particular, o documento intitulado Secure Coding Guidelines for Java SE é importante. Ele fornece conselhos sobre como escrever código seguro. Este documento transmite uma enorme quantidade de informações altamente úteis. Se você tiver a chance, você definitivamente deveria lê-lo. Para atiçar seu interesse por este material, seguem algumas dicas interessantes:
  1. Evite a serialização de classes sensíveis à segurança. A serialização expõe a interface de classe no arquivo serializado, sem falar nos dados que são serializados.
  2. Tente evitar classes mutáveis ​​para dados. Isso fornece todos os benefícios das classes imutáveis ​​(por exemplo, thread safety). Se você tiver um objeto mutável, isso pode levar a um comportamento inesperado.
  3. Faça cópias de objetos mutáveis ​​retornados. Se um método retornar uma referência a um objeto mutável interno, o código do cliente poderá alterar o estado interno do objeto.
  4. E assim por diante…
Basicamente, Secure Coding Guidelines for Java SE é uma coleção de dicas e truques sobre como escrever código Java de forma correta e segura.

2. Elimine vulnerabilidades de injeção de SQL

Este é um tipo especial de vulnerabilidade. É especial porque é uma das vulnerabilidades mais famosas e mais comuns. Se você nunca se interessou por segurança de computador, não deve saber. O que é injeção de SQL? Este é um ataque de banco de dados que envolve a injeção de código SQL adicional onde não é esperado. Suponha que temos um método que aceita algum tipo de parâmetro para consultar o banco de dados. Por exemplo, um nome de usuário. O código vulnerável seria mais ou menos assim:

// This method retrieves from the database all users with a certain name
public List findByFirstName(String firstName) throws SQLException {
   // Connect to the database
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
  
   // Compose a SQL database query with our firstName
   String query = "SELECT * FROM USERS WHERE firstName = " + firstName;
  
   // Execute the query
   Statement statement = connection.createStatement();
   ResultSet result = statement.executeQuery(query);

   // Use mapToUsers to convert the ResultSet into a collection of users.
   return mapToUsers(result);
}

private List mapToUsers(ResultSet resultSet) {
   // Converts to a collection of users
}
Neste exemplo, uma consulta SQL é preparada antecipadamente em uma linha separada. Então, qual é o problema, certo? Talvez o problema seja que seria melhor usar String.format ? Não? Bem, e então? Vamos nos colocar no lugar de um testador e pensar sobre o que poderia ser passado como o valor de firstName . Por exemplo:
  1. Podemos passar o que é esperado - um nome de usuário. Em seguida, o banco de dados retornará todos os usuários com esse nome.
  2. Podemos passar uma string vazia. Então todos os usuários serão retornados.
  3. Mas também podemos passar o seguinte: "'; DROP TABLE USERS;". E aqui agora temos problemas enormes. Esta consulta excluirá uma tabela do banco de dados. Junto com todos os dados. TUDO ISSO.
Você pode imaginar os problemas que isso causaria? Além disso, você pode escrever o que quiser. Você pode alterar os nomes de todos os usuários. Você pode excluir seus endereços. O escopo para sabotagem é imenso. Para evitar isso, você precisa evitar a injeção de uma consulta pronta e, em vez disso, formar a consulta usando parâmetros. Essa deve ser a única maneira de criar consultas de banco de dados. É assim que você pode eliminar essa vulnerabilidade. Por exemplo:

// This method retrieves from the database all users with a certain name
public List findByFirstName(String firstName) throws SQLException {
   // Connect to the database
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

   // Create a parameterized query.
   String query = "SELECT * FROM USERS WHERE firstName = ?";

   // Create a prepared statement with the parameterized query
   PreparedStatement statement = connection.prepareStatement(query);
  
   // Pass the parameter's value
   statement.setString(1, firstName);

   // Execute the query
   ResultSet result = statement.executeQuery(query);

   // Use mapToUsers to convert the ResultSet into a collection of users.
   return mapToUsers(result);
}

private List mapToUsers(ResultSet resultSet) {
   // Converts to a collection of users
}
Desta forma, a vulnerabilidade é evitada. Para quem quiser se aprofundar neste artigo, aqui está um ótimo exemplo . Como você sabe quando entende essa vulnerabilidade? Se você entendeu a piada do quadrinho abaixo, provavelmente tem uma noção clara do que é essa vulnerabilidade :DSegurança em Java: melhores práticas - 2

3. Verifique as dependências e mantenha-as atualizadas

O que isso significa? Se você não sabe o que é uma dependência, vou explicar. Uma dependência é um arquivo JAR com código conectado a um projeto usando sistemas de compilação automática (Maven, Gradle, Ant) para reutilizar a solução de outra pessoa. Por exemplo, Projeto Lombok , que gera getters, setters, etc. para nós no tempo de execução. Aplicativos grandes podem ter muitas e muitas dependências. Alguns são transitivos (isto é, cada dependência pode ter suas próprias dependências e assim por diante). Como resultado, os invasores estão cada vez mais atentos às dependências de código aberto, pois são usadas regularmente e muitos clientes podem ter problemas por causa delas. É importante certificar-se de que não há vulnerabilidades conhecidas em toda a árvore de dependências (sim, parece uma árvore). Existem várias maneiras de fazer isso.

Use Snyk para monitoramento de dependência

Snyk verifica todas as dependências do projeto e sinaliza vulnerabilidades conhecidas. Você pode se registrar no Snyk e importar seus projetos via GitHub. Segurança em Java: melhores práticas - 3Além disso, como você pode ver na imagem acima, se uma vulnerabilidade for corrigida em uma versão mais recente, o Snyk oferecerá a correção e criará uma solicitação pull. Você pode usá-lo gratuitamente para projetos de código aberto. Os projetos são verificados em intervalos regulares, por exemplo, uma vez por semana, uma vez por mês. Eu registrei e adicionei todos os meus repositórios públicos ao Snyk scan (não há nada de perigoso nisso, pois eles já são públicos para todos). Snyk então mostrou o resultado da verificação: Segurança em Java: melhores práticas - 4E depois de um tempo, o Snyk-bot preparou várias solicitações pull em projetos onde as dependências precisam ser atualizadas: Segurança em Java: melhores práticas - 5E também:Segurança em Java: melhores práticas - 6Esta é uma ótima ferramenta para encontrar vulnerabilidades e monitorar atualizações para novas versões.

Use o laboratório de segurança do GitHub

Qualquer pessoa que trabalhe no GitHub pode aproveitar suas ferramentas integradas. Você pode ler mais sobre essa abordagem na postagem do blog intitulada Announcing GitHub Security Lab . Essa ferramenta, é claro, é mais simples que o Snyk, mas você definitivamente não deve negligenciá-la. Além do mais, o número de vulnerabilidades conhecidas só aumentará, então tanto o Snyk quanto o GitHub Security Lab continuarão a se expandir e melhorar.

Habilitar Sonatype DepShield

Se você usa o GitHub para armazenar seus repositórios, pode adicionar Sonatype DepShield, um dos aplicativos do MarketPlace, aos seus projetos. Ele também pode ser usado para verificar projetos em busca de dependências. Além disso, se encontrar algo, um GitHub Issue será gerado com uma descrição apropriada conforme mostrado abaixo:Segurança em Java: melhores práticas - 7

4. Manuseie dados confidenciais com cuidado

Podemos alternativamente usar a frase "dados confidenciais". O vazamento de informações pessoais, números de cartão de crédito e outras informações confidenciais de um cliente pode causar danos irreparáveis. Em primeiro lugar, observe atentamente o design do seu aplicativo e determine se você realmente precisa deste ou daquele dado. Talvez você não precise realmente de alguns dos dados que possui - dados que foram adicionados para um futuro que ainda não chegou e provavelmente não virá. Além disso, muitos vazam inadvertidamente esses dados por meio do registro. Uma maneira fácil de impedir que dados confidenciais entrem em seus logs é limpar os métodos toString() de entidades de domínio (como Usuário, Aluno, Professor, etc.). Isso evitará que você envie campos confidenciais acidentalmente. Se você usar o Lombok para gerar o toString()método, você pode usar a anotação @ToString.Exclude para evitar que um campo seja usado na saída do método toString() . Além disso, tenha muito cuidado ao enviar dados para o mundo exterior. Suponha que tenhamos um endpoint HTTP que mostre os nomes de todos os usuários. Não há necessidade de mostrar o ID interno exclusivo de um usuário. Por que? Porque um invasor pode usá-lo para obter outras informações mais confidenciais sobre o usuário. Por exemplo, se você usar Jackson para serializar/desserializar um POJO de/para JSON , poderá usar @JsonIgnore e @JsonIgnorePropertiesanotações para impedir a serialização/desserialização de campos específicos. Em geral, você precisa usar diferentes classes POJO em lugares diferentes. O que isso significa?
  1. Ao trabalhar com um banco de dados, use um tipo de POJO (uma entidade).
  2. Ao trabalhar com lógica de negócios, converta uma entidade em um modelo.
  3. Ao trabalhar com o mundo externo e enviar solicitações HTTP, use diferentes entidades (DTOs).
Desta forma, você pode definir claramente quais campos serão visíveis de fora e quais não serão.

Use criptografia forte e algoritmos de hash

Os dados confidenciais dos clientes devem ser armazenados de forma segura. Para fazer isso, precisamos usar criptografia. Dependendo da tarefa, você precisa decidir qual tipo de criptografia usar. Além disso, uma criptografia mais forte leva mais tempo, portanto, novamente, você precisa considerar o quanto a necessidade dela justifica o tempo gasto nela. Claro, você mesmo pode escrever um algoritmo de criptografia. Mas isso é desnecessário. Você pode usar as soluções existentes nesta área. Por exemplo, Google Tink :

<!-- https://mvnrepository.com/artifact/com.google.crypto.tink/tink -->
<dependency>
   <groupid>com.google.crypto.tink</groupid>
   <artifactid>tink</artifactid>
   <version>1.3.0</version>
</dependency>
Vamos ver o que fazer, usando este exemplo envolvendo criptografia e descriptografia:

private static void encryptDecryptExample() {
   AeadConfig.register();
   KeysetHandle handle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_CTR_HMAC_SHA256);

   String plaintext = "Elvis lives!";
   String aad = "Buddy Holly";

   Aead aead = handle.getPrimitive(Aead.class);
   byte[] encrypted = aead.encrypt(plaintext.getBytes(), aad.getBytes());
   String encryptedString = Base64.getEncoder().encodeToString(encrypted);
   System.out.println(encryptedString);

   byte[] decrypted = aead.decrypt(Base64.getDecoder().decode(encrypted), aad.getBytes());
   System.out.println(new String(decrypted));
}

Criptografia de senhas

Para esta tarefa, é mais seguro usar criptografia assimétrica. Por que? Porque o aplicativo realmente não precisa descriptografar senhas. Esta é a abordagem padrão. Na realidade, quando um usuário insere uma senha, o sistema a criptografa e a compara com o que existe no armazenamento de senhas. O mesmo processo de criptografia é executado, então podemos esperar que eles correspondam, se a senha correta for inserida, é claro :) BCrypt e SCrypt são adequados aqui. Ambas são funções unidirecionais (hashes criptográficos) com algoritmos computacionalmente complexos que levam muito tempo. Isso é exatamente o que precisamos, já que os cálculos diretos levarão uma eternidade (bem, muito, muito tempo). Spring Security suporta toda uma gama de algoritmos. Podemos usar SCryptPasswordEncoder e BCryptPasswordEncoder. O que atualmente é considerado um algoritmo de criptografia forte pode ser considerado fraco no próximo ano. Como resultado, concluímos que devemos verificar regularmente os algoritmos que usamos e, conforme necessário, atualizar as bibliotecas que contêm os algoritmos de criptografia.

Em vez de uma conclusão

Hoje falamos de segurança e, naturalmente, muitas coisas ficaram para trás. Acabei de abrir a porta para um novo mundo para você, um mundo que tem vida própria. A segurança é como a política: se você não se ocupar com a política, a política se ocupará de você. Eu tradicionalmente sugiro que você me siga na conta do GitHub . Lá posto minhas criações envolvendo diversas tecnologias que estou estudando e aplicando no trabalho.

Links Úteis

  1. Guru99: Tutorial de injeção de SQL
  2. Oracle: Centro de Recursos de Segurança Java
  3. Oracle: Diretrizes de Codificação Segura para Java SE
  4. Baeldung: Os fundamentos da segurança Java
  5. Médio: 10 dicas para aumentar sua segurança Java
  6. Snyk: 10 melhores práticas de segurança Java
  7. GitHub: Anunciando o GitHub Security Lab: protegendo o código do mundo, juntos
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION