XSS attack

XSS attacks usually refer to injecting malicious instruction code into the web page by ingenious methods by exploiting the loopholes left by the web page development, so that the user loads and executes the code.

Common ways to prevent XSS attacks

  1. In the website design stage, you need to pay attention to filtering user input content and coding it to a certain extent
  2. Use dangerouslySetInnerHTML with caution in Next.js
  3. Restrict script execution with appropriate CSP

For the third point, Nonce can be used to maximize the difficulty of malicious attacks.

What is Nonce

Usually, CSP (Content Security Policy) ensures the security of various types of content through the Host. Therefore, CSP can only "one size fits all" for inline scripts, either execute or block. Based on this, CSP proposed the concept of Nonce:

Nonce is a random string that changes with the page. The script can only be executed if the script element contains a nonce attribute value that matches the CSP.

for example:

If the server sends back the following HTTP Header when responding to the URL/posts/1 request:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-63NFVc4+Hnh1sfCsAqeCND/QIvClufnDvVft5xQ6xRw='; // ...

Then the following script can be executed:

<script async="" src="https://www.googletagmanager.com/gtm.js" nonce="63NFVc4+Hnh1sfCsAqeCND/QIvClufnDvVft5xQ6xRw="></script>

The following scripts will be blocked from execution by the browser:

<script async="" src="https://www.googletagmanager.com/gtm.js" nonce="Some other strings"></script>
// 或
<script async="" src="https://www.googletagmanager.com/gtm.js"></script>

Next.js add Nonce

Since Next.js builds each page individually, we can modify _document.tsx to randomly generate nonce without affecting page response time. Just create _document.tsx under /pages/ and paste the following script:

import Document, {Head, Html, Main, NextScript} from 'next/document'
import crypto from 'crypto';
import {v4} from 'uuid';
import Script from "next/script";


/**
 * Generate Content Security Policy for the app.
 * Uses randomly generated nonce (base64)
 *
 * @returns {csp: string, nonce: string} - CSP string in first array element, nonce in the second array element.
 */
const generateCsp = (): {csp: string, nonce: string} => {
    const production = process.env.NODE_ENV === 'production';

    // generate random nonce converted to base64. Must be different on every HTTP page load
    const hash = crypto.createHash('sha256');
    hash.update(v4());
    const nonce = hash.digest('base64');

    // 这里根据需求修改CSP模板
    // 注意:Next.js在dev阶段需要使用'unsafe-eval'脚本进行热刷新,因此仅需在dev阶段包含'unsafe-eval'。
    const csp = `
default-src 'self';
script-src 'self' ${production ? '' : "'unsafe-eval'"} 'nonce-${nonce}';
frame-src 'self';
base-uri 'self';
block-all-mixed-content;
font-src 'self' https: data:;
img-src * 'self' data: https;
object-src 'none';
script-src-attr 'none';
style-src 'self' https: 'unsafe-inline';
upgrade-insecure-requests;
`.replace(/\n/g, " ");

    return {csp, nonce};
};

type MyDocumentProps = {
    nonce: string,
}

export default class MyDocument extends Document<MyDocumentProps> {
    static async getInitialProps(ctx) {
        const initialProps = await Document.getInitialProps(ctx)
        const {csp, nonce} = generateCsp();
        const res = ctx?.res
        if (res != null && !res.headersSent) {
            res.setHeader('Content-Security-Policy', csp)
        }
        return {
            ...initialProps,
            nonce,
        }
    }

    render() {
        const {nonce} = this.props;
        // 应当在每一处Script中加入nonce属性。
        return (
            <Html>
                <Head nonce={nonce}>
{/*                    这里根据个人需要加入第三方脚本(Google Analytics)*/}
{/*                    <Script*/}
{/*                        strategy="afterInteractive"*/}
{/*                        type="text/javascript"*/}
{/*                        onError={(i) => {*/}
{/*                            console.error("gtm error", i)*/}
{/*                        }}*/}
{/*                        nonce={nonce}*/}
{/*                        />*/}
                </Head>
                <body>
                <Main/>
                {/* Next.js脚本也需要加入nonce属性 */}
                <NextScript nonce={nonce}/>
                </body>
            </Html>
        )
    }
}

THIS SERVICE MAY CONTAIN TRANSLATIONS POWERED BY GOOGLE. GOOGLE DISCLAIMS ALL WARRANTIES RELATED TO THE TRANSLATIONS, EXPRESS OR IMPLIED, INCLUDING ANY WARRANTIES OF ACCURACY, RELIABILITY, AND ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.

Comments

Create Comment

Your email is not known to other users and is only used to generate unique random nicknames.