-
Notifications
You must be signed in to change notification settings - Fork 484
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’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Restricted Content-Types #141
Conversation
👍 to this. If you have not set a host whitelist then imageproxy may happily proxy content from the network in which it is running too. For example |
Really looking forward for this PR to get merged. This will be a huge plus for security. Thanks, @ccbrown! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies for the delay in reviewing, but thanks for this change... it definitely looks to be on the right track. A few suggested changes and questions below.
imageproxy.go
Outdated
copyHeader(w.Header(), resp.Header, "Content-Length", "Content-Type") | ||
if contentType := p.allowedContentType(resp.Header.Get("Content-Type")); contentType != "" { | ||
w.Header().Set("Content-Type", contentType) | ||
w.Header().Set("X-Content-Type-Options", "nosniff") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't whatwg/fetch#395 suggest that you shouldn't use nosniff with images?
And can this content type check simply be moved into the existing allowed() method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My current understanding is that it's fine (and more secure) as long you also specify the correct Content-Type. I should research and test that a bit more though.
I think the reason it's not in the existing allowed
method is because it has to happen after we fetch the remote image.
imageproxy.go
Outdated
@@ -211,6 +225,41 @@ func (p *Proxy) allowed(r *Request) error { | |||
return fmt.Errorf("request does not contain an allowed host or valid signature: %v", r) | |||
} | |||
|
|||
// allowedContentType returns an allowed content type string to use in responses or "" if the | |||
// content type cannot be used. | |||
func (p *Proxy) allowedContentType(contentType string) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why return a string? It doesn't look like the return value is really needed. Instead, perhaps rename this method to validContentType and return a bool like the other valid* methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like there was a good reason for this, but I can't remember it. I'll refactor if it doesn't come to me.
imageproxy.go
Outdated
|
||
if len(p.ContentTypes) == 0 { | ||
switch mediaType { | ||
case "image/bmp", "image/cgm", "image/g3fax", "image/gif", "image/ief", "image/jp2", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having to list out all of these images types seems really messy and error prone. You've already added support for image/*
style wildcards below, so instead of treating an empty ContentTypes as special, just have the default flag value explicitly be image/*
. That results in safe default behavior (just images), but still allows for the existing behavior (all types), by simply passing an empty value for the contentTypes flag.
Or is there some other image/* subtype that is deemed unsafe that you're specifically trying to avoid?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is a security PR, I feel a lot better about using a more restrictive whitelist that's already been put to the test in other applications.
I can't say with much certainty that there are not dangerous image/* mimetypes. But perhaps more importantly, we can't say there will never be a dangerous image/* mimetype in the future (if there isn't already one).
So while there's nothing specifically that I'm aware of needing to avoid, I think it would be a security best practice to use a whitelist such as this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where did this whitelist come from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -299,20 +299,20 @@ func (t testTransport) RoundTrip(req *http.Request) (*http.Response, error) { | |||
var raw string | |||
|
|||
switch req.URL.Path { | |||
case "/ok": | |||
case "/plain": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with the behavior I suggested above (namely that an empty ContentTypes value is not treated special), then this change should be reverted, as well as the corresponding StatusForbidden below.
Thanks for taking a look @willnorris. I'll try to re-familiarize myself with this PR and at least look into the nosniff thing / allowedContentType signature tomorrow. |
d7889d1
to
b0bdbd9
Compare
@willnorris I made the requested As for using "nosniff", the fact that Camo uses it by default combined with the fact that many very large and popular websites use "nosniff" for images has me convinced that there is not a compatibility issue with modern browsers. And I think there's a very real risk of some browsers sniffing proxied resources into non-image types if the header is omitted. |
Is there any update on the status of this PR? We've renewed our focus on adding a built-in image proxy to Mattermost, and this feature would be very important for that |
For security purposes, we need to ensure that the proxy is only used to serve images. Serving non-image content opens up a lot of attack surface for phishing, XSS, SSRF, and other nasty tricks. Being labeled "imageproxy", I was surprised to find out that our proxies were happily serving any arbitrary HTML that was thrown at them.
I'm putting this PR in hoping that we can make the proxy more secure by default and bring it up to par with atmos/camo in this regard.
Our
upcoming4.7 release of Mattermost was all set to include built-in support for proxying user-posted images (via this or Camo), but we have a lot of very security-minded customers, so this put a slight wrinkle in our plans.I've read over all of the related discussion I could find (namely this) and I think this addresses the big concerns except for one:
I strongly believe this should be enabled by default. It shouldn't be viewed as simply being a breaking change. It's a security patch. It's supposed to prevent people from doing things that they could do before.
The Changes
-contentTypes
flag. Shell pattern matching is used, so you can do things like-contentTypes image/*,video/mp4
if you'd like. Or you can just enable everything with wildcards to get today's behavior, but probably no one should do this.This alters the behavior at the last moment, right before sending the response back to the client. This does not do sniffing. As stated in some of the other discussion, sniffing is pointless in the context of security. The only thing that matters is how browsers interpret the content. And browsers decide that based on the Content-Type and X-Content-Type-Options headers, which are now both set and strictly controlled.