Test scenarios for verifying of JS injection for WebAuthn Policy
Closes #9544
This commit is contained in:
parent
47208b7a20
commit
243b6ba552
2 changed files with 206 additions and 0 deletions
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue