Welcome to the first part in our AngularJS Security Series. Here, we’ll discuss the various solutions to write more secure applications. Our goal is simple: to help developers better understand Angular and embrace the practice of writing more secure code.
The AngularJS Module is the basic building block of every AngularJS application. The Module houses components like controllers, config, and services. In this post, we’ll take a closer look at the $http service, which provides two default security features: automatic CSRF protection and the anti-JSON hijacking mechanism.
As we mentioned in the series introduction, AngularJS comes with a slew of advanced security features already enabled. You don’t have to do a single thing in the client-side code to use either of the two security features we’ll discuss today.
However, there’s a catch. In order to take full advantage of automatic CSRF protection and the anti-JSON hijacking mechanism, you’ll have to do some work on the server-side.
Thwarting request forgeries is a cakewalk
The first feature of AngularJS you’ll need to augment is the built-in automatic Cross-Site Request Forgery (CSRF) protection.
This feature protects your application by automatically submitting a previously-received CSRF protection value, sometimes called a nonce (a cryptographic value used only once) or token, back to the server when using the AngularJS $http and $resource services. The server then needs to compare this value to the original CSRF token sent to the client. If both tokens are the same, the server proceeds with the client’s request. If not, the request is discarded. It is important to point out that this comparison is not done automatically by AngularJS. This is something that you, as the developer, need to do on the server-side. Fortunately, many of the server-side frameworks out there can help with implementing this server-side control.
Sounds straightforward enough, but what does CSRF token handling actually look like?
CSRF protection in a nutshell
The simplified, five-step process goes something like this:
1.The user performs the initial GET request for the login page. The site returns the login page (note that no CSRF token has been given to the client since the client has yet to authenticate).
2.The user enters their credentials in the login page and submits this form.
3.If the credentials are valid, the server returns the user to an authenticated site page. In addition, on the response, the user receives an embedded CSRF token stored in a cookie (XSRF-TOKEN). This token is created by the server and stored on the server for use later in step five. Here again, this is the server-side piece that we just discussed, which must be implemented by you, the developer.
4.Now things get a bit messy. When the user performs any client-side action that requires server-side functionality (through the use of the $http and $resource services), AngularJS first reads the token stored in the cookie (XSRF-TOKEN) provided by the server and then creates a new HTTP header called X-XSRF-TOKEN. The server token (read from the XSRF-TOKEN cookie) is stored in this header value.
Finally, the client request is sent to the server. This is all automatically performed by AngularJS on the client-side. No extra code needs to be written.
5.When the server receives this client request, it compares the token stored in the header X-XSRF-TOKEN to the original token value (created in step three).
If the tokens are equivalent, the server may process the request. If not, the server will disregard the request and present the user with an error message. Again, this part must be implemented by you, the developer (or your framework), on the server-side.
CSRF protection with AngularJS and Express.js
The key to strengthening your application with CSRF protection is in the server-side code.
Since there are so many different server technologies and each has its own idiosyncrasies, we’ll focus on Node.js and Express.js, and use the popular csurf middleware. Even though we’re using csurf, the basic premise of handling CSRF protection on the server can be translated to other technologies with ease.
The first thing we need to do is add the necessary middleware into our Node.js application:
Next, we configure the csurf middleware to handle our AngularJS front-end:
Finishing up the routes
Now we can create our routes that require CSRF protection. The initial rendering of the login page requires the following route to display the login page to the user:
The login.html page is rendered on the user’s browser. At this point, the user can enter and submit their credentials.
When the user submits their credentials, the /doLogin route on the server is invoked by the client to authenticate the user:
If the authentication is successful, the server generates a new CSRF token and places it in a cookie with the name XSRF-TOKEN. The CSRF token value is obtained from the csurf middleware via the req.csrfToken() function. By default, AngularJS will look for this cookie named XSRF-TOKEN and put its value into the X-XSRF-TOKEN header on subsequent requests. If we didn’t set the XSRF-TOKEN value in the response cookie at this point, the CSRF token would not be created and passed to the client, causing all subsequent client requests for CSRF protected routes to be rejected.
An additional point of protection: Set the attribute within the cookie transporting the CSRF token. This setting will make sure that the CSRF token is only transported over a secure channel (i.e. SSL/TLS).
Note that both the /login.html and /doLogin routes will not verify the CSRF token due to the fact that they are both using the HTTP GET method, which is, by default, exempt from CSRF protection when using csurf middleware. The HTTP POST method, by default, always verifies the CSRF token.
The next method will change the state on the server; therefore, you’ll want to use CSRF protection. This method needs to be called from within an authenticated page such as home.html (seen in our previous route function /doLogin). Otherwise, a valid CSRF token would not be sent to it causing the client request to be rejected:
The final route we need is our /logout route to clear any CSRF tokens in our cookies:
And then, of course, we add in our error handling:
This app.use function needs to be placed before all of the CSRF protected routes so that our custom error handler will be injected before any protected routes are called.
Hackers can circumvent CSRF protections through XSS
When implemented correctly, the CSRF protections we’ve given you will make your application more secure. That said, you’ll still need to pay close attention to your application since a single XSS exploit could allow CSRF tokens to be stolen from the user and reused in malicious requests.
It is of the utmost importance that you find and eliminate all XSS vulnerabilities, not only in your application but in other applications sharing the same domain/sub-domain, too.
As an added layer of prevention against re-using stolen CSRF tokens, the server can generate new CSRF tokens on every response. However, if there is an XSS hole in your application, the attacker can simply use the $http or $resource services in their XSS exploit code to create a new (valid) request without having to steal your CSRF token. Your best bet is always to find and eliminate any XSS vulnerabilities in your applications.
A few more things to keep in mind
•If you use another CSRF protection mechanism on the server, verify that it produces a of sufficient length for the token. The csurf middleware uses appropriate methods to create a sufficiently strong CSRF token.
•Use an appropriate session timeout for every logged in user. This will reduce an attacker’s ability to brute force the CSRF token.
•Upon logout or session timeout, the CSRF token should be explicitly invalidated and removed.
•At a minimum, a new token should be generated on the server for each session. Do not reuse or store tokens after the session ends. To provide even stronger CSRF protection, generate a new token for each server response to further prevent an attacker from reusing existing tokens.
•Only send the CSRF token over secure (SSL/TLS) connections. As previously mentioned, use the secure attribute for cookies to make sure CSRF tokens are being sent over a secure connection.
•Make sure all server routes that mutate state are protected with a CSRF token. Not all routes will need CSRF protection. Also, verify that all requests do not mutate state since CSRF protection is turned off by default for all GET requests (as well as HEAD and OPTIONS requests).
•If the token comparison on the server does not match, do not allow any processing of the request to occur and simply respond with a generic error message. Do not provide any details in the error message that would aid an attacker.
•If your application connects to and sends requests to multiple servers, verify that each server is properly generating and handling CSRF protection tokens. In addition, verify that your AngularJS application is handling CSRF protection tokens from third-party servers correctly.
Now that we’ve shown you how to shore up CSRF protection in AngularJS, let’s dig into anti-JSON hijacking.
JSON hijacking: Say hello to our little friend
The second built-in security feature of AngularJS is the anti-JSON hijacking mechanism. Fortunately for us, the browser manufacturers have fixed this security issue in modern browsers.
We have attempted to replicate this kind of attack on a variety of current browsers without being able to hijack the JSON and exploiting the browser. In July 27, 2016, we tested the latest version of each of the following browsers:
None of them allowed us to exploit JSON hijacking. In fact, only FireFox 2.0 and lower were.
If, for some reason, you need to support very old browser versions and/or are using a less popular browser that you know is vulnerable to JSON hijacking, continue reading. We’ll show you how to avoid this kind of attack.
The power of the prefix
The crux of the built-in AngularJS anti-JSON hijacking mechanism is that the server prefixes the following characters to every JSON sent back to the AngularJS client:
When this JSON string is received by the AngularJS client, the $http or $resource service will automatically strip those prefixed characters from the JSON before processing the JSON.
Similar to the CSRF protection in AngularJS, nothing needs to be changed on the client. However, the server must explicitly prefix these characters to the JSON before sending the response to the client. The following code shows one way this can be done using Node.js:
That’s all that needs to be done. The AngularJS framework on the client side will take care of the rest.
It is important to note that this “solution” (i.e., prefixing special characters to the beginning of the JSON string) is really a hacky solution. The best solution, of course, is to simply support only the newer browsers when using JSON endpoints. However, if there is a business justification to using JSON along with very old browser versions, then this workaround is the next best method to prevent JSON hijacking.