Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial feedback on version "22 July 2016" #1

Closed
ptoomey3 opened this issue Jul 22, 2016 · 13 comments
Closed

Initial feedback on version "22 July 2016" #1

ptoomey3 opened this issue Jul 22, 2016 · 13 comments

Comments

@ptoomey3
Copy link

ptoomey3 commented Jul 22, 2016

First of all, 馃檱 for opening this up. There are obviously lots of details to flesh out, but this is a great starting point. Some of my feedback might be off base, but here are some random thoughts I had while reading through it:

  • Security-Policy: "v1" - Having an arbitrary version number feels funny, as it seems like it leaves room for error (updating the policy but forgetting to bump the version). Why not have the version be a hash of the json policy file? This would allow for trivial code to set the "version number" in headers (i.e. self updating) and would allow a nice belt and suspenders check on the client (you don't process a manifest unless it actually hashes to the same value provided in the header response).
  • I think I like the concept of fallback-csp (more broadly, fallbacks as a general feature), as we have run into this at GitHub. An early failure in normal request processing resulted in no CSP header getting set in the response. While we fixed the underlying issue, we also added a "fallback csp" on all our static error pages by setting a super strict policy via a meta tag. That said, we would generally be fine if our fallback matched our regular policy. So, I wonder how often people will actually strongly prefer a different policy. It may be that it is generally not needed, so maybe the baseline-csp would be sufficient. Or, maybe this could be made optional (and apply to other directives). Fore eample, there could be a fallback attribute you can set for any (where it makes sense) of the top level keys in this config (ex. Setting fallback for the referrer directive probably makes sense).
  • I like that this manifest is a home for "everything". I have been a bit confused/frustrated (though I understand the reasoning) with security headers being split between CSP and other various headers. So, it is nice that this manifest can be extended independent of where the "canonical location" is defined for the associated policy (standalone header vs. csp directive).
  • The document doesn't really talk about how multiple policies would compose. For example, if I set a baseline-csp policy, what effect does returning a Content-Security-Policy header have? Taking GitHub as an example, we would like to have a base policy that applies to the vast majority of requests, but we would like to override certain directives on certain pages. For example, we add on an additional connect-src to our policy on our payment pages (to allow connection to a third party payment processor). We don't want that source in our base policy, as it is not needed for 99% of requests. The most trivial solution would seem to be to prefer Content-Security-Policy over the manifest to override things when needed. But, that isn't ideal, as we don't really want to have to deliver the entire policy via header given that part of the goal of the manifest is not having to send an ever growing policy via header responses. This issue may very well be unique to CSP, as a complete override via a header generally makes sense (since most headers are single purpose). But, I do wonder if CSP should evolve in coordination with this manifest to allow for more granular overrides. Using my payment example above, what we really want is some base policy, and then some way to do something like Content-Security-Policy: append-connect-src 'https://api.braintreegateway.com' to append a single connect-src to the base policy for this single response. Most people probably use a static policy, but once you are addicted to CSP 馃槃, you see more and more places to prefer a granular directive set (generally in the form of a simple addition to a base policy).
  • The cors stuff feels a bit out of place. While I see the benefit of it, and CORS does have some security consequences, it doesn't feel like a "security policy" to me in quite the same way as the others. The other headers were explicitly added for security, where as the CORS header was just a "necessary evil" to make cross origin sharing work at all. I wonder if the Web App Manifest is a better home (though if these manifests meld into one then it doesn't really matter).

I'll add more comments if/when I think of things. Thanks again!

/cc
Team CC - @oreoshake @mastahyeti @gregose @brentjo
Twitter CC - @ericlaw1979 @ScottHelme @marumari

@ptoomey3
Copy link
Author

Doh...I left out @intchloe from the list of us chatting about this on twitter.

@april
Copy link

april commented Jul 22, 2016

