Share Tweet Share





A lot of the modern web apps have moved from older JavaScript solutions like jQuery to newer technologies. People are often drawn towards using React.js, Vue & Angular thanks to the benefits of isomorphic (or universal) rendering and their popularity. They help you build a great UI, they use the latest stack that developers and they’re supported by a huge community that includes Facebook and Google. So, why not?

You can create an entire SPA by using a framework like Angular whereas React and Vue are more popular as a drop-in solutions. You can just use them anywhere or build the entire app out of it.

However, that won’t be our primary focus in this tutorial. Here, we’ll talk about Security. How secure is your JavaScript code? Have you ever thought about the security aspects of these components. We’ll be primarily looking at the security features from a React perspective, but you should be thinking about these issues regardless of the stack that you’re running.

An Example

This was an issue originally pointed out by Emelia Smith over on Medium. Isomorphic rendering, as your might already be familiar with, is the ability to render your single page application on the server-side and lots of people use this for SEO benefits.

Libraries like Redux even have documentation as to just how to provide this functionality. In their documentation they featured the following code snippet:

function renderFullPage(html, preloadedState) {

return `

<!doctype html>

< html >

< head >

< title > Redux Universal Example </ title >

</ head >

< body >

< div id= “root” > ${html} </ div >

< script >

// WARNING: See the following for security issues around embedding JSON in HTML:

// http://redux.js.org/recipes/ServerRendering.html#security-considerations

window .__PRELOADED_STATE__ = ${ JSON .stringify(preloadedState).replace(

/</g ,

‘u003c’

)}

</ script >

< script src= “/static/bundle.js” ></ script >

</ body >

</ html >

`

}

XSS Vulnerable example snippet from the Redux documentation on how to share state from the server with the client.

Now, you may look at that snippet and be unsure as to why there is a security issue.

Don’t worry; you’re not alone. The issue with that code snippet is in how we pass the Store state into the application. In the above code, we do a JSON.stringify call, and assign it to a global variable in a script tag. This is the vulnerability

When a web browser parses the HTML of the page, and it encounters that <script> tag, they will continue reading until they see </script> — which means if your redux store has a value like the following in it, then when you load the client, you’ll receive an alert “You have an XSS vulnerability!”.

{

user: {

username: “NodeSecurity” ,

bio: “this is the end</script><script>alert(‘You have an XSS vulnerability!’)</script>”

}

}

The browser won’t read until the last curly bracket, instead, it will finish the script tag after “this is the end

How can you prevent this XSS vulnerability?

The Open Web Application Security Project luckily has a great selection of resources about XSS prevention. To prevent this vulnerability, we need a few safety measures in place in our applications:

All user input should have HTML entities escaped. When you’re using React, it does prevent most XSS vulnerabilities, due to how it creates DOM Nodes and textual content.

When serializing state on the server to be sent to the client, you need to serialize in a way that escapes HTML entities. This is because you’re often no longer using React to create this string, hence not having the string automatically escaped.

Engineers over at Yahoo have luckily made the latter of these safety practices straightforward to implement through their Serialize JavaScript module. You can import this into your application for security reasons.

First, install the module

npm install –save serialize-javascript

Then we can change the snippet from earlier to look like the following; In particular notice the change where we use serialize instead of JSON.stringify when assigning the value to __PRELOADED_STATE__

The patched snippet, which removes the XSS vulnerability by using Yahoo’s Serialize JavaScript module.

As for receiving the data on the client-side, it works just the same as before, except that you now have HTML in the string escaped (they’ll look like: u003Cu002Fscriptu003E rather than as </script>)

Here is the list of other possible vulnerabilities that you might come across.

Cross-Site Scripting (XSS)

Cross-Site Scripting is a commonly used technique that allows running external JavaScript in the context of the attacked website. XSS allows getting access to full Web API. The simplest example of an XSS attack implies that a hacker finds a vulnerable input field on the page and creates a link that injects a snipper to another page. After the user opens the hyperlink, the hacker gets to decide what happens next.

XSS is a high-rated security vulnerability since the attacker can get access to LocalStorage, SessionStorage, or cookies. That’s why it’s recommended not to store any sensitive data in these storages.

To protect your users against XSS, make sure that you never inject unknown user input into the page. Remember to use the escape syntax for those parts of the HTML document that you’re putting untrusted data. Use HTML and JavaScript escapes before putting untrusted data into HTML and JavaScript data values. Besides that don’t forget to use CSS escape. It’s recommended to avoid using sources to get rid of JavaScript security issues.

You can even try to create a SafeURL component like this one below to ensure that you’re working with good URLs.

import React, { Component } from ‘react’

import ReactDOM from ‘react-dom’

import URL from ‘url-parse’

class SafeURL extends Component {

isSafe(dangerousURL, text) {

const url = URL(dangerousURL, {})

if (url.protocol === ‘javascript:’ ) return false

if (url.protocol === ” ) return false

return true

}

render() {

const dangerousURL = this .props.dangerousURL

const safeURL = this .isSafe(dangerousURL) ? dangerousURL : null

return < a href= {safeURL} > {this.props.text} </ a >

}

}

ReactDOM.render(

< SafeURL dangerousURL= ” javascript: alert(1)” text= “Click me!” /> ,

document.getElementById(‘root’)

)

