There is a type of attack (CSRF) that every form post is susceptible to. User 1 is logged into a site A and has a valid session. User 2 then sends an email message with a form post to site A, or a link to a rogue web page with a form post to site A. Since the session is maintained in cookies, this new form post will be accepted by the server as a valid part of the current session. In this way, User 2 can make User 1 make changes in the Site A that User 1 was not expecting.
User 2 wants to get administrator privileges on a particular application. User 1 has the right to make other users to be administrators, and there is a form-based UI to allow his to do that. User 2 figures out what the post request would look like, to make User 2 an administrator. User 2 creates a bogus form that has all the right form field values (hidden) and a submit button that appears innocuous. User 2 then somehow fools User 1 into clicking the button. The form posted by User 1 makes User 2 an administrator, without User 1 knowing it. User 2, now being administrator, can go do more nefarious things in the system.
In short, User 1 can operate a web UI that will send data to the server, and that server will attribute that data as a request from User 1. What we need to do is to find a way to assure that User 2 can not arrange for User 1 to send something they were not expecting.
Site A is using a cookie to keep track of the session, so that all interactions by User 1 are automatically associated with the authenticated session. This cookie will be used for all interactions with Site A, whether the link being clicked on originates from Site A, or from a rogue site.
Form post is the normal way that you change data on the server. Often a form is presented, the user enters new values, and post those updated values back to the server. But a form post does not need to be from a visible form. It could be that all the fields are hidden, and the only thing you see is the button to press. Or a completely invisible form could be posted using java script. From the server’s point of view, all of these are the same: the information is submitted to the server.
A rogue User 2 might want to create a new record in Site A but lacks the rights to do so. What User 2 needs to do is to figure out what the data in the form post would be, and then trick User 1 (who has the right to do this) to click on the form button or link. The new record would be created, and User 1 will have created it, only User 1 does not know that they did it. From their point of view it was just a link or a button to click on.
The rise in use of AJAX includes some additional possibilities. An AJAX based UI is always posting data back to the server as chunks of XML. User 2 tricks USer 1 to visit a page which looks innocuous, but actually makes XML post requests to the Site A, and can do all sorts of things all validated as if User 1 authorized these actions.
Solution to CSRF
The first step is that any time you have a form that does a POST to a particular address, the handler at that address should be set to accept ONLY POST. A GET operation should be disabled (causing an error). This eliminates the easiest form of manipulations, and that is to simply make a web link with all the right parameters that causes the server to update something. The server should change internal data ONLY on POST operations, and never on GET operations. Still, it is easy enough to arrange for a rogue post operation.
The second step is that all operations that will modify server data should include a session password in the posted data. When modifying data, it is not sufficient to get the session key from the cookie, but it should be part of the form data as well.
An evil User 2 might know what POST operation is needed, and might know the names and values of all the parameters to that POST. But User 2 will NOT know User 1’s current session ID. User 2 may know that User 1 is logged in a lot, and has the appropriate rights, and User 2 can make a form that posts all the right data values. But User 2 can not know that User 1’s current session ID is.
This means that when the form is generated by the UI layer for User 1, it will be generated with an additional hidden field that is specific to User 1. Then, when the server sees a form post from User 1, if there is anything else beside User 1 value in that hidden field, the post data will not be accepted. It has to be a value unique to that session, so when they log out, any od trace that happens to be hanging around will no longer work.
It does not have to be a session ID, in fact it probably shouldn’t be the session ID, it should be a different value which is unique in the session. Every new session will generate a new hidden value. Also, if possible, each form should have a different variant so that observing the value on one form would not be enough. A unique value each time a form is generated would assure that each form is used only once. But this might be overkill. When using SSL, it should be quite hard for User 2 to see anything in the session that User 1 currently has, and so a simple value unique to the session for each user is best.
Each time a user logs in, a new “form submission key” value will be generated and stored in the user session area. Every time a form is generated, a hidden field of the form will include this form submission key. When data is posted to the server, the form submission key will be matched to the value stored in that user’s session, and if it does not match, the data is rejected.
This will make it many times harder for a hacker to fool a user into clicking on a button that causes the server to update information.