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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refine CORS documentation for wildcard processing #31143

Closed
lowcasz opened this issue Aug 30, 2023 · 10 comments
Closed

Refine CORS documentation for wildcard processing #31143

lowcasz opened this issue Aug 30, 2023 · 10 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches type: documentation A documentation task
Milestone

Comments

@lowcasz
Copy link

lowcasz commented Aug 30, 2023

In CORS configuration we don't have access for request and response, e.g. by servlet filter
We have allowedOriginsPattern to override * wildcard when we are using allowCredentials=true
In this situation we overwrite allowed-origin header by request origin, but heders and methods are still not overwrite wildcard which is not allowed by documentation when we allow credentials.

When some configuration for requestHeaders, exposedHeaders, allowedMethods is null then all is clear,
but when we have allowed credentials and some list is defined with one value with asterisk then it should be overwritten according to CORS documentation.

It is problematically for preflighted requests when we don't know all request headers or exposed headers,
but in single request it should be overwritten according to headers from request and response when we allow credentials,
or we should have possibility to calculate cors headers dynamically based on request and response to fix it manually.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Aug 30, 2023
@bclozel
Copy link
Member

bclozel commented Aug 31, 2023

From the description of this issue, it's not clear whether you are reporting a bug, requesting an enhancement, or asking a question.

Can we take a step back and start over with:

  • what are you trying to achieve
  • what have you tried (a code snippet of the CORS configuration you are using)
  • what's the behavior your are getting
  • what behavior you were expecting itself

You can read up good advice on how to create a minimal sample if this is a better option for you.

@bclozel bclozel added the status: waiting-for-feedback We need additional information before we can continue label Aug 31, 2023
@lowcasz
Copy link
Author

lowcasz commented Aug 31, 2023

What am I trying to achieve?

  • I want extend default CORS configuration to pass everything with any origin, method, requestHeaders, exposedHeaders, allowedCredentials.

What am I tried?
cors-demo.zip

@Configuration
@EnableWebMvc
@RequiredArgsConstructor
public class CorsConfig implements WebMvcConfigurer {

    private final CorsProperties corsProperties;

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").combine(corsProperties.applyPermitDefaultValues());
    }

}
package example;

import static java.util.Objects.isNull;
import static org.springframework.util.CollectionUtils.isEmpty;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.web.cors.CorsConfiguration;

@ConfigurationProperties("cors")
public class CorsProperties extends CorsConfiguration {

    @Override
    public CorsConfiguration applyPermitDefaultValues() {
        if (isEmpty(getAllowedOrigins()) && isEmpty(getAllowedOrigins())) {
            addAllowedOriginPattern(ALL);
        }
        if (isEmpty(getAllowedMethods())) {
            addAllowedMethod(ALL);
        }
        if (isEmpty(getAllowedHeaders())) {
            addAllowedHeader(ALL);
        }
        if (isEmpty(getExposedHeaders())) {
            addExposedHeader(ALL);
        }
        if (isNull(getMaxAge())) {
            setMaxAge(1800L);
        }
        if (isNull(getAllowCredentials())) {
            setAllowCredentials(true);
            //todo owerwrite "*" (ALL="*") in all fields dynamically based on request and response, when allow credentials
        }
        return this;
    }

}

Whats behaviour am I gets?
image

What is expected bahaviour?
CORS documentation don't allow * wildcard (in allowOrigin, methods, requestHeaders, exposedHeaders) when allowCredentials is true.
In this situation, when wildcard was declared in configuration, all response headers should contains listed all needed values instead of *, but response header contains *:
Access-Control-Expose-Headers: *
instead of: Access-Control-Expose-Headers: Location

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Aug 31, 2023
@sdeleuze
Copy link
Contributor

sdeleuze commented Aug 31, 2023

Looks like there is potentially 2 things here:

  • A potential bug related to the fact that we are not preventing users to specify * for Control-Allow-Headers, Control-Allow-Methods and Control-Expose-Headers like we do for Origin when credentials are enabled. Both Mozilla documentation related section and WHATWG specs are mentioning that (could have been updated or we missed that, not sure)
  • A feature request to manage patterns for allowed methods, request headers, exposed headers like we do for allowed origins (see related Support for pattern based origin cors configuration #25016 issue).

@lowcasz Could you please confirm your are raising both the described potential bug and enhancement request?

@sdeleuze sdeleuze self-assigned this Aug 31, 2023
@sdeleuze sdeleuze added in: web Issues in web modules (web, webmvc, webflux, websocket) status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Aug 31, 2023
@lowcasz
Copy link
Author

lowcasz commented Sep 3, 2023

Sure ;) It can be like that. We are talking about situation when allowCredentials (Access-Control-Allow-Credentials) is true only. Without credentials actual version is ok.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Sep 3, 2023
@sdeleuze sdeleuze added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged or decided on status: feedback-provided Feedback has been provided labels Sep 4, 2023
@sdeleuze sdeleuze added this to the 6.0.12 milestone Sep 4, 2023
@sdeleuze sdeleuze added the for: backport-to-5.3.x Marks an issue as a candidate for backport to 5.3.x label Sep 4, 2023
@github-actions github-actions bot added status: backported An issue that has been backported to maintenance branches and removed for: backport-to-5.3.x Marks an issue as a candidate for backport to 5.3.x labels Sep 4, 2023
@lowcasz
Copy link
Author

lowcasz commented Sep 4, 2023

