diff --git a/.github/scripts/.bash_history b/.github/scripts/.bash_history index 5bb63833c..ede715c5d 100644 --- a/.github/scripts/.bash_history +++ b/.github/scripts/.bash_history @@ -347,7 +347,7 @@ rm -rf jdk-18_linux-x64_bin.deb git rebase -i main git rebase -i master git stash -export tempPassword="XvAn9Kcj4XKXBFfkL0IBttcYp8GmZq7JuPjIjghfDZw=" +export tempPassword="T/8yzIOE0Xz3RIxjA2HMyncgmhUoZsHZLW6lQVj5yV4=" mvn run tempPassword k6 npx k6 diff --git a/README.md b/README.md index 59d882e79..6463b4a7a 100644 --- a/README.md +++ b/README.md @@ -429,7 +429,9 @@ If you want to dev without a Vault instance, use additionally the `without-vault ./mvnw spring-boot:run -Dspring-boot.run.profiles=local,without-vault ``` -Want to push a container? See `.github/scripts/docker-create-and-push.sh` for a script that generates and pushes all containers. Do not forget to rebuild the app before composing the container +Want to push a container? See `.github/scripts/docker-create-and-push.sh` for a script that generates and pushes all containers. Do not forget to rebuild the app before composing the container. + +Want to check why something in vault is not working in kubernetes? Do `kubectl exec vault-0 -n vault -- vault audit enable file file_path=stdout`. ### Dependency management diff --git a/k8s-vault-minkube-start.sh b/k8s-vault-minkube-start.sh index 43bb3de50..666a6f615 100755 --- a/k8s-vault-minkube-start.sh +++ b/k8s-vault-minkube-start.sh @@ -45,7 +45,7 @@ else helm repo add hashicorp https://helm.releases.hashicorp.com fi kubectl create ns vault -helm upgrade --install vault hashicorp/vault --version 0.23.0 --namespace vault --values k8s/helm-vault-values.yml +helm upgrade --install vault hashicorp/vault --version 0.27.0 --namespace vault --values k8s/helm-vault-values.yml isvaultrunning=$(kubectl get pods -n vault --field-selector=status.phase=Running) while [[ $isvaultrunning != *"vault-0"* ]]; do echo "waiting for Vault1" && sleep 2 && isvaultrunning=$(kubectl get pods -n vault --field-selector=status.phase=Running); done @@ -81,26 +81,66 @@ kubectl exec vault-0 -n vault -- vault secrets enable -path=secret kv-v2 echo "Putting a secret in" kubectl exec vault-0 -n vault -- vault kv put secret/secret-challenge vaultpassword.password="$(openssl rand -base64 16)" +echo "Putting a subkey issue in" +kubectl exec vault-0 -n vault -- vault kv put secret/wrongsecret aaaauser."$(openssl rand -base64 8)"="$(openssl rand -base64 16)" + +echo "Oepsi metadata" +kubectl exec vault-0 -n vault -- vault kv metadata put -mount=secret -custom-metadata=secret="$(openssl rand -base64 16)" wrongsecret + echo "Enable k8s auth" kubectl exec vault-0 -n vault -- vault auth enable kubernetes echo "Writing k8s auth config" - kubectl exec vault-0 -n vault -- /bin/sh -c 'vault write auth/kubernetes/config \ token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \ kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt' +kubectl exec vault-0 -n vault -- vault audit enable file file_path=stdout + echo "Writing policy for secret-challenge" kubectl exec vault-0 -n vault -- /bin/sh -c 'vault policy write secret-challenge - <pom import + + org.testcontainers + testcontainers-bom + ${testcontainers.version} + + com.google.cloud spring-cloud-gcp-dependencies @@ -123,11 +129,6 @@ org.springframework.boot spring-boot-starter - - org.testcontainers - testcontainers - test - org.springframework.boot spring-boot-starter-actuator @@ -148,6 +149,20 @@ ${spring-security.version} test + + org.testcontainers + vault + test + + + org.testcontainers + junit-jupiter + + + org.testcontainers + testcontainers + test + org.springframework.boot spring-boot-starter-web @@ -278,6 +293,11 @@ 1.9.0 test + + org.springframework.vault + spring-vault-core + 3.1.0 + diff --git a/scripts/install-vault.sh b/scripts/install-vault.sh index 186b8614b..24717192a 100644 --- a/scripts/install-vault.sh +++ b/scripts/install-vault.sh @@ -11,7 +11,7 @@ if [ $? == 0 ]; then echo "Vault ns is already there" else kubectl create ns vault - helm upgrade --install vault hashicorp/vault --version 0.23.0 --namespace vault --values ../k8s/helm-vault-values.yml + helm upgrade --install vault hashicorp/vault --version 0.27.0 --namespace vault --values ../k8s/helm-vault-values.yml fi @@ -49,11 +49,16 @@ kubectl exec vault-0 -n vault -- vault secrets enable -path=secret kv-v2 echo "Putting a secret in" kubectl exec vault-0 -n vault -- vault kv put secret/secret-challenge vaultpassword.password="$(openssl rand -base64 16)" +echo "Putting a subkey issue in" +kubectl exec vault-0 -n vault -- vault kv put secret/wrongsecret aaaauser."$(openssl rand -base64 8)"="$(openssl rand -base64 16)" + +echo "Oepsi metadata" +kubectl exec vault-0 -n vault -- vault kv metadata put -mount=secret -custom-metadata=secret="$(openssl rand -base64 16)" wrongsecret + echo "Enable k8s auth" kubectl exec vault-0 -n vault -- vault auth enable kubernetes echo "Writing k8s auth config" - kubectl exec vault-0 -n vault -- /bin/sh -c 'vault write auth/kubernetes/config \ token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \ @@ -64,11 +69,44 @@ kubectl exec vault-0 -n vault -- /bin/sh -c 'vault policy write secret-challenge path "secret/data/secret-challenge" { capabilities = ["read"] } +path "secret/metadata/wrongsecret" { + capabilities = ["read", "list" ] +} +path "secret/subkeys/wrongsecret" { + capabilities = ["read", "list" ] +} +path "secret/data/wrongsecret" { + capabilities = ["read", "list" ] +} path "secret/data/application" { capabilities = ["read"] } EOF' +kubectl exec vault-0 -n vault -- /bin/sh -c 'vault policy write standard_sre - <> versioned = versionedOperations.get("wrongsecret"); + if (versioned == null) { + return vaultPasswordString; + } + var metadata = versioned.getMetadata(); + if (metadata == null) { + return vaultPasswordString; + } + var customMetadata = metadata.getCustomMetadata(); + if (!customMetadata.isEmpty()) { + String customMedataSecret = customMetadata.get("secret"); + if (Strings.isNullOrEmpty(customMedataSecret)) { + return vaultPasswordString; + } + return customMedataSecret; + } + } catch (Exception e) { + log.warn("Exception during execution of challenge44", e); + } + return vaultPasswordString; + } +} diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/VaultSubKeyChallenge.java b/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/VaultSubKeyChallenge.java new file mode 100644 index 000000000..72b1b5fd1 --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/VaultSubKeyChallenge.java @@ -0,0 +1,56 @@ +package org.owasp.wrongsecrets.challenges.kubernetes; + +import java.util.Map; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; +import org.owasp.wrongsecrets.challenges.FixedAnswerChallenge; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.vault.config.VaultProperties; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; +import org.springframework.vault.core.VaultTemplate; +import org.springframework.vault.core.VaultVersionedKeyValueOperations; +import org.springframework.vault.support.Versioned; + +@Component +@Slf4j +public class VaultSubKeyChallenge extends FixedAnswerChallenge { + + private final String vaultPasswordString; + private final VaultTemplate vaultTemplate; + + private final VaultProperties.AuthenticationMethod authenticationMethod; + + public VaultSubKeyChallenge( + @Value("${vaultpassword}") String vaultPasswordString, + @Nullable VaultTemplate vaultTemplate, + @Value("${spring.cloud.vault.authentication}") + VaultProperties.AuthenticationMethod vaultAuthmethod) { + this.vaultPasswordString = vaultPasswordString; + this.vaultTemplate = vaultTemplate; + this.authenticationMethod = vaultAuthmethod; + } + + @Override + public String getAnswer() { + try { + if (VaultProperties.AuthenticationMethod.NONE.equals(authenticationMethod) + || vaultTemplate == null) { + log.warn("Vault not setup for challenge 45"); + return vaultPasswordString; + } + VaultVersionedKeyValueOperations versionedOperations = + vaultTemplate.opsForVersionedKeyValue("secret"); + Versioned> versioned = versionedOperations.get("wrongsecret"); + if (versioned == null) { + return vaultPasswordString; + } + Optional first = versioned.getRequiredData().keySet().stream().findFirst(); + return first.orElse(vaultPasswordString); + + } catch (Exception e) { + log.warn("Exception during execution of challenge45", e); + } + return vaultPasswordString; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 305cd57a8..c724a76ba 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,6 +12,12 @@ CHALLENGE33=if_you_see_this_please_use_k8s ARG_BASED_PASSWORD=if_you_see_this_please_use_docker_instead DOCKER_ENV_PASSWORD=if_you_see_this_please_use_docker_instead vaultpassword=if_you_see_this_please_use_K8S_and_Vault +spring.cloud.vault.uri=https://tobediefined.org +spring.cloud.vault.authentication=NONE +spring.cloud.vault.role=none +spring.cloud.vault.kubernetes-path=none +spring.cloud.vault.scheme=https://tobediefined.org +spring.cloud.vault.kubernetes.service-account-token-file="none" default_aws_value=if_you_see_this_please_use_AWS_Setup default_aws_value_challenge_9=if_you_see_this_please_use_AWS_Setup default_aws_value_challenge_10=if_you_see_this_please_use_AWS_Setup diff --git a/src/main/resources/explanations/challenge44.adoc b/src/main/resources/explanations/challenge44.adoc new file mode 100644 index 000000000..e2aa38447 --- /dev/null +++ b/src/main/resources/explanations/challenge44.adoc @@ -0,0 +1,9 @@ +=== Vault Metadata Challenge + +Secrets management systems now often have metadata support for their secrets! This is awesome, as it allows you to enrich the secret with contextual data further, making it easier to remember the secret. + +But what if you put confidential/secret information into a secret by mistake? + +A developer has put secret metadata on a `wrongsecret` in Vault. Can you find it? + +Tip: take a look at the policies when vault is installed; you can see that the application is only allowed to use the metadata ;-). diff --git a/src/main/resources/explanations/challenge44_hint.adoc b/src/main/resources/explanations/challenge44_hint.adoc new file mode 100644 index 000000000..25d8bb6d5 --- /dev/null +++ b/src/main/resources/explanations/challenge44_hint.adoc @@ -0,0 +1,27 @@ +This challenge can be solved using the following steps: + +1. Find the secret with the commandline +- use `kubectl exec vault-0 -n vault -- vault kv metadata get -mount=secret wrongsecret` take a look at the metadata: do you see a map with a `secret`? that's the value you need to enter + + +2. Find the Secret in Vault using the logged root token: +- When you setup the K8s environment, the script tells you the value of the root token as below: + + Key Value + --- ----- + token s.Jqka4lSy8ayQw2LFsvyAgnTI + token_accessor HEr9RYa3OcZNDOHeFRXIMYCV + token_duration ∞ + token_renewable false + token_policies ["root"] + identity_policies [] + policies ["root"] + + +- Use the token to login into Vault exposed at port 8200 +- Take a look around: can you find the location of the secret in the secrets overview? + +3. Find the secret as the SRE member +- go to the vault web interface +- login with in with username "helper" and password "foo" +- find the actual secret. diff --git a/src/main/resources/explanations/challenge44_reason.adoc b/src/main/resources/explanations/challenge44_reason.adoc new file mode 100644 index 000000000..fce2d123c --- /dev/null +++ b/src/main/resources/explanations/challenge44_reason.adoc @@ -0,0 +1,6 @@ +*Why putting sensitive data as metadata is a bad idea* + +Sometimes, people reason that less sensitive data should be stored as secret metadata. Think of, for instance, a username - less sensitive than a password, or is it? +In many of these cases, these are equally important and should get equal protection as the secret (e.g. the password) itself. + +We often don't want to give read access to secrets to our employees, but we do want to give read access to metadata instead. If any secret is stored in the metadata, that secret is then compromised internally. diff --git a/src/main/resources/explanations/challenge45.adoc b/src/main/resources/explanations/challenge45.adoc new file mode 100644 index 000000000..cc491aff3 --- /dev/null +++ b/src/main/resources/explanations/challenge45.adoc @@ -0,0 +1,4 @@ +=== Vault subkey challenge + +Sometimes, all you want to do is have that concise entry in your secrets management system. So, what about storing your username and password in the same entry? +We tried doing that but got into a new problem! With Hashicorp Vault, you can set up policies to allow access to a subkey (Which is the key to the value of your secret). Can you find the very random username we set up for this challenge? diff --git a/src/main/resources/explanations/challenge45_hint.adoc b/src/main/resources/explanations/challenge45_hint.adoc new file mode 100644 index 000000000..c4319dcde --- /dev/null +++ b/src/main/resources/explanations/challenge45_hint.adoc @@ -0,0 +1,27 @@ +This challenge can be solved using the following steps: + +1. Find the secret with the commandline +- use `kubectl exec vault-0 -n vault -- vault kv get secret/wrongsecret` to find the data. + + +2. Find the Secret in Vault using the logged root token: +- When you setup the K8s environment, the script tells you the value of the root token as below: + + Key Value + --- ----- + token s.Jqka4lSy8ayQw2LFsvyAgnTI + token_accessor HEr9RYa3OcZNDOHeFRXIMYCV + token_duration ∞ + token_renewable false + token_policies ["root"] + identity_policies [] + policies ["root"] + + +- Use the token to login into Vault exposed at port 8200 +- Take a look around: can you find the location of the secret in the secrets overview? + +3. Find the secret as the SRE member +- go to the vault web interface +- login with in with username "helper" and password "foo" +- find the actual secret. diff --git a/src/main/resources/explanations/challenge45_reason.adoc b/src/main/resources/explanations/challenge45_reason.adoc new file mode 100644 index 000000000..ff5e1924e --- /dev/null +++ b/src/main/resources/explanations/challenge45_reason.adoc @@ -0,0 +1,6 @@ +*Why putting sensitive data as keys is a bad idea* + +Sometimes, people reason that less sensitive data should be stored as a subkey of the actual secret. That way, both a username and a password, for instance, can be combined in a single entry. +In many cases, these secrets are equally important and should get equal protection as the secret (e.g. the password) itself. And in Vault's case, you can access a subkey (E.g., the username), but not the secret value itself (e.g., the password), which would already leak the username. + +We often don't want to give read access to secrets to our employees, but we do want to provide read access to subkeys instead. If any secret is stored in the subkeys, that secret is then compromised internally. diff --git a/src/main/resources/templates/about.html b/src/main/resources/templates/about.html index 1e5bac12d..a3e32690c 100644 --- a/src/main/resources/templates/about.html +++ b/src/main/resources/templates/about.html @@ -35,7 +35,7 @@ The list below is generated with `mvn license:add-third-party`
    -
  • Lists of 350 third-party dependencies.
  • +
  • Lists of 351 third-party dependencies.
  • (Eclipse Public License - v 1.0) (GNU Lesser General Public License) Logback Classic Module (ch.qos.logback:logback-classic:1.4.14 - http://logback.qos.ch/logback-classic)
  • (Eclipse Public License - v 1.0) (GNU Lesser General Public License) Logback Core Module (ch.qos.logback:logback-core:1.4.14 - http://logback.qos.ch/logback-core)
  • (The MIT License (MIT)) Microsoft Azure Java Core Library (com.azure:azure-core:1.45.1 - https://github.com/Azure/azure-sdk-for-java)
  • @@ -346,6 +346,7 @@
  • (Apache 2.0) spring-security-rsa (org.springframework.security:spring-security-rsa:1.1.1 - http://github.com/spring-projects/spring-security-oauth)
  • (Apache License, Version 2.0) spring-security-web (org.springframework.security:spring-security-web:6.2.1 - https://spring.io/projects/spring-security)
  • (Apache License, Version 2.0) Spring Vault Core (org.springframework.vault:spring-vault-core:3.1.0 - https://projects.spring.io/spring-vault/spring-vault-core/)
  • +
  • (MIT) Testcontainers :: JUnit Jupiter Extension (org.testcontainers:junit-jupiter:1.19.3 - https://java.testcontainers.org)
  • (BSD-3-Clause) ThreeTen backport (org.threeten:threetenbp:1.6.8 - https://www.threeten.org/threetenbp)
  • (The Apache Software License, Version 2.0) thymeleaf (org.thymeleaf:thymeleaf:3.1.2.RELEASE - http://www.thymeleaf.org/thymeleaf-lib/thymeleaf)
  • (The Apache Software License, Version 2.0) thymeleaf-spring6 (org.thymeleaf:thymeleaf-spring6:3.1.2.RELEASE - http://www.thymeleaf.org/thymeleaf-lib/thymeleaf-spring6)
  • diff --git a/src/main/resources/wrong-secrets-configuration.yaml b/src/main/resources/wrong-secrets-configuration.yaml index f822e1208..a13b1393c 100644 --- a/src/main/resources/wrong-secrets-configuration.yaml +++ b/src/main/resources/wrong-secrets-configuration.yaml @@ -710,3 +710,31 @@ configurations: category: *doc ctf: enabled: true + + - name: Challenge 44 + short-name: "challenge-44" + sources: + - class-name: "org.owasp.wrongsecrets.challenges.kubernetes.MetaDataChallenge" + explanation: "explanations/challenge44.adoc" + hint: "explanations/challenge44_hint.adoc" + reason: "explanations/challenge44_reason.adoc" + environments: [ *gcp, *aws, *azure, *k8s_vault ] + difficulty: *expert + category: *vault + ctf: + enabled: true + missing_environment: "explanations/missing_vault.adoc" + + - name: Challenge 45 + short-name: "challenge-45" + sources: + - class-name: "org.owasp.wrongsecrets.challenges.kubernetes.VaultSubKeyChallenge" + explanation: "explanations/challenge45.adoc" + hint: "explanations/challenge45_hint.adoc" + reason: "explanations/challenge45_reason.adoc" + environments: [ *gcp, *aws, *azure, *k8s_vault ] + difficulty: *expert + category: *vault + ctf: + enabled: true + missing_environment: "explanations/missing_vault.adoc" diff --git a/src/test/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge44Test.java b/src/test/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge44Test.java new file mode 100644 index 000000000..3a217dbd0 --- /dev/null +++ b/src/test/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge44Test.java @@ -0,0 +1,63 @@ +package org.owasp.wrongsecrets.challenges.kubernetes; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.springframework.cloud.vault.config.VaultProperties; +import org.springframework.vault.authentication.TokenAuthentication; +import org.springframework.vault.client.VaultEndpoint; +import org.springframework.vault.core.VaultTemplate; +import org.testcontainers.containers.Container.ExecResult; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.vault.VaultContainer; + +@Testcontainers +public class Challenge44Test { + private static final String VAULT_TOKEN = "my-token"; + + @Container + public static VaultContainer vaultContainer = + new VaultContainer<>("hashicorp/vault:1.13") + .withVaultToken(VAULT_TOKEN) + .withInitCommand("secrets enable transit"); + + @Test + public void readFirstSecretPathWithCli() throws Exception { + var putSecretResult = + vaultContainer.execInContainer( + "vault", + "kv", + "put", + "secret/wrongsecret", + "vaultpassword.password='$(openssl rand -base64 16)'"); + assertThat(putSecretResult.getStdout()).contains("secret/data/wrongsecret"); + + var putResult = + vaultContainer.execInContainer( + "vault", + "kv", + "metadata", + "put", + "-mount=secret", + "-custom-metadata=secret=test", + "wrongsecret"); + + assertThat(putResult.getStdout()) + .contains("Success! Data written to: secret/metadata/wrongsecret"); + + ExecResult readResult = + vaultContainer.execInContainer( + "vault", "kv", "metadata", "get", "-mount=secret", "wrongsecret"); + assertThat(readResult.getStdout()).contains("map[secret:test]"); + String address = vaultContainer.getHttpHostAddress(); + assertThat(readResult.getStdout()).contains("test"); + + var metadataChallenge = + new MetaDataChallenge( + "ACTUAL_ANSWER_CHALLENGE7", + new VaultTemplate(VaultEndpoint.from(address), new TokenAuthentication(VAULT_TOKEN)), + VaultProperties.AuthenticationMethod.TOKEN); + assertThat(metadataChallenge.spoiler().solution()).isEqualTo("test"); + } +} diff --git a/src/test/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge45Test.java b/src/test/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge45Test.java new file mode 100644 index 000000000..916664e58 --- /dev/null +++ b/src/test/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge45Test.java @@ -0,0 +1,42 @@ +package org.owasp.wrongsecrets.challenges.kubernetes; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.springframework.cloud.vault.config.VaultProperties; +import org.springframework.vault.authentication.TokenAuthentication; +import org.springframework.vault.client.VaultEndpoint; +import org.springframework.vault.core.VaultTemplate; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.vault.VaultContainer; + +@Testcontainers +public class Challenge45Test { + private static final String VAULT_TOKEN = "my-token"; + + @Container + public static VaultContainer vaultContainer = + new VaultContainer<>("hashicorp/vault:1.13") + .withVaultToken(VAULT_TOKEN) + .withInitCommand("secrets enable transit"); + + @Test + public void readFirstSecretPathWithCli() throws Exception { + var putSecretResult = + vaultContainer.execInContainer( + "vault", + "kv", + "put", + "secret/wrongsecret", + "aaasecret.password='$(openssl rand -base64 16)'"); + assertThat(putSecretResult.getStdout()).contains("secret/data/wrongsecret"); + String address = vaultContainer.getHttpHostAddress(); + var subkeyChallenge = + new VaultSubKeyChallenge( + "ACTUAL_ANSWER_CHALLENGE7", + new VaultTemplate(VaultEndpoint.from(address), new TokenAuthentication(VAULT_TOKEN)), + VaultProperties.AuthenticationMethod.TOKEN); + assertThat(subkeyChallenge.spoiler().solution()).isEqualTo("aaasecret.password"); + } +} diff --git a/src/test/java/org/owasp/wrongsecrets/challenges/kubernetes/ChallengesControllerWithPresetKubernetesValuesTest.java b/src/test/java/org/owasp/wrongsecrets/challenges/kubernetes/ChallengesControllerWithPresetKubernetesValuesTest.java index f0ada3c76..62e036bcd 100644 --- a/src/test/java/org/owasp/wrongsecrets/challenges/kubernetes/ChallengesControllerWithPresetKubernetesValuesTest.java +++ b/src/test/java/org/owasp/wrongsecrets/challenges/kubernetes/ChallengesControllerWithPresetKubernetesValuesTest.java @@ -53,7 +53,9 @@ void shouldNotShowDisabledChallengeAnywhere() throws Exception { if (shortname.contains("7") || shortname.contains("9") || shortname.contains("10") - || shortname.contains("11")) { + || shortname.contains("11") + || shortname.contains("44") + || shortname.contains("45")) { continue; } mvc.perform(get("/challenge/%s".formatted(challenge.name().shortName())))