Issues Creating a whitelist for my content security headers

2,315 views
Skip to first unread message

Nathan Hanak

unread,
Jul 12, 2017, 6:10:41 AM7/12/17
to Play Framework
Hello all,

I'm brand new to web development, and have chosen to do my first project with Play 2.6, so apologies for any glaring mistakes I may be making, and thank you for your help and patience.

I'm attempting to load some content from external sources, specifically I'm trying to use some image icons to appear in various places with my text on my app from fontawesome.com (it loads through <script src="https://use.fontawesome.com/3aed4d9ed5.js"></script> located in my header) and also a font from fonts.googleapi.com as such:
<link href="https://fonts.googleapis.com/css?family=Ubuntu:400,500" rel="stylesheet">

I discovered that both were being blocked by the content security policy, and would not load with the rest of my site. So, after reading up on Play's documentation regarding configuring security headers that I needed to add them to the configuration policy. So, I added this to the application.conf file:

play.filters.headers.contentSecurityPolicy += "; font-src 'self' .fonts.googleapis.com.; script-src 'self' .fontawesome.com."

(as a note, I have also tried not starting it with a semicolon as well)
However, when I now try to load my application, I get this error:

 
Cannot concatenate object or list with a non-object-or-list, Quoted("default-src 'self'") and SimpleConfigList(["; font-src 'self' ...


So I'm not sure why, but it won't concatenate the lists. So, instead, I just try to rebuild it entirely, adding this instead to my application.conf file:

play.filters.headers.contentSecurityPolicy = "default-src 'self'; font-src 'self' .fonts.googleapis.com.; script-src 'self' .fontawesome.com."

This allows the page to load at least, but fails to load anything, this lights up my console:

Content Security Policy: Couldnt parse invalid host .fonts.googleapis.com.  (unknown)
Content Security Policy: Couldnt parse invalid host .fontawesome.com.  (unknown)
Content Security Policy: The pages settings blocked the loading of a resource at https://use.fontawesome.com/3aed4d9ed5.js (“script-src http://localhost:9000”).  (unknown)
Content Security Policy: The pages settings blocked the loading of a resource at https://fonts.googleapis.com/css?family=Ubuntu:400,500 (“default-src http://localhost:9000”).  (unknown)
Content Security Policy: The pages settings blocked the loading of a resource at self (“script-src http://localhost:9000”). Source: onclick attribute on BUTTON element.  localhost:9000

So it seems it's still blocking everything. Including even a button attribute I define on the page which just takes the app from the front page to its own login page.

However, I DID find something that works, kind of. I use allowed-action headers. So first, I need to enable them in my application.conf file:

play.filters.headers.allowActionSpecificHeaders = true

And then I need to add this specifically to every action to make it work:

def index() = Action {
implicit request: Request[AnyContent] => Ok(views.html.index()).withHeaders(SecurityHeadersFilter
.CONTENT_SECURITY_POLICY_HEADER -> " .fontawesome.com .fonts.googleapis.com")
}

And, after all that, everything does load fine. Though, it still says this in my console:
Content Security Policy: Couldn’t process unknown directive ‘.fontawesome.com’  (unknown)

This works, but I'm grossly violating the DRY principle here by adding this to every action. I would think creating the list as I first attempted would work better, as it would be nice to have just one place to add (or remove) something should I need to load more externals in the future. I'm wondering if I'm just getting my syntax wrong. Any suggestions? Thanks in advance!

Justin du coeur

unread,
Jul 12, 2017, 8:38:16 AM7/12/17
to play-fr...@googlegroups.com
Hmm.  I *suspect* that the += syntax isn't legal with contentSecurityPolicy -- it looks to me like it's a string, not a list, and I don't think you can concatenate config strings like that.  (+= usually connotes a List, not a String -- your first error message looks like it's complaining about you trying to add a String plus a List.)  In both the documentation and my working system, it uses = instead of +=, and spells everything out instead of trying to build on the default.

Note that I'm using CSP for largely the same purpose as you (FontAwesome among others); it works fine so long as I spell out *full* URLs, not just domains.  I *think* the problem in your second try is that ".fontawesome.com" isn't a legal CSP syntax.  My working system has it like this:

play {
  filters {
    headers {
      # Allow the CDNs to work:
      contentSecurityPolicy = "default-src 'self' https://maxcdn.bootstrapcdn.com https://ajax.googleapis.com https://use.fontawesome.com;"
    }
  }
}

That's a bit looser than the way you have it specified (and obviously includes a couple of other domains as well), but it works as intended, and might serve as a starting point...

--
You received this message because you are subscribed to the Google Groups "Play Framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/play-framework/c071af1f-da34-466d-8b58-6b7d4a7bc16b%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Greg Methvin

unread,
Jul 12, 2017, 5:05:32 PM7/12/17
to play-framework
On Wed, Jul 12, 2017 at 2:54 AM, Nathan Hanak <nathan...@gmail.com> wrote:
Hello all,

I'm brand new to web development, and have chosen to do my first project with Play 2.6, so apologies for any glaring mistakes I may be making, and thank you for your help and patience.

I'm attempting to load some content from external sources, specifically I'm trying to use some image icons to appear in various places with my text on my app from fontawesome.com (it loads through <script src="https://use.fontawesome.com/3aed4d9ed5.js"></script> located in my header) and also a font from fonts.googleapi.com as such:
<link href="https://fonts.googleapis.com/css?family=Ubuntu:400,500" rel="stylesheet">

I discovered that both were being blocked by the content security policy, and would not load with the rest of my site. So, after reading up on Play's documentation regarding configuring security headers that I needed to add them to the configuration policy. So, I added this to the application.conf file:

play.filters.headers.contentSecurityPolicy += "; font-src 'self' .fonts.googleapis.com.; script-src 'self' .fontawesome.com."

(as a note, I have also tried not starting it with a semicolon as well)
However, when I now try to load my application, I get this error:

 
Cannot concatenate object or list with a non-object-or-list, Quoted("default-src 'self'") and SimpleConfigList(["; font-src 'self' ...


+= is for lists. This config key has already been declared to be a string. Typesafe config does support string concatenation (https://github.com/typesafehub/config#concatenation) but I'm not sure it makes sense in this particular case, since you need to be sure the whole header is well-formed, and it's really not a big deal to override the whole value.
 

So I'm not sure why, but it won't concatenate the lists. So, instead, I just try to rebuild it entirely, adding this instead to my application.conf file:

play.filters.headers.contentSecurityPolicy = "default-src 'self'; font-src 'self' .fonts.googleapis.com.; script-src 'self' .fontawesome.com."

This allows the page to load at least, but fails to load anything, this lights up my console:

Content Security Policy: Couldnt parse invalid host .fonts.googleapis.com.  (unknown)
Content Security Policy: Couldnt parse invalid host .fontawesome.com.  (unknown)
Content Security Policy: The pages settings blocked the loading of a resource at https://use.fontawesome.com/3aed4d9ed5.js (“script-src http://localhost:9000”).  (unknown)
Content Security Policy: The pages settings blocked the loading of a resource at https://fonts.googleapis.com/css?family=Ubuntu:400,500 (“default-src http://localhost:9000”).  (unknown)
Content Security Policy: The pages settings blocked the loading of a resource at self (“script-src http://localhost:9000”). Source: onclick attribute on BUTTON element.  localhost:9000

So it seems it's still blocking everything. Including even a button attribute I define on the page which just takes the app from the front page to its own login page.


I believe the issue is that the dot at the end of the domain is allowed in CSP: https://www.w3.org/TR/CSP/#framework-directive-source-list
 
However, I DID find something that works, kind of. I use allowed-action headers. So first, I need to enable them in my application.conf file:

play.filters.headers.allowActionSpecificHeaders = true

And then I need to add this specifically to every action to make it work:

def index() = Action {
implicit request: Request[AnyContent] => Ok(views.html.index()).withHeaders(SecurityHeadersFilter
.CONTENT_SECURITY_POLICY_HEADER -> " .fontawesome.com .fonts.googleapis.com")
}

And, after all that, everything does load fine. Though, it still says this in my console:
Content Security Policy: Couldn’t process unknown directive ‘.fontawesome.com’  (unknown)

This works, but I'm grossly violating the DRY principle here by adding this to every action. I would think creating the list as I first attempted would work better, as it would be nice to have just one place to add (or remove) something should I need to load more externals in the future. I'm wondering if I'm just getting my syntax wrong. Any suggestions? Thanks in advance!

I don't think this works the way you're expecting it to. It's just causing the browser to ignore the CSP header since it fails to parse. You need a directive like script-src or font-src like you have above.

If you want to add something to your headers for every response, then a filter is a better way to do that, and that's exactly why the SecurityHeadersFilter exists. The contentSecurityPolicy in config is exactly what's set as the Content-Security-Policy header in the HTTP response, so the issue is almost certainly a malformed CSP header. Play is just adding the header exactly as set in configuration.
 

--
You received this message because you are subscribed to the Google Groups "Play Framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/play-framework/c071af1f-da34-466d-8b58-6b7d4a7bc16b%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Greg Methvin
Tech Lead - Play Framework

Chris Nappin

unread,
Jul 13, 2017, 3:32:06 AM7/13/17
to Play Framework
Hi Nathan,

  Just to echo the feedback already given, you should find your example works fine if you set the Play filter setting to the full policy string, and use fully qualified URLs, as follows:

play.filters.headers.contentSecurityPolicy = "default-src 'self'; font-src 'self' https://fonts.googleapis.com/; script-src 'self' https://use.fontawesome.com/;"

Using tools like the dev console in Chrome you can inspect the HTTP headers being returned in your response, and determine whether the browser is behaving as you expect. If Play is correctly returning the policy you expect but the browser has console errors then the issue is the browser doesn't like the syntax you used. This will depend upon which version of CSP you are using and which browser version being used.

Hope that helps,

  Chris

Will Sargent

unread,
Jul 14, 2017, 3:06:58 PM7/14/17
to play-fr...@googlegroups.com
If you want to append or prepend onto a string in HOCON, you can do it by using string value concatenation and substitution:

myvar = "foo" 
myvar = ${myvar} "bar" 


--
You received this message because you are subscribed to the Google Groups "Play Framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework+unsubscribe@googlegroups.com.

Adam Lane

unread,
Jul 14, 2017, 3:58:50 PM7/14/17
to Play Framework
Here is what I did to keep it clean looking and dynamic for cdn links on different env

default-src = "default-src 'self' " ${application.cdn}
font-src = "font-src fonts.gstatic.com "
img-src = "img-src 'self' data: " ${application.cdn}
script-src = "script-src 'self' " ${application.cdn}
style-src = "style-src 'self' " ${application.cdn}
play.filters.headers.contentSecurityPolicy = ${default-src} ; ${font-src} ; ${img-src} ; ${script-src} ; ${style-src} ; report-uri /cspReport;

You may also want to setup the reporting feature so you can be alerted on anything you missed when it goes live:

+ nocsrf
POST /cspReport controllers.ApplicationController.cspReport

def cspReport = Action.async(parse.tolerantJson) { implicit request =>
  // Must be tolerantJson because the type isn't json for some reason
LogOrMailIt(request.body.toString())

}


Reply all
Reply to author
Forward
0 new messages