Prevenir o carregamento de dados comprometidos

Em posts anteriores, compartilhei algumas dicas sobre como se proteger melhor contra vazamentos de dados, seja implementando uma arquitetura que separa os dados em questões, seja criptografando seus dados de forma transparente na camada de persistência. Essa dica é diferente: mostra como evitar que dados que estão comprometidos sejam carregados.

O raciocínio é que uma possível vulnerabilidade em sua pilha pode dar a um invasor a possibilidade de ter seus privilégios escalados manipulando dados em seu banco de dados. Por exemplo, uma injeção de SQL pode abrir as portas para um invasor criar um novo usuário administrador. Ou o uso indevido da “atribuição em massa” presente em alguns frameworks, que permitiria a um invasor atualizar um campo que não deveria ser atualizado.

A ideia é semelhante ao conceito de CRC, ou checksum , mas com um propósito e implementação diferentes.

O código é extremamente simples. Tudo o que você precisa fazer é criar alguns novos métodos e campos no modelo que deseja proteger:

cascaio-app-info / src / main / java /…/ Application.java

private String checksum;
...
@PrePersist
@PreUpdate
protected void updateChecksum() {
String newChecksum = recordsChecksum();
StrongPasswordEncryptor passwordEncryptor = new StrongPasswordEncryptor();
this.checksum = passwordEncryptor.encryptPassword(newChecksum);
}

@PostLoad
protected void checkChecksum() {
String expectedChecksum = recordsChecksum();
StrongPasswordEncryptor passwordEncryptor = new StrongPasswordEncryptor();
if (!passwordEncryptor.checkPassword(expectedChecksum, this.checksum)) {
throw new IllegalStateException("It seems that this record has been tampered.");
}
}

private String recordsChecksum() {
return new StringBuilder(this.getId())
.append("-").append(this.getName())
.append("-").append(this.salt)
.append("-").append(this.id)
.append("-").append(this.applicationType)
.append("-").append(CascaioAppInfoProperties.getProperty("model.pepper"))
.toString();
}

E para provar que isso funciona, podemos usar este teste de unidade.

cascaio-app-info / src / test / java /…/ ApplicationUnitTest.java

@Test(expected = IllegalStateException.class)
public void testCompromisedDataIsNotLoaded() {
entityManager
.getTransaction().begin();
final String accessKey = KeyGenerator.generate();
final String secretKey = KeyGenerator.generate();
final Application application = new Application("testCompromisedDataIsNotLoaded", accessKey, secretKey, ApplicationType.REFERENCE_DATA);
entityManager
.persist(application);
entityManager
.getTransaction().commit();

entityManager
.getTransaction().begin();
entityManager
.unwrap(Session.class).doWork(new Work() {
@Override
public void execute(Connection connection) throws SQLException {
PreparedStatement pstmt = connection.prepareStatement("update application set applicationType = 'USER_DATA' where id = ?");
pstmt
.setString(1, application.getId());
int affectedRows = pstmt.executeUpdate();
assertEquals
("Expected to get exactly 1 row updated.", 1, affectedRows);
}
});
entityManager
.getTransaction().commit();

entityManager
.clear();

entityManager
.getTransaction().begin();
Application fromDatabase = entityManager.find(Application.class, application.getId());
System.out.println(fromDatabase.getId());
System.out.println(fromDatabase.getApplicationType().toString());
entityManager
.getTransaction().commit();
}

Algumas coisas aqui devem ser mencionadas:

  • Como sempre, estamos usando Jasypt para facilitar nossa criptografia. Nesse caso, estamos usando o StrongPasswordEncryptordele. Você também pode usar o StandardPasswordEncryptor, que é mais rápido e, para isso, é bom o suficiente.
  • Estamos usando um sal específico para registro e uma pimenta de aplicação específica para esse fim. Isso evita que um invasor tenha todas as informações necessárias para gerar uma soma de verificação válida apenas olhando o banco de dados. A pimenta é armazenada criptografada no arquivo de propriedades.
  • Estamos lançando uma exceção não verificada. Se não for manuseado corretamente, ele explodirá na cara do usuário. Portanto, certifique-se de ter uma maneira genérica de capturar essas exceções e formatá-las adequadamente para seus usuários. Além disso, construir um relator de exceção não é uma má ideia, de modo que você seja notificado sempre que algo de ruim acontecer.