Projects:CSRF Token/Specs
Contents |
Functional Requirements
Overview
Cross-Site Request Forgery (CSRF) is an attack where a malicious attacker makes a user’s browser to perform an unauthorized action on a trusted site for which the user is authenticated.
As the attacker has no way to receiver the command results, this attack is used to perform state-changing actions (i.e. changing a Product’s price) instead of simply fetching data.
Recommended approaches
OWASP recommends in their CSRF Prevention Cheat Sheet two complementary approaches to protect applications against CSRF attacks:
- First use HTTP request headers to confirm the request is same origin. If there is no XSS threat, you can assume the attacked won’t be able to spoof the Origin header of the malicious request.
- Use the Synchronizer Token pattern: The server generates a random token that is unique per session and includes in all URLs and requests that changes the application’s state. When the request is submitted, the server checks the token is present and is the same one expected for this session. This token protects against the attack as long as the attacker does not know the token.
This OWASP document notes that although the security can be improved using a different token per request, it may also leads to usability issues.
Reference Implementations
CSRF Protection is a basic security feature that is implemented in most web application frameworks. The following are examples of how web applications/frameworks tackles this issue.
Tomcat
Starting from version 7, Tomcat introduces a Filter that protects all non-GET requests introducing a nonce (one-use token string) that is known only by the server and the authenticated user. This nonce is stored in the HttpSession and can hold up to a defined number of the last generated tokens. A new nonce is generated when a response is retrieved and appended to the URL as a Query string.
Joomla
Joomla uses CSRF Token both in GET and POST requests. GET requests appends the token as a Query string while POST requests introduces a hidden field with the token. In any case, this token is unique per Session and per user. In fact, it is generated as a md5 hash of the User id appended with the Session token.
Symfony
Symfony implements by default a CSRF token that is unique per Session. Simfony lets you define a different token for each Form to improve security. To define a CSRF token in a manual form:
<input type="hidden" name="token" value="{{ csrf_token('delete-item') }}" />
Then in the controller you can check the token is valid with the following:
if ($this->isCsrfTokenValid('delete-item', $submittedToken))
Spring
Spring Security implements a synchronizer token pattern storing the token in the HttpSession object. This token is included as a hidden field in forms and as a HTTP header (X-CSRF-TOKEN) for Ajax and JSON requests. This token is used in all HTTP verbs that may change state of the application (POST, PUT, PATCH, DELETE). For a detailed documentation of applications and caveats of using CSRF protection, check out Spring Security documentation.
Technical Requirements
The solution adopted for this project is to implement only the Synchronizer token pattern, as it provides a fairly good protection against the attack by itself while remaining transparent for the rest of the system. To implement this pattern, a number of steps should be taken:
Backoffice
CSRF Protection is only implemented on standard ERP windows for state changing operations (ie. POST for saving entries or PUT to edit).
Generate and store the token
The token should be generated when the session starts and remain the same until the user logs out. For this reason the token is generated using SequenceIdData.getUUID(), which uses the UUID generator but removing the dashes and converting the characters to upper-case. This generation is performed in LoginUtils.fillSessionArguments and the resulting token is stored in the user session.
![]() | Note that generating the token un LoginUtil implies that the token is renewed every time the session changes, i.e. when the user changes its role. |
Make this token available in the client side
The token generated in the previous step will be retrieved to the client using the SessionDynamic request, and the token will be accesible in the JS generated code as OB.User.csrfToken.
![]() | As Unit Tests (i.e. BaseDataSourceTestDal tests) does not use the generated JS to make requests, they make a SessionDynamic request and extract the token from the response. |
Add the token in all required requests
There are two places to add the token to cover all backoffice standard windows: the standard view datasource and the notes view to add/remove notes. The most straightforward approach to include a parameter in all required requests is by adding it as a new parameter in ob-standard-view-datasource and in ob-view-form-notes, but it has a drawback: these parameters will be included in the URL query string, making the token visible for POST/PUT/DELETE requests, which is not desiderable.
For this reason, the OBRestDataSource (which is the common ancestor for the standard view and the notes datasource) is extended to support a csrfToken parameter that will be included in the JSON payload in POST/PUT request and in the query string in DELETE request, because DELETE requests has no payload.
Verify the token in the server side
Token check can be performed in a centralized way in the DataSourceServlet class. It takes the token from the JSON payload for POST and PUT requests while it is extracted from the query string parameters in DELETE requests.
POS
In retail POS we followed a similar approach as backoffice, but with some implementation changes.
In this case, protection is applied to POST requests handled by MobileService that are not self-authenticated, that is, requests that rely on the session created by the login request to authenticate.
Generate and store the token
As MobileCoreLoginHandler extends from LoginHandler, token is still generated and saved in the Session. No changes needed in this step.
Make this token available in the client side
Token is available through the response of preRenderActions command of loginutils. This response is used by ob-terminal-model to store this value as OB.MobileApp.model.get('csrfToken').
Add the token in all required requests
All POST requests are processed using OB.DS.Process.prototype.exec, so by adding the CSRF token in the data, it will be included in all POST requests.
Verify the token in the server side
MobileService has a doGetOrPost method which is common for GET and POST requests. In case it receives a POST request, the CSRF token is checked and if token is invalid, it returns a response with HTTP Unauthorized code (401), which will show a dialog to the user prompting them to restart their session.