Mostly I agree with what @ptoomey3 said, as he seems like a rational fellow. Here are some of my thoughts:

  • If we're doing JSON as the format, why don't we normalize these things instead of requiring the browser to both parse the JSON and parse the string? For example:
{
  "baseline-csp": {
    "sources": {
      "default": ["'none'"],
      "img": ["'self'", "https://github.io"],
      "script": ["'self'", "https://cdn.query.com"]
    },
    "report-only": true
  },
  "hsts": {
    "max-age": 31536000,
    "includeSubDomains": true,
    "preload": true
  },
  "nosniff": true,
}
  • I had thought v1 was like spf, that is, version one of the standard. Maybe you want spmv1 as the standard number, and a something like hash=sha256-hash as the "serial" number?
  • Instead of just having a static /.well-known/{serial}, you could just have _include, like in SPF. That way you could locate the files anywhere, and possibly have multiple files if you wanted to merge the JSON entries.
  • If you have a modifying serial number, is it necessary to have all these files in different places? You can have the file in a static place, and incrementing the header instructs the browser to reload it.

I guess altogether that would make the header look something like:

Security-Policy-Manifest: v=spmv1; integrity=sha256-0d39bf414d3d72647888e5ed62c122ac21c4b947;
  include=/.well-known/serial-policy-manifest

@april
Copy link

april commented Jul 22, 2016

Another option, instead of (or in addition to) integrity is having a ttl in the header. That way somebody who has access to updating the code but not the headers could modify the file and browsers could pick it up automatically. They just keep this JSON file bundled with their code base, and they get pushed out at the same time. Set the ttl to something like 300, and it's unlikely to break too many things and if it does, it's for a short period of time.

@intchloe
Copy link

intchloe commented Jul 23, 2016

I wonder how the request header Origin-Policy will be implemented. I see some issues with privacy here, ie. if a client has spoofed its User Agent the Origin-Policy header will kinda spoil the browser because of how widespread the CSP version-support is. I don't know if this was referenced in 搂5.1 or if Mike meant cache-wise. Could you clarify? @mikewest

I'm not a fan of the idea, because this already kinda works in most web servers. One advantage is that we won't send to big headers. Also, I see issues with using dynamic CSP here as @ptoomey3 mentioned as well.

This could kinda be a footgun too - if we don't use Clear-Site-Data - because if the clients will cache (depending on the cache of course) the manifest and an attacker have found a CSP-bypass that can be fixed server side we have a problem because the client will use an old - cached version of the CSP [manifest].

I've had the same issues as @ptoomey3 with some pages not having a CSP sat. With a complex site structure it's very easy to miss some subpaths that should have a CSP.

PS. I like when examples are used. That's why I had a hard time understanding the Suborigins ED.

@mikewest
Copy link
Member

Oh. Hi, everyone. I wasn't actually ready to throw this to the world, but, since you're here, hello! :)

I slapped this together during IETF downtime, so take it with a grain or two of salt. It's a pretty rough strawman to spawn some discussion. Let's see what y'all had to say:

Security-Policy: "v1" - Having an arbitrary version number feels funny, as it seems like it leaves room for error (updating the policy but forgetting to bump the version). Why not have the version be a hash of the json policy file? This would allow for trivial code to set the "version number" in headers (i.e. self updating) and would allow a nice belt and suspenders check on the client (you don't process a manifest unless it actually hashes to the same value provided in the header response).

The goal here was to simplify things by using normal HTTP caching rules. That is, I'd expect /.well-known/origin-policy/{version} to be served with a quasi-infinite lifetime so we wouldn't need any new revalidation semantics on a single URL. That said, reducing flexibility by turning the version into an integrity hash is interesting. I think it's something to consider, though I worry a bit about introducing complexity if we want to keep agility by specifying multiple hashes, etc. I think I could get behind Security-Policy: {algorithm}-{hash} pointing to /.well-known/origin-policy/{hash} though. Good thing to consider.

I think I like the concept of fallback-csp (more broadly, fallbacks as a general feature), as we have run into this at GitHub.

  1. I updated this at some point yesterday to be a little more generic in nature: https://mikewest.github.io/security-policy-manifest/#example-adec858a
  2. This is more or less a reinvention of https://w3c.github.io/webappsec-csp/pinning/. I think it's valuable, but it's not going to work for everyone. The idea is that a "baseline" policy is applied to every response from your origin. A "fallback" policy is applied to every response that doesn't itself contain a C-S-P or C-S-P-R-O header, according to the specified type. I think that covers most cases. For pages that explicitly don't need a policy, you could use a fallback policy, and serve C-S-P: [invalid] or something.

The document doesn't really talk about how multiple policies would compose.

No, it doesn't. I haven't gotten to the implementation bits yet. :) The idea is that we'd treat it just like another header. If you're using a "baseline" policy, it would be just like including another C-S-P header with a response, and would compose the same way that multiple policies do today. That is, a baseline of object-src 'none' on a resource that serves script-src 'none' would block both script and objects.

