background

The JS that my website executes is of course the JS that I write myself ah, difficult not still have security problem 🤔? But think about it, in many cases, the JS files of the website are loaded from the CDN service of the third party. If the CDN server is attacked and JS files are tampered with, it will bring security risks. How to ensure that these script files of our website are not modified?

In other cases, there may be some inline script in the HTML. These JS may be injected when the server renders the HTML template. What if the packet is tampered with during network transmission?

Pseudo code:

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <! -- inject-placeholader -->
    <div id="root"></div>
  </body>
</html>

Copy the code
router.get("/".async (ctx) => {
  const htmlTpl = await getHtmlTpl();
  const script = `window.__SOME_DATA__ = The ${JSON.stringify(__SOME_DATA__)}; `;
  ctx.type = "html";
  ctx.body = htmlTpl.replace("<! -- inject-placeholader -->".`<script>${script}</script>`);
});
Copy the code

Let’s take a look at how traditional software ensures that what users get is untampered with. For example, linuxkit/ LinuxKit, whose publishing page will attach Checksums:

These hash values are derived from the file content using a fixed algorithm, such as SHA256 above. This allows users who have obtained the file to perform a calculation using the same algorithm, and if the hash value matches the one indicated by the author, they can verify that the software has not been tampered with.

Going back to the front end, is there a similar security solution on the front end? The answer is yes.

1. Subresource Integrity (SRI)

We add the integrity attribute to the

<link href="//somecdn.com/foo.css" rel="stylesheet" integrity="sha256-t7Z7PgokIxRooJ8azMRTqZZIdgaQX6ViGg3pn3pxZZs= sha384-KJi9xVfT8JzG/tFq6Dpgw6URtNE3WK83VaQOWpfHsVAN6Az5+AjliGZuSiiWd4ah" crossorigin="anonymous">

<script src="//somecdn.com/bar.js" integrity="sha256-XXVAhVe8STxZbyQhPOwZpZmx3X9iHnnrBPHUN/4vooc= sha384-438vOegRAvOckkDAIIIL8+k0JhRCRfY7Q2QXLjgFOHQbhyFK/YwGIDJBxYCdaHjA" crossorigin="anonymous"></script>
Copy the code

Where SHA256 and SHA384 are hash algorithms, and the part following SHA256 – or SHA384 – is hash code. For cross-domain script requests, the script server needs to set the CORS response header. Allow cross-domain sites to Access his content access-Control-allow-Origin: *,

For scripts or styles set to SRI, the browser does the same hash algorithm for the content of the requested file and rejects execution if the Hash code does not match.

Webpack projects can be packaged with webpack-subresource-integrity to automatically add integrity and Anonymous, as shown in the following configuration:

2. Content-Security-Policy (CSP)

For some inline scripts, we can check by setting CSP. Such as:

router.get("/".async (ctx) => {
  const htmlTpl = await getHtmlTpl();
  const script = `window.__SOME_DATA__ = The ${JSON.stringify(__SOME_DATA__)}; `;
  const hash = require("crypto").createHash("sha256").update(script).digest("base64");
  ctx.set("Content-Security-Policy".`script-src 'self' 'sha256-${hash}'`);
  ctx.type = "html";
  ctx.body = htmlTpl.replace("<! -- inject-placeholader -->".`<script>${script}</script>`);
});
Copy the code

The CSP setting above ensures that inline-script will be executed only if the hash value matches the hash value given. For inline-script that does not match, an error will be thrown:

However, since the algorithm is fixed, the process of delivering hash code is also transmitted to the front end over the network (HTML content, response header). If someone can modify the response during network transmission, then he can do the same algorithm for the malicious code again. Then change the Hash code, too, and you’ll still end up with unsafe scripts. This requires us to ensure the safety of network transmission. On the one hand, we need to configure HTTPS for the website to avoid plaintext transmission, and on the other hand, we need to tell users not to use untrusted proxy when visiting the website.

supplement

By the way, another common way to ensure content reliability. Those of you who have connected with the wechat public number may have the impression that the wechat server will push the message to our server. How do we ensure that the message is sent by the wechat server, rather than forged by others? The idea is that both wechat and our own servers hold the same Token. The message sent from wechat contains a signature, which is generated by the content of the request and the Token. As long as we ensure that the wechat side and our own services hold the same Token and sign the same content with the same algorithm, it is ok. If the signatures obtained in the end are consistent, it means that the request is indeed sent to us by wechat. You can refer to the example OF Node.js connecting to wechat notification I wrote earlier.

This approach is different from the above point that both parties keep a Token unknown to the third party. As long as the Token is not disclosed, even if others know what your signature algorithm is, they cannot forge a signature. This approach doesn’t work on the front end, which can’t pre-bury a Token in all users’ systems without going over the network.

Refer to the link

Developer.mozilla.org/en-US/docs/…

Developer.mozilla.org/zh-CN/docs/…

Github.com/waysact/web…

Webpack.js.org/configurati…

Developer.mozilla.org/en-US/docs/…