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."
|
Stephen Teilhet, Lewis Ardern, & David Johansson
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.
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?
The simplified, five-step process goes something like this:
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.
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:
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.
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.
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.
Remember: you cannot rely on the HttpOnly cookie protection when using AngularJS CSRF protection. AngularJS relies on the ability to read the cookie information directly from JavaScript, applying HttpOnly protection to the cookie renders AngularJS unable to read the cookie information and, in turn, pass the correct CSRF token back to the server.
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.
Now that we’ve shown you how to shore up CSRF protection in AngularJS, let’s dig into anti-JSON hijacking.
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 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.