The cors stuff feels a bit out of place.

It needs to live somewhere, as I think we need some way to reduce the number of preflights in cases where we know we don't need them. As you note, that whole bit of CORS is a hack around the status quo at that time. If we can assert that the server understands CORS, then the preflights are just wasted requests.

In any event, I agree that it's a new feature, but since it has origin-wide implications, it makes sense to me to make it part of the manifest.

Aaaaand now I'm hopping on a plane. I'll try to get through the rest of the discussion later this weekend. Thanks for the (earlier than expected!) feedback!

@mikewest
Copy link
Member

@marumari:

If we're doing JSON as the format, why don't we normalize these things instead of requiring the browser to both parse the JSON and parse the string?

Keeping the format the same means that we'd just be able to inject the string as a header; from a browser implementer's perspective, that's the simplest thing to do, as we can do it up in the network stack without any understanding of the meaning of the things we're putting in place.

Honestly, if we invent a new syntax for CSP, I have about 80 other changes I'd make at the same time. :) I'm not sure this is the right way to do that, as it's something of an orthogonal task which would require a new header anyway.

I had thought v1 was like spf, that is, version one of the standard. Maybe you want spmv1 as the standard number, and a something like hash=sha256-hash as the "serial" number?

No, the intent in this draft is for it to be the filename in the well-known directory.

Instead of just having a static /.well-known/{serial}, you could just have _include, like in SPF. That way you could locate the files anywhere, and possibly have multiple files if you wanted to merge the JSON entries.

I don't actually want to include files from anywhere. Setting policy for an origin is potentially dangerous, and I don't want to give folks other than the server owner the ability to inject policy. Locking things down to a .well-known suffix and forcing a specific mime type are both mechanisms by which I aim to make it harder for user-generated content to masquerade as the manifest. I may be missing a good reason for flexibility, but in the absence of that reason, I'd prefer to keep things in one place.

If you have a modifying serial number, is it necessary to have all these files in different places?
...
Another option, instead of (or in addition to) integrity is having a ttl in the header.

As above, I'd like to not reinvent browser caching rules. The closer we can keep the manifest file's behavior to any other HTTP request, the happier I am.

@intchloe:

I wonder how the request header Origin-Policy will be implemented. I see some issues with privacy here, ie. if a client has spoofed its User Agent the Origin-Policy header will kinda spoil the browser because of how widespread the CSP version-support is.

I don't understand the attack you're suggesting. Could you clarify? It sounds like you're saying that folks who change their user agent from Chrome; Like Safari; Like KHTML; Like Gecko; Like IE; Like Mosaic; Like Lynx to Sekrit Browser would be exposed as actually using a browser that supports origin policy? If so, then yes. That would happen. I'm not sure it's a risk I care about, honestly, as the user would also be able to use the same user-agent-changing extension to drop the Origin-Policy header (and, really, if you're changing the browser, you're on your own. :) ) Am I being too cavalier there? How much of a risk do you expect this scenario to pose?

I don't know if this was referenced in 搂5.1 or if Mike meant cache-wise.

The point of section 5.1 is that the name of the manifest file ~is a cookie, as a server could provide a unique manifest to each user, and thus reidentify them via the request HTTP header. Given that, we'd need to be sure that we give it the same lifetime as a cookie from a user's perspective. If a user clears their cookies or cache, we should clear out the stored manifest as well.

