nuxt-modules / security

🛡 Automatically configure your app to follow OWASP security patterns and principles by using HTTP Headers and Middleware
https://nuxt-security.vercel.app/
MIT License
826 stars 60 forks source link

xFrameOptions 'DENY' allows to show site inside iframe #540

Closed artrayd closed 1 month ago

artrayd commented 1 month ago

Version

nuxt-security: 2.0.0 nuxt: 3.11.2

Reproduction Link

https://jsfiddle.net/k6a081rf/

Steps to reproduce

  1. Set 'xFrameOptions' to 'DENY' or 'SAMEORIGIN'
  2. Load it from iframe

What is Expected?

  1. Iframe should show message "Refused to connect"

What is actually happening?

  1. Site is displayed inside iframe, making it vulnerable to clickjacking attacks.
vejja commented 1 month ago

Hi @artrayd Can you give us more information please? From what I can see, your site is not using Nuxt Security.

artrayd commented 1 month ago

I do have nuxt-security at nuxt.config

modules: [
    'nuxt-security',
]

in my modules

I also have disabled some rules:

    headers: {
      crossOriginOpenerPolicy: false,
      crossOriginEmbedderPolicy: false,
      contentSecurityPolicy: false,
      xFrameOptions: 'DENY',
    },
    rateLimiter: false,
vejja commented 1 month ago

Can you please give me:

I can't see Nuxt Security there from the outside

artrayd commented 1 month ago

I can see the correct header on localhost in dev mode, but after deployment, it disappears.

Localhost: hedears-localhost

After deployment: headers-deployment

Here is my full nuxt.config.js

