Ignoring the semantics of HTTP methods such as GET and POST can have disastrous security repercussions. You might think that using GET methods for deleting entities is fine since you only need to pass a single identifier and do not want to set up its own HTML form for doing so. Please let me convince you of the opposite.
Before we can go into detail why this distinction matters for web application security, we need to grasp the semantics of HTTP methods.
MDN lists HTTP GET as an HTTP method that needs to be safe and idempotent. If you want to conform to their interpretation of REST principles (and I believe this to be a very good idea), you need to fulfil those two properties in all your GET methods.
According to MDN:
An HTTP method is safe if it doesn’t alter the state of the server. In other words, a method is safe if it leads to a read-only operation.
Safe methods do not change attributes in a database, they do not send emails and they most certainly do not delete anything. Examples for safe methods are:
- Fetch the email address of user X
- Fetch the current system time
- Fetch the currently logged in user
As you can see, most such methods only retrieve data and therefore are supposed to use the GET HTTP method.
According to MDN:
An HTTP method is idempotent if an identical request can be made once or several times in a row with the same effect while leaving the server in the same state. In other words, an idempotent method should not have any side-effects (except for keeping statistics).
If this definition sounds similar to that of safety, consider a method that deletes a user with a certain identifier. When you call the method multiple times in a row, it will not suddenly change its behaviour and delete other users but it will just make sure that after its invocation, there no longer exists a user with that particular identifier. Such a function is therefore still idempotent, but not safe since it alters the state of the server by (potentially) deleting a user.
Differences between GET and POST
While all GET methods should be idempotent and safe, this is not required for POST methods. Individual such methods might have those properties, but the end users of the API cannot assume them.
More importantly, web frameworks that implement security mechanisms cannot assume those properties.
Security Impact of GET and POST
There is one particular security threat that distinguishes between GET and POST methods: Cross-Site Request Forgery (CSRF) attacks. Getting into detail would fill a whole book, but imagine you have the following HTTP GET method that deletes a user:
So if you are logged into your application with the necessary privileges, calling this method would completely erase the user with identifier
Now imagine an attacker somehow manages to direct your browser to visit a page with the following code in its HTML source:
<img src="https://www.example.org/users/delete/123" />
Your innocent browser does not detect any malicious intentions and tries to load the picture under the above URL. However, this leads to the request being loaded under your (still active) application session and whoops – the method call has been made and the user has been deleted.
Notice that the risk of CSRF attacks is not that attackers could get access to the data returned by the requests – this is forbidden by the cross-origin detection mechanisms of the browser. The danger of CSRF attacks is rather, that attackers change the server state with such a request (as in the example above).
Most modern web application frameworks automatically have protections against such attacks: CSRF-tokens. They are randomly generated strings that are added to all POST forms in order to make such attacks impossible – requests are only forwarded to the application controller when the CSRF token matches the expected one, and an attacker has no way to find out the valid token since it is randomly generated at each page view.
However, no such mechanisms are used for GET methods. Why not? Because the HTTP specifications specify that GET requests are idempotent and safe, and therefore any such protection mechanisms are not needed and would only have a negative performance impact.
So the next time you use GET for methods that are not safe and idempotent, remember: you are on your own and neither browser mechanisms nor the CSRF protections from your framework are going to protect you. As a result, you are probably making your users vulnerable to CSRF attacks.
The Correct Way
So using GET requests for deleting entities is a bad idea – what to use instead?
Many programmers only distinguish between GET and POST requests for the above security issues. However, there is a more appropriate solution for deleting entities: the DELETE method.
Compared to POST, it is idempotent (as mentioned above): issuing the same DELETE call multiple times with the same identifier will cause no side-effects. Also, it immediately conveys a certain semantic to the user of the API – users immediately have a pretty good idea what DELETE means.
However, whether you use POST or DELETE does not matter for CSRF-protection purposes. As long as you do not use GET for calls which are not safe, everything is fine.