Preventing the loading of compromised data
In previous posts, I shared a couple of tips on how to better protect yourself from getting your data leaked, be it by implementing an architecture that separate the data in concerns, be it by transparently encrypting your data on the persistence layer. This tip is different: it shows how to prevent data that is known to be compromised to be loaded.
The reasoning is that a possible vulnerability on your stack might give an attacker the possibility of having their privileges escalated by manipulating data on your database. For instance, a SQL Injection might open the doors for an attacker to create a new admin user. Or the misuse of the "mass assignment" present in some frameworks, that would allow an attacker to update a field which was not supposed to be updated.
The idea is similar to the concept of CRC, or checksum, but with a different purpose and implementation.
The code is extremely simple. All you need to do is to create some new methods and fields on the model you want to protect:
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();
}
And to prove that this works, we can use this unit test.
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();
}
Some things here are worth mentioning:
- As always, we are using Jasypt to facilitate our encryption. In this case, we are using the
StrongPasswordEncryptor
from it. You can also use theStandardPasswordEncryptor
, which is faster and, for this purpose, is good enough. - We are using a record-specific salt and an application pepper specific for this purpose. This is to prevent an attacker from having all the required information to generate a valid checksum by just looking at the database. The pepper is stored encrypted into the properties file.
- We are throwing an unchecked exception. If not properly handled, it will explode into your user's face. So, make sure to have a generic way to catch those exceptions and properly format them to your users. Also, building an exception reporter is not a bad idea, so that you get notified whenever something bad as this happens.