export default defineNuxtConfig({
  devtools: { enabled: false },
  app: {
  },
  vite: {
    plugins: [svgLoader({})],
    vue: {
      script: {
        defineModel: true,
      },
    },
  },
  modules: [
    'nuxt-security',
    '@nuxtjs/tailwindcss',
    '@nuxtjs/i18n',
    'nuxt-vuefire',
    '@pinia/nuxt',
    '@pinia-plugin-persistedstate/nuxt',
    '@vueuse/nuxt',
    [
      '@nuxtjs/google-fonts',
      {
        families: {
          Courgette: { wght: [400, 700] },
          'Roboto Slab': { wght: [400, 700] },
        },
        display: 'swap',
        preconnect: true,
      },
    ],
    '@nuxtjs/robots',
    'nuxt-simple-sitemap',
    '@nuxt/image',
    '@vite-pwa/nuxt',
    'nuxt-gtag',
    'nuxt-delay-hydration',
  ],
  delayHydration: {
    mode: 'init',
  },

  css: ['/styles/main.css'],
  i18n: {
    lazy: true,
    compilation: {
      strictMessage: false,
    },
    langDir: 'locales',
    defaultLocale: 'en',
    baseUrl: process.env.APP_URL,
    detectBrowserLanguage: { redirectOn: 'root', useCookie: true, fallbackLocale: 'en' },
    locales,
    pages: {
      terms: false,
      privacy: false,
      cookies: false,
      disclaimer: false,
      dmca: false,
    },
  },
  security: {
    headers: {
      crossOriginOpenerPolicy: false,
      crossOriginEmbedderPolicy: false,
      contentSecurityPolicy: false,
      xFrameOptions: 'DENY',
    },
    rateLimiter: false,
  },
  runtimeConfig: {
    public: {

    },
  },
  vuefire: {
    auth: {
      popupRedirectResolver: false,
    },
  },
  nitro: {
    // Compress to brotli, increases page speed
    compressPublicAssets: true,
    prerender: {
      crawlLinks: true,
      routes: allUrls,
    },
    firebase: {
      gen: 2,
      nodeVersion: '20',
    },
  },
  site: {
    url: process.env.APP_URL,
  },
  robots: {
    rules: {
      UserAgent: '*',
      Disallow: '',
      Sitemap: `${process.env.APP_URL || 'https://domain.name'}/sitemap_index.xml`,
    },
  },
  experimental: {
    payloadExtraction: false,
  },
  pwa: {
    start_url: '.',
    registerType: 'autoUpdate',
    devOptions: {
      enabled: process.env.APP_ENV === 'development',
    },
    injectRegister: 'auto',
    workbox: {
      importScripts: ['share-sw.js', 'offline-sw.js'],
      globPatterns: ['**/*.{js,css}'],
      navigateFallback: null,
    },

  gtag: {
    enabled: false,
  },
  hooks: {
    // prevent some prefetch behaviour
    'build:manifest': (manifest) => {
      for (const key in manifest) {
        manifest[key].dynamicImports = []

        const file = manifest[key]
        if (file.assets) {
          file.assets = file.assets.filter((assetName) => !/.+\.(gif|jpe?g|png|svg)$/.test(assetName))
        }
      }
    },
    'pages:extend': (pages) => {
      pages.push(...routesRedirects)
    },
  },
})
vejja commented 1 month ago

how do you deploy?

artrayd commented 1 month ago

@vejja I deploy to Firebase Hosting via GitHub Actions. Each time I push to GitHub, it automatically deploys.

vejja commented 1 month ago

OK, thanks I think this has nothing to do with us but I'd like to make sure of it. Would you mind doing the following 2 tests:

  1. remove the nitro.prerender entry in nuxt.config.ts, then test in localhost dev mode (can you see the headers?) then deploy to Firebase (can you see the headers?)

  2. in your nuxt.config.ts, add a new section:

    routeRules: {
    '/**': {
    headers: { foo: 'bar' }
    }
    }

    Can you see the foo: bar header in localhost dev? In production ?

artrayd commented 1 month ago

@vejja, same issue also with this config. It seems that after deployment, all headers are ignored. I’ve tested on both Firebase Hosting and Netlify

vejja commented 1 month ago

Ok so it means that Nitro’s route rules are not respected by Firebase upon deployment. It is probably a Nitro problem, but I am still surprised that the headers are not generated if you remove the prerender option, so it could also be something with the way Firebase is configured.

In any case it is not a Nuxt Security problem so I will close this issue here.

I think you should open an issue in the Nitro repo, it will be a better place for this than the Nuxt repo.

@danielroe copying you here as I think the issue might be related to the Firebase preset for Nitro.

vejja commented 1 month ago

@vejja, same issue also with this config. It seems that after deployment, all headers are ignored. I’ve tested on both Firebase Hosting and Netlify

Update: sorry I missed the end where you are saying that you don’t have headers on Netlify either. This is completely abnormal. We deploy regularly with Netlify without any issue. There is something unconventional happening here

vejja commented 1 month ago

Update again: try to remove your hooks section, maybe this is messing with something internal to Nuxt.

artrayd commented 1 month ago

After removing hooks section, nothing changed also

vejja commented 1 month ago

Are you sure that you don’t have a specific firebaserc config? Or something in your GitHub actions env ? What if you deploy from your local Firebase CLI ?

artrayd commented 1 month ago

I do have firebase config and github actions, here is how that looks like:

Firebase config

{
  "projects": {
    "prod": "project-name",
    "default": "project-name-test",
    "staging": "project-name-test"
  },
  "targets": {
    "project-name-test": {
      "hosting": {
        "staging": [
          "project-name-test"
        ],
        "production": [
          "project-name-test"
        ]
      }
    },
    "surprising-gift": {
      "hosting": {
        "staging": [
          "project-name"
        ],
        "production": [
          "project-name"
        ]
      }
    }
  },
  "etags": {},
  "dataconnectEmulatorConfig": {}
}

Actions

name: Firebase Hosting Deployment

on:
  push:
    branches:
      - main
      - staging

permissions:
  checks: write
  contents: read
  pull-requests: write

jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '20'
      - name: Install dependencies
        run: yarn install
      - name: Export environment variables
        run: |
            echo "APP_URL=${{ vars.APP_URL }}" >> $GITHUB_ENV
            echo "APP_ENV=${{ vars.APP_ENV }}" >> $GITHUB_ENV
            echo "IP_API_KEY=${{ vars.IP_API_KEY }}" >> $GITHUB_ENV
            echo "NUXT_ENV_FIREBASE_API_KEY=${{ vars.NUXT_ENV_FIREBASE_API_KEY }}" >> $GITHUB_ENV
            echo "NUXT_ENV_FIREBASE_AUTH_DOMAIN=${{ vars.NUXT_ENV_FIREBASE_AUTH_DOMAIN }}" >> $GITHUB_ENV
            echo "NUXT_ENV_FIREBASE_DATABASE_URL=${{ vars.NUXT_ENV_FIREBASE_DATABASE_URL }}" >> $GITHUB_ENV
            echo "NUXT_ENV_FIREBASE_PROJECT_ID=${{ vars.NUXT_ENV_FIREBASE_PROJECT_ID }}" >> $GITHUB_ENV
            echo "NUXT_ENV_FIREBASE_STORAGE_BUCKET=${{ vars.NUXT_ENV_FIREBASE_STORAGE_BUCKET }}" >> $GITHUB_ENV
            echo "NUXT_ENV_FIREBASE_MESSAGING_SENDER_ID=${{ vars.NUXT_ENV_FIREBASE_MESSAGING_SENDER_ID }}" >> $GITHUB_ENV
            echo "NUXT_ENV_FIREBASE_APP_ID=${{ vars.NUXT_ENV_FIREBASE_APP_ID }}" >> $GITHUB_ENV
            echo "NUXT_ENV_FIREBASE_MEASUREMENT_ID=${{ vars.NUXT_ENV_FIREBASE_MEASUREMENT_ID }}" >> $GITHUB_ENV
            echo "NUXT_ENV_FIREBASE_FCM_KEY=${{ vars.NUXT_ENV_FIREBASE_FCM_KEY }}" >> $GITHUB_ENV
            echo "CREATE_APPCHECK_TOKEN_URL=${{ vars.CREATE_APPCHECK_TOKEN_URL }}" >> $GITHUB_ENV
      - name: Build project
        env:
          NODE_OPTIONS: "--max_old_space_size=4096"
        run: yarn generate

      - name: Deploy to Firebase Hosting (Staging)
        if: github.ref == 'refs/heads/staging'
        uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_NUXT_PLAYGROUND_TEST }}' 
          projectId: nuxt-playground-test
          channelId: live

      - name: Deploy to Firebase Hosting (Production)
        if: github.ref == 'refs/heads/main'
        uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT }}'
          projectId: surprising-gift
          channelId: live
vejja commented 1 month ago

OK so you are doing yarn generate, which means not using SSR and not deploying on Firebase functions, but instead only deploying static assets on Firebase hosting CDN. Is there a specific reason for this?

artrayd commented 1 month ago

@vejja I was under the impression that I was using SSR. It seems I need to investigate how to enable SSR rendering on Firebase. Thank you! By the way, I noticed that it’s actually working on Netlify.

vejja commented 1 month ago

@vejja I was under the impression that I was using SSR. It seems I need to investigate how to enable SSR rendering on Firebase. Thank you! By the way, I noticed that it’s actually working on Netlify.

SSR = yarn build instead of generate But look at the nitro guide link, you will have all the details

artrayd commented 3 weeks ago

@vejja in the just added these rules with firebase.json, noticed that without SSR my web app has even better performance score

vejja commented 3 weeks ago

@vejja in the just added these rules with firebase.json, noticed that without SSR my web app has even better performance score

which rules?