I'm sorry for late answer. I tested it on chrome only, and this browser actually works with asterisk even if credentials are allowed.
So in this situation maybe it is not the best way to totally block it even if documentation requires other behavior.
In this situation I think bugfix as restriction is no needed and only pattern feauture is needed.

@sdeleuze
Copy link
Contributor

sdeleuze commented Sep 4, 2023

After a discussion with the team, for headers and methods, we are not going to introduce pattern management.

You can watch this draft commit to see the direction I am leaning towards.

I have implemented support for copying the request method and headers but I will likely not be able to support exposed headers as that would require, if I am not mistaken, to copy the response headers which are not available when the CorsProcessor is invoked. Could be in theory implemented by deferring this part of the processing after all filters + handler have been invoked, but that's out of the scope of this issue, and I am not sure it is worth the effort.

I tested it on chrome only, and this browser actually works with asterisk even if credentials are allowed.
So in this situation maybe it is not the best way to totally block it even if documentation requires other behavior.
In this situation I think bugfix as restriction is no needed and only pattern feauture is needed.

Are you talking about allowed methods, allowed headers or exposed headers here? If this is about exposed headers, did you checked headers were accessible from JavaScript with Access-Control-Expose-Headers: true when credentials are enabled?

@lowcasz
Copy link
Author

lowcasz commented Sep 4, 2023

I have read actual Bean defining CORS and I know it is difficult to change. It is reason why I wrote it instead extend this class or create additional filters, becase o lot of people have the same problem and should be resolved on library level.
I don't understand your question because is wrong. :/ I hope you mean: Access-Control-Expose-Headers: *
So yes, I tested it and was problem without Access-Control-Expose-Headers, but add it with * resolve my problem on chrome which uses JS for requests. I don't know yet how many browsers support * with enabled credentials.
Documentation of CORS describe the *, cannot be used in any headers like allowed origins, methods, headers, exposed headers when credentials are enabled. Some day a lot of users using spring configured with credentials and * will have to make a complicated hotfixes when any browser will start support this documentation requirements. There is a lot of ways where application is not vulnerable for this kind of attack and CORS porotection is no needed, but we cannot just switch off it when browser will require it.

It is why I think it is important issue.

I know other problem without answer: How to process preflight requests?
So overwrite it by library in normally response is especially important.

Instead of copy response headers, we can solve this problem in other way which support preflight requests too. We can create new issue when it is needed.
We can replace * on all known headers/methods specified in documentations and add all custom headers specified in application property file, but it have one disadvantage.
It will be generate big response with a lot of boiler plate, that can be a overkill on every request.

I think that will be very important and hot feature, when any popular browser starts support this restrictions.
This feature will open way to other possibility: "disable" CORS protection. It could be great feauture for beginners in spring and applications/services without sensitive data. I can start new issue to connect it, when you think it is valuable feature.

@rstoyanchev
Copy link
Contributor

rstoyanchev commented Sep 5, 2023

It is reason why I wrote it instead extend this class or create additional filters, becase o lot of people have the same problem and should be resolved on library level.

Your example in #31143 (comment) shows overriding applyDefaultPermits. This method is used on startup to switch from more conservative defaults (for general purpose use) to more permissive, opinionated defaults with @CrossOrigin. At that stage, there is no way to know what request or response headers will be used at runtime, and no practical way to replace * from there.

At best we can make decisions at runtime, in DefaultCorsProcessor, as in the commit @sdeleuze pointed to where we can use the actual HTTP method, and the headers from "access-control-request-headers".

For expose headers, there isn't anything straight forward we can do to replace *. This is something that I believe an application would have to do, explicitly listing the headers it wants to expose, which can be done in global CORS config, and/or via annotations where needed.

@sdeleuze
Copy link
Contributor

sdeleuze commented Sep 5, 2023

After some tests, I can confirm that Chrome, Safari and Firefox in practice all allow using Access-Control-Expose-Headers: * with enabled credentials (it is possible to get headers value from JavaScript), so it might potentially be relevant to not reject this combination since it works in practice and it is up to the browser's implementation to follow the spec or not. But we need to evaluate if that's OK from a security perspective or not for Spring.

@lowcasz lowcasz closed this as completed Sep 7, 2023
@sdeleuze
Copy link
Contributor

sdeleuze commented Sep 8, 2023

Please keep this issue open as we will do some refinements.

@sdeleuze sdeleuze reopened this Sep 8, 2023
@sdeleuze sdeleuze added type: documentation A documentation task and removed type: bug A general bug labels Sep 11, 2023
@sdeleuze sdeleuze changed the title CORS configuration - * wildcard with credentials is not overwritten Refine CORS documentation for wildcard processing Sep 11, 2023
sdeleuze added a commit to sdeleuze/spring-framework that referenced this issue Sep 11, 2023
This commit refines CORS wildcard processing Javadoc to
provides more details on how wildcards are handled for
Access-Control-Allow-Methods, Access-Control-Allow-Headers
and Access-Control-Expose-Headers CORS headers.

For Access-Control-Expose-Headers, it is not possible to copy
the response headers which are not available at the point
when the CorsProcessor is invoked. Since all the major browsers
seem to support wildcard including on requests with credentials,
and since this is ultimately the user-agent responsibility to
check on client-side what is authorized or not, Spring Framework
continues to support this use case.

See spring-projectsgh-31143
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches type: documentation A documentation task
Projects
None yet
Development

No branches or pull requests

5 participants