You know the sales pitch by now: agile development helps to deliver value to stakeholders fast and efficiently. Frequent releases guarantee maximum visibility. We want to see progress now. So we’ll spare a thought for security along the way and worry about it when it’s too late.
Don’t get me wrong: if that’s how a team chooses to work they only have themselves to blame. There is nothing in agile development that downplays the importance of security. It’s just that an inordinate focus on delivering visible value fast doesn’t bode well for safe software, as this is a quality attribute that takes time (=money) to do well and meanwhile is not very visible to end users. Until it all goes horribly wrong of course.
It’s amazing how little code you need these days to set up a REST enabled server backend backed by a JDBC/Hibernate database. Check out the Spring Data REST docs and tutorial. It’s quite impressive and a great example of convention over configuration. So many goodies that come out of the box! It’s also a Pandora’s box of vulnerabilities waiting to happen if you’re not careful.
My main gripe with REST is that it is data centric instead of task or user oriented. Very often REST resources have a one-to-one mapping to database tables (although there is nothing about REST which demands this) with a single controller class and a database handle taking care of all CRUD operations. The problem with crud is also that it’s rather crude when it comes to privileges for manipulating data.
Many databases support authorization for the appropriate users and roles right up to the individual columns of a table. Imagine a personnel table: updating private phone numbers, address details, email and amending a social security number are different scenarios, requiring different levels of authority and probably accessed by different frontends. Your RDBMS has supported it for decades, but all this fine-grained control goes out the window when you take the shortcut of using a single PUT method in a REST endpoint to handle all scenarios. Ideally you wat to define your REST resources so they follow the authorization requirements of the corresponding domain objects and database tables. That probably means different endpoints, each with a database handle that has minimum privileges to the tables it needs to access. But really, who has time for that?
- it’s too much hassle to set up and administer.
- It doesn’t give immediate and visible value to the stakeholder.
- It makes us miss the sprint goal and messes up our velocity.
- It’s just not the way we do it in other projects
- <sarcasm>Who takes pride in doing a good job anyway these days?</sarcasm>
Here’s a textbook example of a pretty gross violation of the principle of least privilege. The Spring runtime creates an access object (using Hibernate) for CRUD operations on a database table. Here’s what the CrudRepository is capable of:
<S extends T> S save(S entity); <S extends T> Iterable<S> saveAll(Iterable<S> entities); Optional<T> findById(ID id); boolean existsById(ID id); Iterable<T> findAll(); Iterable<T> findAllById(Iterable<ID> ids); long count(); void deleteById(ID id); void delete(T entity); void deleteAll(Iterable<? extends T> entities); void deleteAll();
The options range from the relatively harmless existsById() to the nuclear bomb of deleteAll(), if the web server connects to your database in god mode. You extend this interface for the desired ORM table you wish to manipulate and if have an ounce of sense you override the dangerous methods to require the highest level of privilege. Like so:
But look at what the official Spring documentation does here. It suggests that we grant the lowest privilege by default and hand-pick some methods in the interface to require admin rights. What if you forget to override the deleteAll() method? Exactly. It’s the class that should have been annotated with ROLE_ADMIN and the harmless methods overridden to allow access for ordinary users.
I just finished a very interesting online course in Usable Security. Important factors of usability are learnability and efficiency, i.e. to what extent the system prevents you from making mistakes. As a developer you are an end-user of Spring Data, which is a breeze to setup, but hardly foolproof. If you follow the official documentation you might just forget to override deleteAll(). And while this may not be an exploitable vulnerability, it’s still bad programming: your code can do dangerous things that it should not be allowed to.
We can deliver great functionality at an ever faster pace, but we’re not done when it works. A healthy sense of security must be entrenched in a development team’s definition of done – whether or not you choose to call it by that name – for every piece of functionality. Especially with cool tech like Spring Data REST.