I'm not a fan of the idea, because this already kinda works in most web servers.

Does it? I'm not so sure. I think we need a pinning mechanism of some sort. I don't think that the pinning mechanism I sketched out in https://w3c.github.io/webappsec-csp/pinning/ works. This is my second pass. Happy to take a third, but I think it's functionality we need.

One advantage is that we won't send to big headers.

Yes. Which is a big advantage, really.

Also, this can ensure that you don't miss things like error pages, which I know has bitten Google in the past.

Also, I see issues with using dynamic CSP here as @ptoomey3 mentioned as well.

Yup. That's something to work out. I think the fallback mechanism is one way of doing it, but we can probably come up with something more elegant (if also more special-purpose).

This could kinda be a footgun too - if we don't use Clear-Site-Data - because if the clients will cache (depending on the cache of course) the manifest and an attacker have found a CSP-bypass that can be fixed server side we have a problem because the client will use an old - cached version of the CSP [manifest].

I think the draft addresses this scenario. That is, if v1 of your manifest is bad, then when you see a client send:

GET / HTTP/1.1
Host: example.com
Connection: keep-alive
...
Origin-Policy: "v1"
...

then the server can respond with:

HTTP/1.1 200 OK
Content-Encoding: gzip
Accept-Ranges: bytes
Cache-Control: max-age=604800
Content-Type: text/html
...
Origin-Policy: "v2"
...

That will trigger an update in the client, as the name is new. The update, like the original, will happen synchronously, before the page is loaded.

Does this not address the scenario you're concerned with?

I like when examples are used.

Examples are a great way of sketching things out before writing the algorithms. Figuring out what developers should see is a nice way of figuring out how things should work. Glad you appreciate them. :)

@intchloe
Copy link

@mikewest

Am I being too cavalier there? How much of a risk do you expect this scenario to pose?

No, you are correct. I was not understanding how the request header would work. I thought the client would have it's preferred version number of the manifest (based on the servers response) and not always trying to have the latest version. Then this is not a privacy issue for spoofed user agents, but like you said; it could work as cookies. Thank you for clarifying!

Does it? I'm not so sure.

I was referring to fallback. In NginX you can set a CSP-header on / and then explicitly set CSP-headers on locations that you want another policy on. So then you'll get CSP all over the site.

However, we should't rely on web servers (too much). I agree that a manifest is much better than trying to send headers there we want them.

Does this not address the scenario you're concerned with?

It does. As long as the server updates the Origin-Policy header.

Took some time to understand how everything could work, so on seconds thoughts I like the design!
Thanks again Mike.


My thoughts of dynamic policies: if we can send a specific version number of the manifest designed for the current path:.

GET /image.jpg HTTP/1.1
...
Origin-Policy: "static"
...
GET /index.php HTTP/1.1
...
Origin-Policy: "index"
...

I see some issues with implementing this, but I'm really curious to see how we can solve this :)

@april
Copy link

april commented Jul 23, 2016

I don't actually want to include files from anywhere. Setting policy for an origin is potentially dangerous, and I don't want to give folks other than the server owner the ability to inject policy. Locking things down to a .well-known suffix and forcing a specific mime type are both mechanisms by which I aim to make it harder for user-generated content to masquerade as the manifest. I may be missing a good reason for flexibility, but in the absence of that reason, I'd prefer to keep things in one place.

I don't see how this:

Content-Security-Policy: default-src 'self'

Is any different from this:

Security-Policy-Manifest: _include=/some/folder/spm.json

(which has the same CSP inside it)

Either way, somebody had access to write the server headers and presumably is the server owner. But maybe I'm just missing something?

My reason for the flexibility is that historically in larger projects I have found that the people who run the web servers are not the same people that that write the application. And that is often a problem when it comes to the implementation of CSP, much time lost opening issues and shuffling tickets back and forth. It also makes coordination for new releases a lot more cumbersome.

If you could just set it as above, the web server people would never need to be contacted at all.

@mikewest
Copy link
Member

