Test scenarios for verifying of JS injection for WebAuthn Policy

Closes #9544
This commit is contained in:
Martin Bartoš 2022-01-31 11:56:06 +01:00 committed by Marek Posolda
parent 47208b7a20
commit 243b6ba552
2 changed files with 206 additions and 0 deletions

View file

@ -0,0 +1,175 @@
/*
* Copyright 2022 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.webauthn.registration;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.testsuite.webauthn.AbstractWebAuthnVirtualTest;
import org.keycloak.testsuite.webauthn.pages.WebAuthnAuthenticatorsList;
import org.keycloak.testsuite.webauthn.updaters.AbstractWebAuthnRealmUpdater;
import org.keycloak.testsuite.webauthn.utils.WebAuthnRealmData;
import org.keycloak.utils.StringUtil;
import java.io.Closeable;
import java.io.IOException;
import java.util.function.Consumer;
import java.util.function.Function;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* Verify JS attack in WebAuthn Policy
*
* @author <a href="mailto:mabartos@redhat.com">Martin Bartos</a>
*/
public class PolicyJsInjectionTest extends AbstractWebAuthnVirtualTest {
// If there's an unexpected alert, the test fails -> we verified here that the script is not executed
protected final String PROMPT_SCRIPT = "required\"; window.prompt('Injection'); \"<img id=\"image-inject\" src='none'/> ";
// The page is redirect, if the script is executed
protected final String REDIRECT_SCRIPT = "required\"; window.location.href = \"http://www.keycloak.org\";\"";
@Test
public void relyingPartyEntityName() {
verifyInjection((updater) -> updater.setWebAuthnPolicyRpEntityName(REDIRECT_SCRIPT),
WebAuthnRealmData::getRpEntityName,
REDIRECT_SCRIPT);
}
@Test
public void relyingPartyId() throws IOException {
try (Closeable u = getWebAuthnRealmUpdater()
.setWebAuthnPolicyRpId(PROMPT_SCRIPT)
.update()) {
WebAuthnRealmData data = new WebAuthnRealmData(testRealm().toRepresentation(), isPasswordless());
assertThat(data.getRpId(), is(PROMPT_SCRIPT));
registerDefaultUser(false);
webAuthnErrorPage.assertCurrent();
assertThat(webAuthnErrorPage.getError(), containsString("The relying party ID is not a registrable domain suffix of, nor equal to the current domain."));
}
}
@Test
public void attestationConveyancePreference() {
verifyInjection((updater) -> updater.setWebAuthnPolicyAttestationConveyancePreference(REDIRECT_SCRIPT),
WebAuthnRealmData::getAttestationConveyancePreference,
REDIRECT_SCRIPT,
"Failed to read the 'attestation' property from 'PublicKeyCredentialCreationOptions': The provided value 'required\"; window.location.href = \"http://www.keycloak.org\";\"' is not a valid enum value of type AttestationConveyancePreference.");
}
@Test
public void authenticatorAttachment() {
verifyInjection((updater) -> updater.setWebAuthnPolicyAuthenticatorAttachment(REDIRECT_SCRIPT),
WebAuthnRealmData::getAuthenticatorAttachment,
REDIRECT_SCRIPT,
"Failed to read the 'authenticatorAttachment' property from 'AuthenticatorSelectionCriteria': The provided value 'required\"; window.location.href = \"http://www.keycloak.org\";\"' is not a valid enum value of type AuthenticatorAttachment.");
}
@Test
public void requireResidentKey() {
// requireResidentKey is set to 'false' and the value is ignored -> success
verifyInjection((updater) -> updater.setWebAuthnPolicyRequireResidentKey(PROMPT_SCRIPT),
WebAuthnRealmData::getRequireResidentKey,
PROMPT_SCRIPT);
}
@Test
public void userVerificationRequirement() {
verifyInjection((updater) -> updater.setWebAuthnPolicyUserVerificationRequirement(PROMPT_SCRIPT),
WebAuthnRealmData::getUserVerificationRequirement,
PROMPT_SCRIPT,
"Failed to read the 'userVerification' property from 'AuthenticatorSelectionCriteria': The provided value 'required\"; window.prompt('Injection'); \"<img id=\"image-inject\" src='none'/> ' is not a valid enum value of type UserVerificationRequirement.");
}
@Test
public void injectUserLabel() {
final String originalLabel = "label'`;window.prompt(\"another\");'";
registerDefaultUser(originalLabel);
appPage.assertCurrent();
final CredentialRepresentation credential = userResource().credentials()
.stream()
.filter(f -> f.getType().equals(getCredentialType()))
.findFirst()
.orElse(null);
assertThat(credential, notNullValue());
assertThat(credential.getUserLabel(), is(originalLabel));
if (!isPasswordless()) {
logout();
loginPage.open();
loginPage.assertCurrent(TEST_REALM_NAME);
loginPage.login(USERNAME, PASSWORD);
webAuthnLoginPage.assertCurrent();
WebAuthnAuthenticatorsList authenticators = webAuthnLoginPage.getAuthenticators();
assertThat(authenticators, notNullValue());
assertThat(authenticators.getItems(), not(Matchers.empty()));
assertThat(authenticators.getLabels().get(0), is("label`;window.prompt(\"another\");"));
}
}
private void verifyInjection(Consumer<AbstractWebAuthnRealmUpdater<?>> realmSetter, Function<WebAuthnRealmData, String> realmGetter, String requiredValue) {
verifyInjection(realmSetter, realmGetter, requiredValue, "");
}
/**
* Verify the possibility of executing the JS injection in WebAuthn Policy settings
*
* @param realmSetter set Realm WebAuthn policy
* @param realmGetter get Realm WebAuthn policy
* @param expectedValue expected value save in realm
* @param errorMessage expected message if it's present
*/
private void verifyInjection(Consumer<AbstractWebAuthnRealmUpdater<?>> realmSetter, Function<WebAuthnRealmData, String> realmGetter, String expectedValue, String errorMessage) {
AbstractWebAuthnRealmUpdater<?> updater = getWebAuthnRealmUpdater();
realmSetter.accept(updater);
try (Closeable u = updater.update()) {
WebAuthnRealmData data = new WebAuthnRealmData(testRealm().toRepresentation(), isPasswordless());
assertThat(realmGetter.apply(data), is(expectedValue));
boolean shouldSuccess = StringUtil.isBlank(errorMessage);
registerDefaultUser(shouldSuccess);
if (shouldSuccess) {
appPage.assertCurrent();
} else {
webAuthnErrorPage.assertCurrent();
assertThat(webAuthnErrorPage.getError(), containsString(errorMessage));
}
} catch (IOException e) {
throw new RuntimeException("Cannot verify test scenarios for WebAuthn Policy JS Injection", e);
}
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright 2022 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.webauthn.registration.passwordless;
import org.keycloak.testsuite.webauthn.registration.PolicyJsInjectionTest;
/**
* @author <a href="mailto:mabartos@redhat.com">Martin Bartos</a>
*/
public class PwdLessPolicyJsInjectionTest extends PolicyJsInjectionTest {
@Override
public boolean isPasswordless() {
return true;
}
}