Here, we’ve blacklisted elements that have a JavaScript protocol instead of HTTP/HTTPS. However, you can also create a whitelisted version of this component as follows:



import React, { Component } from ‘react’

import ReactDOM from ‘react-dom’

const URL = require ( ‘url-parse’ )

class SafeURL extends Component {

isSafe(dangerousURL, text) {

const url = URL(dangerousURL, {})

if (url.protocol === ‘http:’ ) return true

if (url.protocol === ‘https:’ ) return true

return false

}

render() {

const dangerousURL = this .props.dangerousURL

const safeURL = this .isSafe(dangerousURL) ? dangerousURL : null

return < a href= {safeURL} > {this.props.text} </ a >

}

}

ReactDOM.render(

< SafeURL dangerousURL= ” javascript: alert(1)” text= “Click me!” /> ,

document.getElementById(‘root’)

)

You can read more about avoiding XSS in React here.

Cross-Site Request Forgery (CSRF)

CSRF is an attack that exploits the mechanism of sending HTTP requests from the browser. If a user’s PC stores some cookies from a particular website, these cookies will be sent with the request, and it doesn’t matter who starts a given request. Thus, if you let things slide and don’t defend your web app against CSRF, a hacker can steal the accounts of your users.

A simple example of CSRF attack implies that a hacker finds an unprotected <form> element on the web page. Then, a custom URL that calls the action of the given type can be created. For example, it can update the email address of your user. The hacker can request a password reminder and take over the account.

To avoid such issues, you can add a CSRF token to the form. This token should be unique per session and generated by a secure random number generator. After you create this token, you can add it as a hidden input field in the form on your web page. The next step is to make your server reject the request action in case if the token isn’t validated.

In React’s case, this is particularly relevant when you’re doing SSR. So, in the backend, you can generate a CSRF token like this:

//server.js

import express from ‘express’ ;

import cookieParser from ‘cookie-parser’ ;

import csurf from ‘csurf’ ;

const app = express();

app.use(cookieParser());

app.use({

cookie: {

key: ‘_csrf-my-app’ ,

path: ‘/context-route’ ,

httpOnly: true ,

secure: process.env.NODE_ENV === ‘production’ ,

maxAge: 3600 // 1-hour

}

});

//other routes…

app.use( ‘/’ , universalLoader);

app.listen( 3000 );

We’ll then add the CSRF token to the store like this:

//universalLoader.js

import { createStore } from ‘redux’ ;

universalLoader = (req, res) => {

…

const store = createStore(

rootReducer,

Object .assign(initialState, { csrf: req.csrfToken() })

);

…

}

Next, we need to make sure we are passing that information on any XHR which POSTs data.

//actions.js

const saveData = async (dispatch, getState) => {

try {

const response = await fetch( ‘/api/save’ , {

method: ‘POST’ ,

credentials: ‘same-origin’ ,

headers: new Headers({

‘Content-Type’ : ‘application/json’ ,

‘Accept’ : ‘application/json’ ,

‘CSRF-Token’ : getState().csrf

}),

body: {}

});

if (response.ok) {

try {

return await response.json();

} catch (err) {}

}

} catch (err) {}

}

Other Popular Security Issues

Server-side JavaScript Injection

Server-side JavaScript Injection is one of the most widespread web app vulnerabilities on the web nowadays. It’s a pretty common thing when a developer accidentally introduces proneness into his web application by simple misconfiguration. For example, the eval function can be pretty open for attacks and can be exploited with ease.

<script>



eval ( ‘alert(“Your query string was ‘ + unescape ( document .location.search) + ‘”);’ );



</ script >

You should avoid the use of the function to decrease the risk of such vulnerabilities. It’s used mostly for speed benefits, but it can compile and execute any JavaScript code which significantly increases the risks.

If you use the concatenation of string of unsanctioned dynamic user input, be prepared to meet some unpleasant consequences. We’re talking about the high risk of vulnerabilities. Besides that, remember that if a user’s input is required for a server-side command, then it should be appropriately validated.

Vulnerable Components

There’s a high probability that the components you use are out of date or obsolete. This happens with projects that were started a lot earlier and hasn’t been updated in a while. If you’re going to use components with known vulnerabilities and if you’re not planning to update, you’re actually keeping your security doors open. If the security issues are public and your project is a few patches behind, there’s every reason to be concerned. Someone with good expertise in that framework will already know how to get in and steal what’s yours.

Choose Wisely Between JWT-based and Session-based Authentication

JSON Web Tokens (JWT) is a method for representing claims securely between two parties. It includes some data on the user side (e.g., name and email) along with the other two values: iat (issued at) and exp (expires at). These tokens are signed by a secret key, but you should not use any sensitive data in JWSs since payloads are not encrypted. Each token consists of three base64-encoded parts that describe the used algorithm, contain the actual payload, and the signature. This technique is usually used for authenticating in SPAs and can simplify how you authenticate against different APIs hosted on different subdomains.

You have to store JWTs somewhere. In most cases, they’re stored in local storage or session storage. Such an approach is potentially vulnerable to XSS attacks. Therefore, if we speak about security and authentication, you should prefer with session-based authentication. In such case, all cookies will be protected from JavaScript access by the HttpOnly flag. Besides that, cookies are much smaller which can be helpful in case of mobile applications with no access to broadband internet. JWTs can be used if they’re required only for a short amount of time. For example, to authenticate a site on a subdomain or download some files.