Skip to content

Commit

Permalink
Escape action in the form_post response mode (#30)
Browse files Browse the repository at this point in the history
Closes https://issues.redhat.com/browse/RHBK-652

Signed-off-by: rmartinc <rmartinc@redhat.com>
  • Loading branch information
rmartinc committed Dec 6, 2023
1 parent 15a21bf commit 67f905e
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ public String buildHtmlForm(String actionUrl, Map<String, String> inputTypes) {
.append("</HEAD>")
.append("<BODY Onload=\"document.forms[0].submit()\">")

.append("<FORM METHOD=\"POST\" ACTION=\"").append(actionUrl).append("\">");
.append("<FORM METHOD=\"POST\" ACTION=\"").append(escapeAttribute(actionUrl)).append("\">");

builder.append("<p>Redirecting, please wait.</p>");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,9 @@ public Response build() {
builder.append(" </HEAD>");
builder.append(" <BODY Onload=\"document.forms[0].submit()\">");

builder.append(" <FORM METHOD=\"POST\" ACTION=\"" + redirectUri.toString() + "\">");
builder.append(" <FORM METHOD=\"POST\" ACTION=\"")
.append(HtmlUtils.escapeAttribute(redirectUri.toString()))
.append("\">");

for (Map.Entry<String, String> param : params.entrySet()) {
builder.append(" <INPUT TYPE=\"HIDDEN\" NAME=\"")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,9 @@ public String getCurrentRequest() {
int index = driver.getCurrentUrl().indexOf('?');
if (index == -1) {
index = driver.getCurrentUrl().indexOf('#');
if (index == -1) {
index = driver.getCurrentUrl().length();
}
}
return driver.getCurrentUrl().substring(0, index);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,20 @@ public static SAMLDocumentHolder extractSamlResponseFromForm(String responsePage
return SAMLRequestParser.parseResponsePostBinding(respElement.val());
}

/**
* Extracts the form element from a Post binding.
*
* @param responsePage HTML code in the page
* @return The element that is the form
*/
public static Element extractFormFromPostResponse(String responsePage) {
org.jsoup.nodes.Document theResponsePage = Jsoup.parse(responsePage);
Elements form = theResponsePage.select("form");
assertThat("Checking uniqueness of SAMLResponse/SAMLRequest form in Post binding", form.size(), is(1));

return form.first();
}

/**
* Extracts and parses value of RelayState input field of a form present in the given page.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package org.keycloak.testsuite.oauth;

import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Before;
Expand All @@ -25,6 +27,7 @@
import org.keycloak.OAuthErrorException;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
import org.keycloak.models.Constants;
import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
import org.keycloak.representations.idm.RealmRepresentation;
Expand All @@ -35,6 +38,7 @@
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.WaitUtils;
import org.openqa.selenium.By;

import jakarta.ws.rs.core.UriBuilder;
Expand Down Expand Up @@ -246,6 +250,28 @@ public void authorizationRequestFormPostResponseModeInvalidRedirectUri() throws
}
}

@Test
public void authorizationRequestFormPostResponseModeHTMLEntitiesRedirectUri() throws IOException {
try (var c = ClientAttributeUpdater.forClient(adminClient, "test", "test-app")
.setRedirectUris(Collections.singletonList("*"))
.update()) {
oauth.responseMode(OIDCResponseMode.FORM_POST.value());
oauth.responseType(OAuth2Constants.CODE);
oauth.redirectUri("/test?p=&gt;"); // set HTML entity &gt;
oauth.doLogin("test-user@localhost", "password");

WaitUtils.waitForPageToLoad();
// if not properly encoded %3E would be received instead of &gt;
MatcherAssert.assertThat("Redirect page was not encoded", oauth.getDriver().getCurrentUrl(), Matchers.endsWith("/test?p=&gt;"));

events.expect(EventType.LOGIN)
.user(AssertEvents.isUUID())
.session(AssertEvents.isUUID())
.detail(Details.USERNAME, "test-user@localhost")
.assertEvent();
}
}

@Test
public void authorizationRequestFormPostResponseModeWithCustomState() throws IOException {
oauth.responseMode(OIDCResponseMode.FORM_POST.value());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.matchesRegex;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT;
Expand Down Expand Up @@ -345,4 +346,28 @@ public void testInvalidAssertionConsumerServiceURL() throws IOException {
assertThat(page, containsString("Invalid redirect uri"));
}
}

@Test
public void testConsumerServiceURLHtmlEntities() throws IOException {
try (var c = ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setRedirectUris(Collections.singletonList("*"))
.update()) {

String action = new SamlClientBuilder()
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, "javascript&colon;alert('xss');", Binding.POST)
.build()
.login().user(bburkeUser).build()
.executeAndTransform(response -> {
assertThat(response, statusCodeIsHC(Response.Status.OK));
String responsePage = EntityUtils.toString(response.getEntity(), "UTF-8");
return SamlClient.extractFormFromPostResponse(responsePage)
.attributes().asList().stream()
.filter(a -> "action".equalsIgnoreCase(a.getKey()))
.map(org.jsoup.nodes.Attribute::getValue)
.findAny().orElse(null);
});
// if not encoded properly jsoup returns ":" instead of "&colon;"
assertThat(action, endsWith("javascript&colon;alert('xss');"));
}
}
}

0 comments on commit 67f905e

Please sign in to comment.