I gave a talk at ASFWS 2013 about CSRF. The purpose of the talk was to go over an aspect of CSRF which is often neglected.
I stared with an overview of some security engineering concepts: making sure frameworks are safe by default, indicating potentially unsafe features, keeping code easy to audit, surviving copy-pasting, etc.
I then talked about state mutation on read requests (i.e. on GET/HEAD requests). In general, web applications should not mutate state on such requests, there are however many use-cases for doing it.
An important question is: how do you design a web framework which lets you sometimes mutate state on read requests but does not compromise the security of the entire web application?
Here are the notes I gathered while preparing the talk. I got plenty of help from my co-workers, who reviewed my work and engaged in very interesting discussions. Many thanks to Erling, Scott, Mathieu, Keito, Michael, Alec and everyone who helped me!
Cross Site Request Forgery is a web application security flaw which lets a malcious site take actions on a vulernable site's users.
We are interested in any kind of state mutation. Typically, this is going to include:
As a web application grows, new caches or backend systems get added. It's useful to keep in mind that the set of systems affected by state mutation can change over time.
Smaller web applications are usually not prone to CSRF on read requests. Depending on the team/codebase size, it's possible to rely on coding practices & code review. Things get ugly as the team & codebase grows. I therefore made the following assumptions:
One way to prevent state mutation on read requests is to have some kind of model/tracking system.
The system needs to be flexible enough to handle the different possible kinds of storage systems.
There is a bunch of reasons for having to write on a read request. Here are some:
We can whitelist specific write operations:
allow_writes.push(true); writeLogs(); allow_writes.pop();
We can whitelist specific databases, tables or rows?
mysql_config.push("table=logs"); ... mysql.insert("logs", ...remaining SQL query...);