Open chmodxxx opened 1 week ago
@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?
The Location header for return Response.seeOther(URI.create("https://www.google.com\u2523@example.org")).build();
is https://www.google.com%E2%94%A3@example.org
and for return Response.seeOther(URI.create("https://www.google.com☣@example.org")).build();
it is https://www.google.com%E2%98%A3@example.org
for me.
What container does Jersey run at in your case?
@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;
}
}
}
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 toexample.org
, however it is not the case the redirection goes towww.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.