JavaScript Security 101
Tags: javascript, security, beginners
Published: 2020-12-07 08:22:00

_This blog post was originally published in Tes Engineering blog [here](https://engineering.tes.com/post/javascript-security)._


I recently completed the [JavaScript Security: Best Practices course](https://www.pluralsight.com/courses/javascript-security-best-practices) by [Marcin Hoppe](https://twitter.com/marcin_hoppe) and wanted to share some key practical take aways I learnt on how to write more secure JavaScript.
As well as reading this blog, I'd also highly recommend completing the course. It's short and sweet and hands on!


## JavaScript Threat Environments
It's worth noting that there are two different threat environments: client-side JavaScript vs server-side JavaScript. For client-side JavaScript the browser operates on a low trust & highly restricted basis, necessarily so because it works with JavaScript from uncontrolled sources by virtue of users navigating the web.
In comparison for server-side JavaScript Node.js works on a high trust & privileged basis, because it's a controlled source (i.e. Engineering teams have written the code) and it doesn't change during runtime.
There's a more detailed summary of these differing threat environments in the [Roadmap for Node.js Security](https://nodesecroadmap.fyi/chapter-1/threats.html), and it's important to keep this difference in mind when writing JavaScript. 

The dynamic nature of JavaScript on the one hand makes it incredibly versatile, and on the other creates a number of security pitfalls. Here are three key pitfalls in JavaScript and how to avoid them.

## 1. Comparisons & conversions abuse

 **TLDR;**
JavaScript has a dynamic type system which can have some dangerous but avoidable consequences. Use the JavaScript [Strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) to help avoid pitfalls such as [loose comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness). 

_Some examples..._

#### NaN, Null & undefined
Automated conversions can lead unexpected code to be executed:
```javascript
console.log(typeof NaN) // number
console.log(typeof null) // object
console.log(typeof undefined) // undefined
```

For example, this `calculatingStuff` function relies on the input being a number. Without any validation to guard against the input being `NaN`, the function still runs because `NaN` is classed as a number. 
```javascript
const calculatingStuff = (num) => {
  return num * 3;
};

console.log(calculatingStuff(NaN)) // NaN
```
It's important to have guard clauses and error handling in place to avoid unexpected behaviour in automated conversions. For instance in this version of `calculatingStuffv2` we throw an error if the input is `NaN`.

```javascript
const calculatingStuffv2 = (num) => {
if (isNaN(num)) {
  return new Error('Not a number!')
}
  return num * 3;
};

console.log(calculatingStuffv2(NaN)) // Error: Not a number!
console.log(calculatingStuffv2(undefined)) // Error: Not a number!
console.log(calculatingStuffv2(null)) // 0
console.log(calculatingStuffv2(2)) // 6
```
The `isNaN()` also guards against undefined, but will not guard against `null`. As with everything in JavaScript, there are many ways you could write checks to guard against these `NaN`, `null` and `undefined`.
A more reliable approach to "catch 'em all" is to [check for truthiness](https://stackoverflow.com/questions/5515310/is-there-a-standard-function-to-check-for-null-undefined-or-blank-variables-in), as all of these values are falsy they will always return the error:

```javascript
const calculatingStuffv2 = (num) => {
if (!num) {
  return new Error('Not a number!')
}
  return num * 3;
};

console.log(calculatingStuffv2(NaN)) // Error: Not a number!
console.log(calculatingStuffv2(undefined)) // Error: Not a number!
console.log(calculatingStuffv2(null)) // // Error: Not a number!
console.log(calculatingStuffv2(2)) // 6
```

#### Loose comparison
Loose comparison is another way code could be unexpectedly executed:
```javascript
const num = 0;
const obj = new String('0');
const str = '0';

console.log(num == obj); // true
console.log(num == str); // true
console.log(obj == str); // true
```

Using the strict comparison `===` would rule out the possibility of unexpected side effects, because it always considers operands of different types to be different.

```javascript
const num = 0;
const obj = new String('0');
const str = '0';

console.log(num === obj); // false
console.log(num === str); // false
console.log(obj === str); // false
```

### 2. Injection attacks from dynamically executed code

**TLDR;**
Be sure to always validate data before using it in your application, and avoid passing strings as arguments to JavaScript functions which can dynamically execute code.

_Some examples..._
 
#### eval()
As described in the [mdn docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#Never_use_eval!) eval 'executes the code it's passed with the privileges of the caller'. 

This can become very dangerous if, for example, eval is passed an unvalidated user input with malicious code in it.

```javascript
eval('(' + '<script type='text/javascript'>some malicious code</script>' + '(');
```

#### Unsafe variants of browser APIs
Both [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout) & [setInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval) have an optional syntax where a string can be passed instead of a function.

```javascript
window.setTimeout('<script type='text/javascript'>some malicious code</script>', 2*1000);
``` 
Just like the `eval()` example this would lead to executing the malicious code at runtime. This can be avoided by always using the passing a function as the argument syntax.

## 3. Attacks from Prototype pollution

**TLDR;**
Every JavaScript object has a prototype chain which is mutable and can be changed at runtime. Guard against this by:
1. Freezing the prototype to prevent new properties being added or amended 
2. Create objects without a prototype
3. Prefer [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) over plain `{}` objects

_Some examples..._

Here's an example where the value of the `toString` function in the prototype is changed to execute the malicious script.

```javascript
let cutePuppy = {name: "Barny", breed: "Beagle"}
cutePuppy.__proto__.toString = ()=>{<script type='text/javascript'>some malicious code</script>}
```

A couple of approaches to mitigate this risk is to be careful when initiating new objects, to either create them removing the prototype, freeze the prototype or use [Map object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map).
```javascript
// remove
let cutePuppyNoPrototype = Object.create(null, {name: "Barny", breed: "Beagle"})

// freeze
const proto = cutePuppyNoPrototype.prototype;
Object.freeze(proto);

// Map
let puppyMap = new Map()
cutePuppyNoPrototype.set({name: "Barny", breed: "Beagle"})
```
Prototypal inheritance is an [underrated threat](https://portswigger.net/daily-swig/prototype-pollution-the-dangerous-and-underrated-vulnerability-impacting-javascript-applications) so it's definitely worth considering this to guard against JavaScript being exploited in a variety of ways.

## Tooling
Finally, beyond being aware of these pitfalls of JavaScript, there are a number of tools you could use to get early feedback during development. It's important to consider security concerns for both JavaScript that you have written, and third party JavaScript introduced through dependencies.

Here are a few highlights from some great Static code analysis (SAST) tools listed in [Awesome Node.js security](https://github.com/lirantal/awesome-nodejs-security) & [Guidesmiths Cybersecurity handbook](https://guidesmiths.github.io/cybersecurity-handbook/tooling).

#### In your code

* Always [`use strict` development mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) when writing JavaScript
* Use a linter, for example [eslint](https://eslint.org/) can configured to guard against some of the pitfalls we explored above by editing the rules:
```json
"rules": {
  "no-eval": "error",
  "no-implied-eval": "error",
  "no-new-func": "error",
}
```
* Use a security plugin in your text editor, for example [eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security) 

#### In your JavaScript dependencies code

* Use [npm audit](https://docs.npmjs.com/cli/v6/commands/npm-audit/) to check for known vulnerabilities
* Use [lockfile lint](https://github.com/lirantal/lockfile-lint) to check changes in the `package-lock.json` which is typically not reviewed
* Use [trust but verify](https://github.com/verifynpm/tbv) to compare an npm package with its source repository to ensure the resulting artifact is the same