@marumari: In short, I see the fact that this mechanism requires cross-cutting cooperation from the folks responsible for running an entire origin as a feature, not a bug. I've attempted to spell that out in https://mikewest.github.io/security-policy-manifest/#why-well-known. Basically, you're right to say that there's no difference between CSP: [policy] and CSP: [path-to-policy], but both of those pertain only to a specific response and the page that's rendered. The mechanism we're describing in this document takes one response's assertion and applies it to the whole origin, which is a little bit risky. Consider google.com with Maps and Mail and Docs and Slides and 7-year-old Zeitgeist sites. Giving any response the ability to set a policy for all of those distinct applications seems like something we should be cautious of, and encouraging cross-team communications seems like a win.

That said, I'm happy to be convinced that I'm being overly cautious. I'd like to start with overly cautious, though. It's much easier to roll back restrictions than to impost them ex post facto.

@april
Copy link

april commented Jul 26, 2016

So, I'm reading:

https://mikewest.github.io/origin-policy/#why-well-known

And I guess I don't understand the objections?

  • It still requires cross-cutting cooperation initially, when setting the Origin-Policy header
  • You can already do similar things (ie, not contacting the sysadmins) with CSP by using tags
  • If they returned _include=/some/policy/op.json, Maps would use https://maps.google.com/some/policy/op.json, Mail would use https://mail.google.com/some/policy/op.json, etc.
  • If Google wanted to, they could certainly have one global origin policy by having each site return _include=https://google.com/global-origin-policy/op.json, but I imagine they would have different op.json files for each origin, as would anybody of significant scale.
  • By using _include, you give server admins the exact same option of using .well-known. It would simply start out as _include=/.well-known/origin-policy/v1, not give developers write access, and increment exactly the same as using Origin-Policy: "policy-v1".

@mikewest
Copy link
Member

@marumari:

It still requires cross-cutting cooperation initially, when setting the Origin-Policy header

I read your suggestion as being that setting headers is somewhat easy for application developers, while taking over the /.well-known directory might not be. That is, I'm assuming that sending a header is ~easy, while creating a well-known location is ~hard.

You can already do similar things (ie, not contacting the sysadmins) with CSP by using tags

The things you can do with <meta> create resource-specific policies, which cannot effect the pages you don't control. So, the friendly folks running https://www.google.com/press/zeitgeist/zeitgeist-june.html can only brick themselves if they set a policy of default-src 'none'. They cannot brick https://www.google.com/maps because they don't control that application's code.

If they returned _include=/some/policy/op.json, Maps would use https://maps.google.com/some/policy/op.json, Mail would use https://mail.google.com/some/policy/op.json, etc.

If the services live on separate origins, sure! An issue is that many don't.

If Google wanted to, they could certainly have one global origin policy by having each site return _include=https://google.com/global-origin-policy/op.json

I'm (potentially irrationally) worried about one origin asserting a policy which lives on another origin. I would prefer that the origin be completely responsible for its policy, both by serving the Origin-Policy header, and by serving the manifest itself.

By using _include, you give server admins the exact same option of using .well-known

I agree! But I think I would still prefer not to make that optional. :)

