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

[Security] Unicode normalization can lead to open redirect #5777

Open
chmodxxx opened this issue Oct 22, 2024 · 4 comments
Open

[Security] Unicode normalization can lead to open redirect #5777

chmodxxx opened this issue Oct 22, 2024 · 4 comments

Comments

@chmodxxx
Copy link

Description

We are facing the following behaviour , we have the following endpoint that returns a redirect response via :

return Response.seeOther("https://www.google.com☣@example.org");
Normally this should redirect the user to example.org, however it is not the case the redirection goes to www.google.com .

You can use any unicode character that ends with 23 (e.g \u2523), jakarta will do some sort of normalization in the response headers and convert the unicode character to a literal # causing the legitimate domain to be interpreted as fragment.

Problem

This is a big problem when having an Oauth endpoint using jakarta, the normal implementation would be to take redirect_uri from User input and validate the host, and then redirect to the target domain. The problem here is when parsing this redirect uri in java "https://www.malicious.com☣@legitimate.com" the host will be legitimate.com but the redirection via seeOther() will send the user to malicious.com, which will result in an open redirect in oauth flows.

@jansupol
Copy link
Contributor

@chmodxxx This looks serious. Can you specify how exactly you parse the redirect uri? What client connector do you use (default, netty...)? Do you use some custom logic that includes the work with UriBuilder?

@jansupol
Copy link
Contributor

The Location header for return Response.seeOther(URI.create("https://www.google.com\[email protected]")).build(); is https://www.google.com%E2%94%[email protected] and for return Response.seeOther(URI.create("https://www.google.com☣@example.org")).build(); it is https://www.google.com%E2%98%[email protected] for me.

What container does Jersey run at in your case?

@chmodxxx
Copy link
Author

@jansupol Thanks for looking into this, I don't think our custom logic to validate/parse is relevant for this case, I have created an endpoint that has this only logic :

 @Override
    public Response handleRequest() {
            return Response.seeOther("https://www.google.com☣@example.org");
    }

We have this custom Response interface and the logic roughly looks like this :

import jakarta.ws.rs.core.Response.ResponseBuilder;

public interface Response {

    @Value.Parameter
    int statusCode();

    Map<HttpString, String> headers();

    List<Cookie> cookies();

    Optional<Body> body();

    default jakarta.ws.rs.core.Response toJaxrs() {
        checkState(cookies().isEmpty());

        ResponseBuilder builder = jakarta.ws.rs.core.Response.status(statusCode());

        headers().forEach((name, value) -> builder.header(name.toString(), value));

        if (body().isPresent()) {
            builder.entity(body().get());
        }

        return builder.build();
    }

    static Response seeOther(String location) {
        return builder().seeOther(location).build();
    }

    static Builder builder() {
        return new Builder();
    }

    static Builder ok() {
        return builder().statusCode(StatusCodes.OK);
    }

    final class Builder extends ImmutableResponse.Builder {

        public Builder seeOther(String location) {
            statusCode(StatusCodes.SEE_OTHER);
            putHeaders(Headers.LOCATION, location);
            return this;
        }
}
}

@chmodxxx
Copy link
Author

chmodxxx commented Nov 1, 2024

Any updates ?

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

2 participants