Baroshem / nuxt-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
816 stars 56 forks source link

Content-Security-Policy: The page’s settings blocked the loading of a resource at inline #218

Closed rahul37865 closed 1 year ago

rahul37865 commented 1 year ago

Recently I added Nuxt Security in a Project where i am fetching data from an API URL http://127.0.0.1:8000/api/blog/post/27 While displaying image it throws error Content-Security-Policy: The page’s settings blocked the loading of a resource at http://127.0.0.1:8000/api/blog/post_image/img12.jpg (“img-src”). Which i fixed using below settings

  security: {
    headers: {
      contentSecurityPolicy: {
        'img-src': ["'self'", 'data:', 'http://127.0.0.1:8000'],     // This line fixed it
      },
      strictTransportSecurity: 'max-age=0;'
    },
  },

and adding crossorigin="anonymous" in img tag <img crossorigin="anonymous" :src="postDetail.post_image" /> This fixed the issue but now i am implementing Nuxt Image Module and when i write <NuxtImg crossorigin="anonymous" :src="postDetail.post_image" /> It throws an error (only on Firefox, doesn't throw error on chrome and edge)

Content-Security-Policy: The page’s settings blocked the loading of a resource at inline (“script-src-attr”).
Source: this.setAttribute('data-error', 1)

which i fixed using below settings

security: {
    headers: {
      contentSecurityPolicy: {
        'img-src': ["'self'", 'data:', 'http://127.0.0.1:8000'],
        'script-src-attr': ["'unsafe-inline'"],        // Added this
      },
      strictTransportSecurity: 'max-age=0;'
    },
  },

My concern is, it is considered a security risk, as it can open the door to XSS attacks. It's generally recommended to avoid using 'unsafe-inline' whenever possible. Is there any other secure way to fix this error ?

Baroshem commented 1 year ago

Hey

Yes, the safer solution would be to use nonce like following

https://nuxt-security.vercel.app/security/headers#nonce-support

rahul37865 commented 1 year ago

I am not sure if i am using this in a correct way I changed setting as below

security: {
  headers: {
    contentSecurityPolicy: {
      'img-src': ["'self'", 'data:', 'http://127.0.0.1:8000'],
      'script-src-attr': [ "'self'", "'nonce-{{nonce}}'","'strict-dynamic'" ]
    },
    strictTransportSecurity: 'max-age=0;'
  },
},

Then i used useNonce() composable to get valid nonce then injected it as below

<script setup>
    const nonce = useNonce()
</script>
<template>
    <NuxtImg :nonce="nonce" crossorigin="anonymous" :src="postDetail.post_image" />
</template>

It doesn't resolve the error which i mentioned previously

Content-Security-Policy: The page’s settings blocked the loading of a resource at inline (“script-src-attr”).
Source: this.setAttribute('data-error', 1)

after making change it is also showing 3 warnings as below

Content-Security-Policy: Couldn’t parse invalid host 'nonce-{{nonce}}'
Content-Security-Policy: Ignoring “'self'” within script-src-attr: ‘strict-dynamic’ specified
Content-Security-Policy: Keyword ‘strict-dynamic’ within “script-src-attr” with no valid nonce or hash might block all scripts from loading
Baroshem commented 1 year ago

Hmm interesting.

@trijpstra-fourlights would you be up for helping with that? You have proven to be an expert in this area 😉

trijpstra-fourlights commented 1 year ago

Pfew, a couple of things going wrong here so I'll try to explain what I think is happening!


First, an explanation on nonce:

By design, the nonce attribute is unique for every request. The middleware will regenerate the nonce and inject it in the headers.

If you are talking to an API endpoint served by your nuxt instance (e.g. /api/image/xyz), that means that without additional configuration the nonce attribute on that request will differ from the originating page, resulting in a CORS violation.

This extra configuration is (sparsely) described int he documentation

relevant part (you would want the mode: check on the relevant API URIs):

image


However, looking at your OP, it seems that the API is actually served under a different URI (i.e. not by nuxt). That's why you need to add the specific URI to the img-src allowed list. The crossorigin='anonymous' makes sure that no cookies are sent when requesting this URI. Without this, the API would be required to set the nuxt URL as an allowed origin, otherwise you'll get CSP violations again.

So, assuming that the image is served by a different service, adding nonce makes no sense as the nonce value is not known by the API.

That leaves us only with the initial error which occurs when using NuxtImg

Content-Security-Policy: The page’s settings blocked the loading of a resource at inline (“script-src-attr”).
Source: this.setAttribute('data-error', 1)

Unfortunately, this is due to how NuxtImg works internally. They are setting attribute at runtime (i.e. inline) which requires you to explicitly allow that in your CSP.

Normally, your options for this are:

  1. adding unsafe-inline to script-scr-attr (which, as you already said, opens up a security hole)
  2. 'whitelisting' NuxtImg to be able to do this (with nonce-)

However, NuxtImg does not currently support receiving (and propagating) a nonce value. Thus you are (if you want to use NuxtImg and enforce a strict CSP) limited to option 1 with all its potential security downsides.

So, as far as I can tell, the fix for this is that someone implements nonce awareness in NuxtImg (i.e. NuxtImg understands the nonce prop and propagates it to the actual injected html tags).


Lastly, while it makes no sense to add the nonce (as explained above), the actual error your seeing in https://github.com/Baroshem/nuxt-security/issues/218#issuecomment-1736760193 is due to you not enabling nonce in the security settings. If you would have added nonce: true to the security config the error would've disappeared (but it would still not have worked due to NuxtImage not understanding a nonce property.


edit (1): fix formatting edit (2): removed hash propagation as a possible solution as it's not applicable in this case because it requires computing a hash of the offending resource and whitelisting that explicitly in your CSP config

rahul37865 commented 1 year ago

Thanks @trijpstra-fourlights for this comprehensive explanation I raised the issue on Nuxt/Image I hope they will come up with a solution.

trijpstra-fourlights commented 1 year ago

No problem.

I've taken a stab at the nonce implementation for @nuxt/image and I think it works. You could try to use it directly, the PR branch is: https://github.com/trijpstra-fourlights/nuxt-image/tree/feat/add-nonce-support-rebased

For reference, this is the nuxt-security config I tested it with:

  security: {
    nonce: true,
    headers: {
      contentSecurityPolicy: {
        'img-src': ["'self'", 'data:', 'https:'],
        'style-src': ["'self'", "'nonce-{{nonce}}'"],
        'script-src': [
          "'self'", // backwards compatibility for older browsers that don't support strict-dynamic
          "'nonce-{{nonce}}'",
          "'strict-dynamic'"
        ],
        'script-src-attr': ["'self'", "'nonce-{{nonce}}'", "'strict-dynamic'"]
      }
    }
  }
<template>
  <NuxtImg src="https://localhost:8000/api/image/xyz" :nonce="nonce" />
</template>

<script lang="ts" setup>
const nonce = useNonce()
</script>
Baroshem commented 1 year ago

Great man @trijpstra-fourlights ! 🚀

Thank you so much. I am looking forward to the development of this issue / feature request :)

Baroshem commented 1 year ago

I have added the documentation about it https://github.com/Baroshem/nuxt-security/pull/212/commits/2f12bd61fb50f9f91a43ed8ed13453492598c745. Once it will be merged to NuxtImage I will deploy Nuxt Security docs with this instructions :)