(One thing I didn't note earlier is that a well-known location also mitigates the risk that user-generated content might be interpreted as a manifest. This, along with the MIME type check, seems valuable.)

@april
Copy link

april commented Jul 27, 2016

I read your suggestion as being that setting headers is somewhat easy for application developers, while taking over the /.well-known directory might not be. That is, I'm assuming that sending a header is ~easy, while creating a well-known location is ~hard.

They're both hard for application developers! The fact that an application developer needs to go back to the server administrator to do work every single time they add a new resource type or domain to load resources from actively hinders CSP adoption. It adds extra steps, it complicates deployments, and it honestly adds like zero security in my experience. In my experience, this is what happens when there needs to be a change:

  1. Application Developer: "Hey, we need to add cdn.jquery.com to the CSP script-src. Here is the updated header."
  2. Sysadmin: "I have no bloody clue about the finer details of your application's resource usage, so I'll just do whatever you say."
  3. Sysadmin changes CSP header to exactly what they were told.

The things you can do with create resource-specific policies, which cannot effect the pages you don't control. So, the friendly folks running https://www.google.com/press/zeitgeist/zeitgeist-june.html can only brick themselves if they set a policy of default-src 'none'. They cannot brick https://www.google.com/maps because they don't control that application's code.

Well sure, you could set things up that way, but that would be a hugely messed up design. Here is how I would imagine it being setup, as in using nginx:

location /maps {
  add_header Origin-Policy "_include:https://google.com/.well-known/global-op.json _include:/maps/.well-known/op.json";
}

location /press {
  add_header Origin-Policy "_include:https://google.com/.well-known/global-op.json _include:/press/.well-known/op.json";
}

This way, maps' policy would only affect itself, press's policy would affect itself, etc. If the press team needed to include media.google.com in their img-src, they could change their JSON file and it would get picked up automatically, and it couldn't affect the rest of the origin. Plus, Google could set a global policy that could inherit (in some way) downward. It gives you a ton of flexibility, and speeds implementation.

I'm (potentially irrationally) worried about one origin asserting a policy which lives on another origin. I would prefer that the origin be completely responsible for its policy, both by serving the Origin-Policy header, and by serving the manifest itself.

Yes, I think you're more worried than I am. We do this stuff all the time:

april@pokeinthe:~$ host -t txt pokeinthe.io
pokeinthe.io descriptive text "v=spf1 include:_spf.google.com ~all"

Here I'm asserting a policy for who is allowed to send mail from my domain, but that policy lives on a completely different origin. Google changes the SPF record constantly; they could in theory completely ruin my ability to send mail, but it manages to work out great. If I wanted, I could manually manage all those Google mail servers (as like keeping everything in .well-known), but I don't. It's the same way with domain registration, DNS, etc.: I delegate control of my entire origin to a third party. It's a scary choice but in principal it works very well. Heck, HSTS has includeSubDomains, and one origin (aka google.com) can affect thousands or hundreds of thousands of other origins.

(One thing I didn't note earlier is that a well-known location also mitigates the risk that user-generated content might be interpreted as a manifest. This, along with the MIME type check, seems valuable.)

If you have to specify a URI for this file, though, it seems like you would put it in a place where user content can't be written, just like with .well-known.

I think part of why _include is awkward is that Origin Policy attempts to solve two distinct problems:

  • We have too many headers, some of which apply to the whole origin and some just for a given resource
  • These headers are too big and waste valuable bandwidth

I think we have the opportunity to come up with a solution that solves the two above problems, while also solving a couple extra problems:

  • CSP is difficult to manage for many organizations, because the application developers are often far removed from the server admins

While still allowing this flexibility:

  • Different applications on a given origin have differing requirements for things like CSP or referrrrrer policy, meaning they wouldn't work with an exclusive policy for an entire origin

I think if we can make an inheritance model work, we can come up with a solution that gives a ton of flexibility, makes it easier for application developers to update their policies, and lets organizations set global standards.

For example (and there are many ways to do it):

Origin-Policy: _include: https://google.com/global-op.json !important; _include /maps/op.json

Perhaps that Google Global Origin Policy could force HSTS for every possible origin that includes it, disables all content sniffing, allows fonts.google.com as a font-src for everybody, forbids evil.com as a script-src no matter what a subdomain wants to do, and sets 'upgrade-insecure-requests' on every origin. And then maps's Origin Policy file could add various CSP origins that it needs specifically for maps.

My previous job was at a bank, and there are times where it would have been completely outstanding to have centralized control over things like X-Content-Type-Options instead of hoping that each individual team's web admins would follow policy and make sure that the header was added.

@domenic
Copy link
Collaborator

domenic commented Nov 19, 2019

Hey everyone! It's been 3+ years, and a lot has changed. I'll close this out, as it seems like a lot of the original issues have been answered, including via the new format. Please file new issues if there's something I missed!

@domenic domenic closed this as completed Nov 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants