SBN

JavaScript security best practices for securing your applications

JavaScript, like other programming languages, are not without security challenges. These JavaScript security best practices will help you build more-secure code.

JavaScript is one of the most popular programming languages, largely because it’s an easy language for beginners. It’s easy to set up, it has an active and vast community, and users can create web, mobile, and desktop applications using only JavaScript.

But as with any programming language, bad actors try to find vulnerabilities to exploit within JavaScript applications. Common vulnerabilities include cross-site scripting, sensitive data disclosure, broken access control, session hijacking, CSRF and man-in-the-middle attacks. This blog post presents JavaScript security best practices for securing your applications.

Browse the JavaScript Security course

1. Write quality code

Programming languages are all different, and knowing the ins and outs is critical to writing quality code. The U.S. Department of Homeland Security reported that “up to 90%of computer security incidents are traceable to vulnerabilities in software that were exploited by an attacker,” so it’s clear that developers need to improve the quality of their code to reduce the bugs that lead to security breaches. Some tips that might help you reduce bugs include

  • Learn important concepts of the JavaScript language. Familiarity with global context, scope declarations, loose equality operators, strict equality operators, hoisting, callbacks, etc. are vital to quality code.
  • Avoid functions that evaluate strings as code. JavaScript functions such as eval, Function, setTimeout, and setInterval are not recommended since they could lead to cross-site scripting (XSS) attacks if used together with untrusted data.
  • Use a linter to find issues early. A linter is a tool that analyses source code for typos, logic errors, and code smells. In short, it helps you to improve the code. I recommend the ESLint linter since it is extendable and easy to start using.
  • Use static application security testing (SAST) tools to detect quality and security issues. I recommend a SAST tool like Synopsys Coverity®, which provides deeper support for security issues and compliance standards. You can also use Rapid Scan Static, a feature of Synopsys Code Sight™ SE, which allows teams to perform real-time IDE-based AppSec testing without breaking developer workflows. The Code Sight plugin, available for VS Code and IntelliJ, enables developers to confirm fixes as they code to avoid work downstream.
  • Convert silent errors into evident errors. Strict mode was introduced in ECMAScript 5 as an optional feature. Use this feature to convert silent errors into evident errors. For a detailed list of changes that happen when using strict mode, see this page.
  • Write tests to detect defects in your thinking. Write tests to corroborate that your thinking, your code, and the expectations are all aligned. This can also serve as a documentation tool.

2. Evaluate the need of third-party libraries

Code reuse is seen as a good practice, and JavaScript developers have pushed this idea to the extreme, creating packages even for the simplest tasks. But reusing packages in a noncontrolled way exposes JavaScript applications to issues and security threats. Consider treating dependencies as code needed for the project to run. The more that is needed, the more points of failures there can be.

The following story illustrates the point. On March 22, 2016, a commonly used package implementing a basic left-pad string function (with only 12 lines of code) was deleted. The dependency chain reaction of deleting this seemingly innocent and simple package broke a big chunk of the web development ecosystem including React, Babel, and other high-profile packages.

The moral to this story is that a project is as weak as its weakest dependency. By using well-known secure libraries and frameworks, you are protecting yourself. When using less-known third-party packages, inspect who has developed it; whether the package is maintained, inactive, and well-tested; and if it has unpatched vulnerabilities. And make sure you’re installing the right package—typosquatting attacks, where malicious packages with similar names to well-known packages make their way into real applications, are common.

You should also use software composition analysis (SCA) tools to help you to detect open source license violations, vulnerabilities, and out-of-date dependencies in open source software. Black Duck® is a software composition analysis tool that helps you with detecting these issues.

3. Do not trust user input

Most web applications allow users to insert data through text input. After the data is inserted, it is reflected somewhere in the web application. But accepting and displaying inputs from users opens the door to cross-site scripting attacks in which cybercriminals use special characters to trick browsers into interpreting text as HTML markup or JavaScript code.

Output encoding transforms potentially dangerous characters into a safe form. The encoding mechanism to use depends on where the untrusted input data is placed. Some of the encoding that the Open Web Application Security Project (OWASP) recognizes are HTML entity encoding, HTML attribute encoding, URL encoding, JavaScript string encoding, and CSS Hex. Since the type of encoding to use depends on where the input data is placed, the best option is to leave these contextual encodings to the framework you are using. If you’re not using a framework, OWASP recommends using a security-focused encoding library to make sure that encodings are implemented properly.

When a web application needs to accept HTML inserted by the user, sanitize the input before displaying it on a page or sending it to other systems. HTML sanitization involves validating the input and cleaning up unexpected characters. The outcome should be a safe HTML version of the HTML input. An excellent tool to sanitize HTML is DOMPurify.

To reduce cross-site scripting attacks, sanitize inputs in addition to applying output encoding.

4. Protect yourself against JSON injection

JSON is a commonly used syntax for exchanging information between applications. It is simple; compact; easy to learn, read, and understand; and has a hierarchical structure.

An injection attack is when an attacker supplies untrusted input, without validation or sanitization, to a program or application. A JSON injection attack can impact the server side or client side. For example, when code on the server side builds a JSON object from a string, and the string is built by concatenating some inputs provided by the user. Simple string concatenation, without validation or sanitization, opens the door for exfiltration of sensitive information or misbehavior.

On the client side, this type of attack can lead to cross-site scripting if the concatenated string runs inside functions that evaluate code such as eval, Function, or setTimeout, among others. For example

```
// location: http://my-website.com/view?username=ENTER_USERNAME
const params = new URLSearchParams(document.location.search);
 
// Get input from search param. Input is not validated or sanitized 
const usernameFromQueryParams=params.get('username');
 
// Create JSON object string from user input
const bannerJSONAsString = `{ "greetings": "Hello ${usernameFromQueryParams}"}`
 
// Use of function that evaluate string as code
const result = eval("(" + bannerJSONAsString + ")");
document.getElementById("#banner").innerText = result. greetings;

In this example, an attacker could alert user cookies by injecting

`test"});alert(document.cookie);({"a":"b`

To defend against this attack vector, validate and sanitize untrusted input, avoid using functions that evaluate strings as code, use the JSON.parse() function instead of eval() to parse JSON strings, and set content security policy to restrict the use of functions that evaluate strings as code.

5. Protect your cookies

HTTP cookies are used to preserve information such as authentication or session tokens, user preferences, or anything that the server should remember between requests. A HTTP cookie is a small string of data that the web server sends to the browser using the Set-Cookie HTTP header in the response. After this, the browser will automatically send the cookie back on almost every request (to the same domain) using the Cookie HTTP header.

If you do not set any flags, the cookie content is accessible programmatically using document.cookie. This is not always desirable. If an attacker is able to inject JavaScript within a web application, the script could read the content of document.cookie, and that could enable the bad actor to access any sensitive information in the cookie.

There are a variety of ways to protect cookies. If the cookie is used only by the webserver, you can use the httpOnly flag to restrict programmatical access to the cookie’s content.

HTTP does not encrypt messages, so man-in-the-middle attacks could succeed, and messages could be intercepted. If the cookie holds sensitive information, restrict the browser from sending it over unencrypted HTTP connections. Use the secure flag to instruct the browser to send cookies only through HTTPS, a protocol extension of HTTP. If you do not use this flag, the browser will send the cookie using both secure (https://my-secure-site.com) and insecure (http://my-secure-site.com) connections to a site.

In the case of a cross-site request forgery attacks, attackers can succeed because web applications can’t differentiate between valid requests and forged requests. For example, say you have a form for updating a user’s password, and the website uses cookies for handling sessions. An attacker could create a page that automatically sends a request to update your password. If cookies aren’t protected and you have a valid session cookie, when you visit this site, a request to update your password is sent from the site. Since your session cookie is valid, the browser will automatically include the cookie in the request. With this information, the webserver receives a request with a valid session cookie and updates the password. Thus, an attacker can reset your password to the value of their choice. To protect your cookies against cross-site request forgery attacks, use the cookie flag samesite=strict to ensure that the cookie is not sent when the request comes from another domain than the site that sets the cookie.

6. Defend against prototype pollution

JavaScript is a prototype-based language. When an object is created, it inherits all properties and methods following the so-called prototype chain. As it is a chain, prototypes have references to other prototypes. The chain is followed until reaching null. The Object prototype sits just below null in this prototype chain. Almost all objects in JavaScript are instances of Object.

When someone targets Object and alters the methods that most objects in JavaScript inherit through the prototype chain, this is called prototype pollution attack. It can happen in the server side or client side, and its consequences can include remote code execution, cross-site scripting, and denial-of-service attacks.

On the client side, an attacker injects code that can modify the Object.prototype directly. On the server side, the application is if it recursively clones object properties or if it sets object properties through a given path. If we have an HTTP server accepting HTTP requests, an attacker could send an HTTP request payload that contains a JSON object that tries to exploit the _proto_, constructor, or prototype properties.

There are different ways to mitigate prototype pollution attacks.

  • Freeze `Object.prototype` to prevent the default prototype from getting polluted.
    ```
    // This should be executed only once
    Object.freeze(Object.prototype);
     
    clone(source, target);
    ```
  • Create prototypeless objects using Object.create(null). These types of objects do not have a prototype. As such, they do not inherit methods from Object.prototype.
    ```
    const target = Object.create(null);
    clone(source, target);
    ```
  • Use Map instead of Object.
  • Avoid unsafe recursive merge functions.

7. Secure browser communication between window objects

Often, web pages present information to users by opening other windows or iframes. Sometimes these windows and iframes require communication between each other. But enabling communication between pages from different subdomains by setting the same value for document.domain on both pages should be avoided as it weakens the security protections provided by the same-origin policy. Instead, the window.postMessage() method should be used.

The window.postMessage() method enables safe communication between window objects such as a page and a pop-up or with an iframe. It allows two windows to share messages without one having any direct control over the other one. Although it’s a safer method, you must be careful with how you use it. For instance, window.postMessage() accepts a targetOrigin parameter, but you should avoid using * as the targetOrigin and make sure that the URL you provide uses HTTPS. The window receiving the message should always verify the sender’s identity (by checking message origin), as well as validate the received message. Do not execute strings coming through the window.postMessage() method to run as code, and avoid listening on message events if you are not expecting them.

8. Secure communications between network devices

When a user enters a URL in their browser and hits the enter button, the result is a mixture of HTML, CSS, images, fonts, and some other bits on the user screen. All these pieces come to the browser because it downloads the content from other systems. Sometimes it can all happen on the very first request. Other times CSS, images, fonts, and JavaScript libraries are downloaded as subsequent requests.

Bad actors try to get into the communication channel to gain access to sensitive data, intercept communication, or modify data while it’s in transit. These attacks can be minimized by securing the communications using HTTPS instead of HTTP. When using HTTP, the transmitted data is not encrypted. This means that an attacker could potentially intercept the transmitted data. When using HTTPS, the communication is encrypted using TLS or SSL protocol, which uses digital certificates for verifying the identity of the server and encrypts the communication between network devices. Using SSL/TLS certificates ensures privacy and data integrity in the communication. Even if encrypted data is intercepted, it cannot be decrypted without access to the secret key. Use HTTPS for all requests to secure communications between network devices.

9. Securely load external resources

In web development, it is a common practice to offload static resources to content delivery networks (CDNs). CDNs offer faster load times, decreased infrastructure costs, and better site performance. But when websites use CDN-hosted static files, they trust the security of a third party. What if an attacker gained control of the CDN and injected malicious content? Without any security measures, a website consuming this malicious content would not be aware of it. A great example of this is the NBA.com website attack when one of the Amazon S3 buckets used for keeping static files was compromised. MalwareBytes announced this attack on June 4, 2019.

To mitigate this attack vector, browsers provide a subresource integrity check feature. This feature allows browsers to validate the integrity of a file by including a base64-encoded cryptographic hash as part of the element that references the external resource. The developer is responsible for creating the hash from a “known-good” version of the source code. When an external resource with the subresource integrity check is downloaded, a base64-encode hash is generated based on the received data. If this generated hash value differs from the one provided by the developer, the resource is not loaded.

Load external resources securely by using the subresource integrity check when including them in your code.

```
<script src="https://code.jquery.com/jquery-2.1.4.min.js"
integrity="sha384-R4/ztc4ZlRqWjqIuvf6RX5yb/v90qNGx6fS48N0tRxiGkqveZETq72KgDVJCp2TC"
crossorigin="anonymous"></script>
```

10. Set a content security policy

When browsers download HTML content, other content such as images from a CDN, audio, and video might be downloaded from other domains. The browser can’t determine whether any of these requests are unexpected.

A content security policy (CSP) is an extra security layer that can help mitigate cross-site scripting and other attacks. CSPs can limit the domains content is downloaded from as well as the protocols to use. They can also restrict inline scripts, inline event handler executions, and functions that can evaluate a string as code such as eval or Function.

Because CSP rules restrict content sources, you must ensure that the rules are not misconfigured. Misconfiguration could cause a website to not render properly due to blocking of downloading/executing assets. CSPs can also be deployed in report-only mode, so policies are not enforced but violations are reported to a specified URL.

CSPs have many policies, so you need to understand their implications, evaluate which policies should be used, enable them, and monitor report violations. For those interested in defense-in-depth against cross-site scripting, read the following article from Google.

Get started

With today’s ever-evolving threat landscape, managing vulnerabilities at the speed of development can be a difficult task. Adopting good coding practices can help your DevSecOps teams address vulnerabilities earlier in the software development life cycle (SDLC). In addition to JavaScript security best practices, Synopsys eLearning material provides deeper insight into application security topics.

application security best practices | Synopsys

*** This is a Security Bloggers Network syndicated blog from Application Security Blog authored by Alberto Fernández Reyes. Read the original post at: https://www.synopsys.com/blogs/software-security/javascript-security-best-practices/