From 04ee7c223f276cf81414903f262956ae56ebbbf9 Mon Sep 17 00:00:00 2001 From: Nanne Baars Date: Fri, 27 Oct 2023 12:20:18 +0200 Subject: [PATCH 01/27] refactor: configure a challenge in a configuration file. --- .github/scripts/.bash_history | 2 +- .github/workflows/minikube-k8s-test.yml | 24 +- .github/workflows/minikube-vault-test.yml | 2 +- .gitignore | 5 +- CONTRIBUTING.md | 77 +- Dockerfile | 2 +- README.md | 84 +-- k8s-vault-minkube-start.sh | 2 +- pom.xml | 87 +-- .../wrongsecrets/AllControllerAdvice.java | 38 +- .../ChallengeUiTemplateResolver.java | 93 +++ .../org/owasp/wrongsecrets/Challenges.java | 106 +++ .../wrongsecrets/ConfigurationException.java | 19 + .../wrongsecrets/FailtoStartupException.java | 16 - .../owasp/wrongsecrets/InMemoryScoreCard.java | 44 +- .../owasp/wrongsecrets/MvcConfiguration.java | 14 +- .../wrongsecrets/RuntimeEnvironment.java | 104 +-- .../org/owasp/wrongsecrets/ScoreCard.java | 18 +- .../owasp/wrongsecrets/StartupListener.java | 46 -- .../wrongsecrets/WrongSecretsApplication.java | 18 +- .../asciidoc/AsciiDoctorTemplateResolver.java | 2 +- .../wrongsecrets/challenges/Challenge.java | 112 +-- .../wrongsecrets/challenges/ChallengeUI.java | 157 ++-- .../challenges/ChallengesAPIController.java | 153 ---- .../challenges/ChallengesController.java | 184 +++-- .../challenges/ChallengesCtfController.java | 87 +++ .../wrongsecrets/challenges/Difficulty.java | 17 - .../challenges/cloud/Challenge10.java | 41 +- .../challenges/cloud/Challenge9.java | 43 +- .../challenges/cloud/CloudChallenge.java | 75 -- .../Challenge11Aws.java} | 123 +--- .../cloud/challenge11/Challenge11Azure.java | 70 ++ .../cloud/challenge11/Challenge11Gcp.java | 81 +++ .../challenges/docker/Challenge0.java | 43 +- .../challenges/docker/Challenge1.java | 42 +- .../challenges/docker/Challenge12.java | 41 +- .../challenges/docker/Challenge13.java | 44 +- .../challenges/docker/Challenge14.java | 41 +- .../challenges/docker/Challenge15.java | 42 +- .../challenges/docker/Challenge16.java | 41 +- .../challenges/docker/Challenge17.java | 41 +- .../challenges/docker/Challenge18.java | 43 +- .../challenges/docker/Challenge19.java | 43 +- .../challenges/docker/Challenge2.java | 44 +- .../challenges/docker/Challenge20.java | 43 +- .../challenges/docker/Challenge21.java | 43 +- .../challenges/docker/Challenge22.java | 43 +- .../challenges/docker/Challenge23.java | 41 +- .../challenges/docker/Challenge24.java | 41 +- .../challenges/docker/Challenge25.java | 40 +- .../challenges/docker/Challenge26.java | 40 +- .../challenges/docker/Challenge27.java | 40 +- .../challenges/docker/Challenge28.java | 41 +- .../challenges/docker/Challenge29.java | 43 +- .../challenges/docker/Challenge3.java | 43 +- .../challenges/docker/Challenge31.java | 41 +- .../challenges/docker/Challenge32.java | 40 +- .../challenges/docker/Challenge34.java | 40 +- .../challenges/docker/Challenge35.java | 40 +- .../challenges/docker/Challenge36.java | 40 +- .../challenges/docker/Challenge37.java | 38 +- .../challenges/docker/Challenge38.java | 40 +- .../challenges/docker/Challenge39.java | 38 +- .../challenges/docker/Challenge4.java | 42 +- .../challenges/docker/Challenge40.java | 40 +- .../challenges/docker/Challenge41.java | 37 +- .../challenges/docker/Challenge8.java | 42 +- ...uthenticatedRestControllerChallenge37.java | 6 +- .../docker/{ => challenge30}/Challenge30.java | 42 +- .../challenge30}/ChallengeRestController.java | 3 +- .../challenges/kubernetes/Challenge33.java | 41 +- .../challenges/kubernetes/Challenge5.java | 42 +- .../challenges/kubernetes/Challenge6.java | 42 +- .../challenges/kubernetes/Challenge7.java | 41 +- .../wrongsecrets/definitions/Category.java | 8 + .../definitions/ChallengeConfig.java | 77 ++ .../definitions/ChallengeDefinition.java | 69 ++ .../ChallengeDefinitionsConfiguration.java | 18 + .../definitions/ChallengeName.java | 21 + .../owasp/wrongsecrets/definitions/Ctf.java | 9 + .../wrongsecrets/definitions/Difficulty.java | 8 + .../wrongsecrets/definitions/Environment.java | 17 + .../wrongsecrets/definitions/Navigation.java | 35 + .../wrongsecrets/definitions/Sources.java | 44 ++ .../wrongsecrets/definitions/Technology.java | 4 + src/main/resources/META-INF/spring.factories | 1 - src/main/resources/application.properties | 1 + .../challenges/challenge-16/challenge-16.js | 11 + .../challenges/challenge-30/challenge-30.js | 5 + .../resources/challenges/missing_cloud.adoc | 2 + .../resources/challenges/missing_docker.adoc | 4 + .../resources/challenges/missing_k8s.adoc | 3 + .../resources/challenges/missing_vault.adoc | 2 + src/main/resources/templates/challenge.html | 56 +- .../resources/templates/fragments/header.html | 4 +- .../templates/fragments/navigation.html | 4 +- src/main/resources/templates/index.html | 19 +- src/main/resources/templates/welcome.html | 20 +- .../wrong-secrets-configuration.yaml | 679 ++++++++++++++++++ .../test/e2e/cypress.config.js | 5 + {cypress => src/test/e2e/cypress}/README.md | 16 +- .../e2e/cypress/integration}/challenges.cy.js | 50 +- .../e2e/cypress/integration}/spoilers.cy.js | 6 +- .../cypress/integration}/themeSwitch.cy.js | 10 +- .../test/e2e/cypress}/pages/challengesPage.js | 8 +- .../test/e2e/cypress}/pages/homePage.js | 2 +- .../test/e2e/cypress}/pages/spoilersPage.js | 0 .../e2e/cypress}/pages/themeSwitchPage.js | 0 .../test/e2e/cypress}/support/commands.js | 21 +- .../test/e2e/cypress}/support/e2e.js | 0 package.json => src/test/e2e/package.json | 8 +- src/test/e2e/reporter-config.json | 9 + .../wrongsecrets/AboutControllerTests.java | 36 +- .../Challenge30IntegrationTest.java | 39 - .../ChallengeAPiControllerTest.java | 34 +- .../owasp/wrongsecrets/ChallengeNameTest.java | 21 + .../wrongsecrets/ChallengeUILimittedTest.java | 38 - .../ChallengesControllerTest.java | 85 +-- .../owasp/wrongsecrets/CodingRuleTest.java | 32 + .../wrongsecrets/CypressIntegrationTest.java | 83 +++ .../wrongsecrets/InMemoryScoreCardTest.java | 39 - .../wrongsecrets/RuntimeEnvironmentTest.java | 53 -- .../SecretLeakageControllerTest.java | 56 +- .../SecretsErrorControllerTest.java | 34 +- .../wrongsecrets/SecurityConfigTest.java | 6 +- .../org/owasp/wrongsecrets/SpringDocTest.java | 2 +- .../StartupListenerErrorTest.java | 47 -- .../StartupListenerSuccessTest.java | 27 - .../wrongsecrets/StatsControllerTests.java | 36 +- .../canaries/CanaryCallbackTest.java | 37 +- .../challenges/cloud/Challenge10Test.java | 36 +- .../challenges/cloud/Challenge9Test.java | 11 +- .../docker/BinaryExectionHelperTest.java | 3 - .../challenges/docker/Challenge12Test.java | 25 +- .../challenges/docker/Challenge13Test.java | 30 +- .../challenges/docker/Challenge14Test.java | 24 +- .../challenges/docker/Challenge15Test.java | 8 - .../challenges/docker/Challenge16Test.java | 25 +- .../challenges/docker/Challenge17Test.java | 13 +- .../challenges/docker/Challenge18Test.java | 25 +- .../challenges/docker/Challenge19Test.java | 19 +- .../challenges/docker/Challenge1Test.java | 26 +- .../challenges/docker/Challenge20Test.java | 19 +- .../challenges/docker/Challenge21Test.java | 19 +- .../challenges/docker/Challenge22Test.java | 19 +- .../challenges/docker/Challenge23Test.java | 16 +- .../challenges/docker/Challenge24Test.java | 16 +- .../challenges/docker/Challenge25Test.java | 15 +- .../challenges/docker/Challenge26Test.java | 15 +- .../challenges/docker/Challenge27Test.java | 17 +- .../challenges/docker/Challenge28Test.java | 24 +- .../challenges/docker/Challenge29Test.java | 26 +- .../challenges/docker/Challenge30Test.java | 22 - .../challenges/docker/Challenge31Test.java | 22 +- .../challenges/docker/Challenge32Test.java | 18 +- .../challenges/docker/Challenge34Test.java | 18 +- .../challenges/docker/Challenge35Test.java | 18 +- .../challenges/docker/Challenge36Test.java | 24 +- .../challenges/docker/Challenge37Test.java | 18 +- .../challenges/docker/Challenge38Test.java | 6 +- .../challenges/docker/Challenge39Test.java | 7 +- .../challenges/docker/Challenge3Test.java | 20 +- .../challenges/docker/Challenge40Test.java | 7 +- .../challenges/docker/Challenge41Test.java | 11 +- .../challenges/docker/Challenge4Test.java | 30 +- .../challenges/docker/Challenge8Test.java | 31 +- .../docker/challenge30/Challenge30Test.java | 20 + .../kubernetes/Challenge33Test.java | 24 +- .../challenges/kubernetes/Challenge5Test.java | 17 +- .../challenges/kubernetes/Challenge6Test.java | 17 +- .../challenges/kubernetes/Challenge7Test.java | 23 +- ...ChallengesControllerCTFClientModeTest.java | 40 +- .../ChallengesControllerCTFModeTest.java | 34 +- ...ollerCTFModeWithPresetCloudValuesTest.java | 72 +- ...TFModeWithPresetK8sAndVaultValuesTest.java | 22 +- ...CTFModeWithPresetK8sNoVaultValuesTest.java | 28 +- .../oauth/TokenControllerTest.java | 2 +- 177 files changed, 2729 insertions(+), 3778 deletions(-) create mode 100644 src/main/java/org/owasp/wrongsecrets/ChallengeUiTemplateResolver.java create mode 100644 src/main/java/org/owasp/wrongsecrets/Challenges.java create mode 100644 src/main/java/org/owasp/wrongsecrets/ConfigurationException.java delete mode 100644 src/main/java/org/owasp/wrongsecrets/FailtoStartupException.java delete mode 100644 src/main/java/org/owasp/wrongsecrets/StartupListener.java delete mode 100644 src/main/java/org/owasp/wrongsecrets/challenges/ChallengesAPIController.java create mode 100644 src/main/java/org/owasp/wrongsecrets/challenges/ChallengesCtfController.java delete mode 100644 src/main/java/org/owasp/wrongsecrets/challenges/Difficulty.java delete mode 100644 src/main/java/org/owasp/wrongsecrets/challenges/cloud/CloudChallenge.java rename src/main/java/org/owasp/wrongsecrets/challenges/cloud/{Challenge11.java => challenge11/Challenge11Aws.java} (52%) create mode 100644 src/main/java/org/owasp/wrongsecrets/challenges/cloud/challenge11/Challenge11Azure.java create mode 100644 src/main/java/org/owasp/wrongsecrets/challenges/cloud/challenge11/Challenge11Gcp.java rename src/main/java/org/owasp/wrongsecrets/challenges/docker/{ => challenge30}/Challenge30.java (54%) rename src/main/java/org/owasp/wrongsecrets/challenges/{ => docker/challenge30}/ChallengeRestController.java (85%) create mode 100644 src/main/java/org/owasp/wrongsecrets/definitions/Category.java create mode 100644 src/main/java/org/owasp/wrongsecrets/definitions/ChallengeConfig.java create mode 100644 src/main/java/org/owasp/wrongsecrets/definitions/ChallengeDefinition.java create mode 100644 src/main/java/org/owasp/wrongsecrets/definitions/ChallengeDefinitionsConfiguration.java create mode 100644 src/main/java/org/owasp/wrongsecrets/definitions/ChallengeName.java create mode 100644 src/main/java/org/owasp/wrongsecrets/definitions/Ctf.java create mode 100644 src/main/java/org/owasp/wrongsecrets/definitions/Difficulty.java create mode 100644 src/main/java/org/owasp/wrongsecrets/definitions/Environment.java create mode 100644 src/main/java/org/owasp/wrongsecrets/definitions/Navigation.java create mode 100644 src/main/java/org/owasp/wrongsecrets/definitions/Sources.java create mode 100644 src/main/java/org/owasp/wrongsecrets/definitions/Technology.java delete mode 100644 src/main/resources/META-INF/spring.factories create mode 100644 src/main/resources/challenges/challenge-16/challenge-16.js create mode 100644 src/main/resources/challenges/challenge-30/challenge-30.js create mode 100644 src/main/resources/challenges/missing_cloud.adoc create mode 100644 src/main/resources/challenges/missing_docker.adoc create mode 100644 src/main/resources/challenges/missing_k8s.adoc create mode 100644 src/main/resources/challenges/missing_vault.adoc create mode 100644 src/main/resources/wrong-secrets-configuration.yaml rename cypress.config.js => src/test/e2e/cypress.config.js (59%) rename {cypress => src/test/e2e/cypress}/README.md (62%) rename {cypress/e2e => src/test/e2e/cypress/integration}/challenges.cy.js (65%) rename {cypress/e2e => src/test/e2e/cypress/integration}/spoilers.cy.js (76%) rename {cypress/e2e => src/test/e2e/cypress/integration}/themeSwitch.cy.js (75%) rename {cypress => src/test/e2e/cypress}/pages/challengesPage.js (89%) rename {cypress => src/test/e2e/cypress}/pages/homePage.js (72%) rename {cypress => src/test/e2e/cypress}/pages/spoilersPage.js (100%) rename {cypress => src/test/e2e/cypress}/pages/themeSwitchPage.js (100%) rename {cypress => src/test/e2e/cypress}/support/commands.js (57%) rename {cypress => src/test/e2e/cypress}/support/e2e.js (100%) rename package.json => src/test/e2e/package.json (67%) create mode 100644 src/test/e2e/reporter-config.json delete mode 100644 src/test/java/org/owasp/wrongsecrets/Challenge30IntegrationTest.java create mode 100644 src/test/java/org/owasp/wrongsecrets/ChallengeNameTest.java delete mode 100644 src/test/java/org/owasp/wrongsecrets/ChallengeUILimittedTest.java create mode 100644 src/test/java/org/owasp/wrongsecrets/CodingRuleTest.java create mode 100644 src/test/java/org/owasp/wrongsecrets/CypressIntegrationTest.java delete mode 100644 src/test/java/org/owasp/wrongsecrets/InMemoryScoreCardTest.java delete mode 100644 src/test/java/org/owasp/wrongsecrets/RuntimeEnvironmentTest.java delete mode 100644 src/test/java/org/owasp/wrongsecrets/StartupListenerErrorTest.java delete mode 100644 src/test/java/org/owasp/wrongsecrets/StartupListenerSuccessTest.java delete mode 100644 src/test/java/org/owasp/wrongsecrets/challenges/docker/Challenge30Test.java create mode 100644 src/test/java/org/owasp/wrongsecrets/challenges/docker/challenge30/Challenge30Test.java diff --git a/.github/scripts/.bash_history b/.github/scripts/.bash_history index 07a1ae38e..4948060f1 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="4QMUMCz8BUpSizlIfnb0XPpCDGir1NyfIxkrkN3Emdc=" +export tempPassword="Placeholder Password, find the real one in the history of the container" mvn run tempPassword k6 npx k6 diff --git a/.github/workflows/minikube-k8s-test.yml b/.github/workflows/minikube-k8s-test.yml index a846e9c73..ce141db09 100644 --- a/.github/workflows/minikube-k8s-test.yml +++ b/.github/workflows/minikube-k8s-test.yml @@ -44,29 +44,29 @@ jobs: echo "Do minikube delete to stop minikube from running and cleanup to start fresh again" echo "wait 20 seconds so we can check if vault-k8s-container works" sleep 20 - if curl http://localhost:8080/spoil-5 -s | grep -q spoiler-answer + if curl http://localhost:8080/spoil/challenge-5 -s | grep -q spoiler-answer then - echo "spoil-5 works" + echo "spoil-challenge-5 works" else - echo "error in spoil-5" + echo "error in spoil-challenge-5" fi - if curl http://localhost:8080/spoil-6 -s | grep -q spoiler-answer + if curl http://localhost:8080/spoil/challenge-6 -s | grep -q spoiler-answer then - echo "spoil-6 works" + echo "spoil-challenge-6 works" else - echo "error in spoil-6" + echo "error in spoil-challenge-6" fi - if curl http://localhost:8080/spoil-7 -s | grep -q spoiler-answer + if curl http://localhost:8080/spoil/challenge-7 -s | grep -q spoiler-answer then - echo "spoil-7 works" + echo "spoil-challenge-7 works" else - echo "error in spoil-7" + echo "error in spoil-challenge-7" fi - if curl http://localhost:8080/spoil-33 -s | grep -q spoiler-answer + if curl http://localhost:8080/spoil/challenge-33 -s | grep -q spoiler-answer then - echo "spoil-33 works" + echo "spoil-challenge-33 works" else - echo "error in spoil-33" + echo "error in spoil-challenge-33" fi echo "logs from pod to make sure:" cat pod.log diff --git a/.github/workflows/minikube-vault-test.yml b/.github/workflows/minikube-vault-test.yml index b4d0db46d..e0e30ad75 100644 --- a/.github/workflows/minikube-vault-test.yml +++ b/.github/workflows/minikube-vault-test.yml @@ -33,4 +33,4 @@ jobs: id: install - name: test script run: | - ./k8s-vault-minkube-start.sh && sleep 5 && curl http://localhost:8080/spoil-7 && minikube delete + ./k8s-vault-minkube-start.sh && sleep 5 && curl http://localhost:8080/spoil/challenge-7 && minikube delete diff --git a/.gitignore b/.gitignore index 6b7b03674..993514ec9 100644 --- a/.gitignore +++ b/.gitignore @@ -78,8 +78,9 @@ node_modules .npm # Cypress -cypress/videos -cypress/screenshots +src/test/e2e/cypress/videos +src/test/e2e/cypress/screenshots +src/test/e2e/cypress/reports cypress/downloads py_env tmp/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3fe4b6b6..5f43cd385 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -268,7 +268,6 @@ First make sure that you have an [Issue](https://github.com/OWASP/wrongsecrets/i Add the **new challenge** in this folder `wrongsecrets/src/main/java/org/owasp/wrongsecrets/challenges/`. These are the things that you have to keep in mind. - First and foremost make sure your challenge is coded in **Java**. -- Don't forget to add your challenge number in `@Order(28)` annotation, **_28_** in my case. - Here is an example of a possible Challenge 28: ```java @@ -287,62 +286,31 @@ These are the things that you have to keep in mind. */ @Slf4j @Component - @Order(28) //make sure this number is the same as your challenge - public class Challenge28 extends Challenge { + public class Challenge28 implements Challenge { private final String secret; - public Challenge28(ScoreCard scoreCard) { - super(scoreCard); - secret = "hello world"; - } - //is this challenge usable in CTF mode? - @Override - public boolean canRunInCTFMode() { - return true; + public Challenge28() { + secret = "hello world"; } + //return the plain text secret here @Override public Spoiler spoiler() { - return new Spoiler(secret); + return new Spoiler(secret); } //here you validate if your answer matches the secret @Override public boolean answerCorrect(String answer) { - return secret.equals(answer); - } - //which runtime can you use to run the challenge on? (You can just use Docker here) - /** - * {@inheritDoc} - */ - @Override - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - //set the difficulty: 1=low, 5=very hard - /** - * {@inheritDoc} - * Difficulty: 1. - */ - @Override - public int difficulty() { - return 1; - } - //on which tech is this challenge? See ChallengeTechnology.Tech for categories - /** - * {@inheritDoc} - * Secrets based. - */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.SECRETS.id; + return secret.equals(answer); } + //if you use this in a shared environment and need to adapt it, then return true here. @Override public boolean isLimittedWhenOnlineHosted() { return false; - - } - } + } +} ``` + ### Step 3: Adding Test File. Add the **new TestFile** in this folder `wrongsecrets/src/test/java/org/owasp/wrongsecrets/challenges/`. TestFile is required to do **unit testing.** @@ -410,5 +378,28 @@ Use this block as refrence for hints: This challenge is only meant for helping new contributors to add new challenges. Please, have fun with trying more difficult challenges;-). ``` -### Step 5: Submitting your PR. + +### Step 5: Add challenge configuration. + +In this step we configure the challenge to make it known to the application. +Open `src/main/resources/wrong_secrets_configuration.yaml` and add the following configuration: + +```yaml + - name: Challenge 28 + url: "challenge-28" + # For each environment you can add a different implementation and documentation + sources: + # Fully qualified name of the class + - class-name: "org.owasp.wrongsecrets.challenges.docker.Challenge28" + explanation: "explanations/challenge28.adoc" + hint: "explanations/challenge28_hint.adoc" + reason: "explanations/challenge28_reason.adoc" + environments: *docker_envs + difficulty: *easy + category: *secrets + ctf: + enabled: true +``` + +### Step 6: Submitting your PR. After completing all the above steps, final step is to submit the PR and refer [**Contributing.md**](https://github.com/OWASP/wrongsecrets/blob/master/CONTRIBUTING.md#how-to-get-your-pr-accepted) on how to get your PR accepted. diff --git a/Dockerfile b/Dockerfile index fdfb62d09..12d9a8d5d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM amazoncorretto:21.0.1-alpine ARG argBasedPassword="default" -ARG argBasedVersion="0.0.0" +ARG argBasedVersion="1.7.2" ARG spring_profile="" ENV SPRING_PROFILES_ACTIVE=$spring_profile ENV ARG_BASED_PASSWORD=$argBasedPassword diff --git a/README.md b/README.md index 5b0d09661..877dd81ee 100644 --- a/README.md +++ b/README.md @@ -87,40 +87,40 @@ docker run -p 8080:8080 jeroenwillemsen/wrongsecrets:latest-no-vault Now you can try to find the secrets by means of solving the challenge offered at: -- [localhost:8080/challenge/1](http://localhost:8080/challenge/1) -- [localhost:8080/challenge/2](http://localhost:8080/challenge/2) -- [localhost:8080/challenge/3](http://localhost:8080/challenge/3) -- [localhost:8080/challenge/4](http://localhost:8080/challenge/4) -- [localhost:8080/challenge/8](http://localhost:8080/challenge/8) -- [localhost:8080/challenge/12](http://localhost:8080/challenge/12) -- [localhost:8080/challenge/13](http://localhost:8080/challenge/13) -- [localhost:8080/challenge/14](http://localhost:8080/challenge/14) -- [localhost:8080/challenge/15](http://localhost:8080/challenge/15) -- [localhost:8080/challenge/16](http://localhost:8080/challenge/16) -- [localhost:8080/challenge/17](http://localhost:8080/challenge/17) -- [localhost:8080/challenge/18](http://localhost:8080/challenge/18) -- [localhost:8080/challenge/19](http://localhost:8080/challenge/19) -- [localhost:8080/challenge/20](http://localhost:8080/challenge/20) -- [localhost:8080/challenge/21](http://localhost:8080/challenge/21) -- [localhost:8080/challenge/22](http://localhost:8080/challenge/22) -- [localhost:8080/challenge/23](http://localhost:8080/challenge/23) -- [localhost:8080/challenge/24](http://localhost:8080/challenge/24) -- [localhost:8080/challenge/25](http://localhost:8080/challenge/25) -- [localhost:8080/challenge/26](http://localhost:8080/challenge/26) -- [localhost:8080/challenge/27](http://localhost:8080/challenge/27) -- [localhost:8080/challenge/28](http://localhost:8080/challenge/28) -- [localhost:8080/challenge/29](http://localhost:8080/challenge/29) -- [localhost:8080/challenge/30](http://localhost:8080/challenge/30) -- [localhost:8080/challenge/31](http://localhost:8080/challenge/31) -- [localhost:8080/challenge/32](http://localhost:8080/challenge/32) -- [localhost:8080/challenge/34](http://localhost:8080/challenge/34) -- [localhost:8080/challenge/35](http://localhost:8080/challenge/35) -- [localhost:8080/challenge/36](http://localhost:8080/challenge/36) -- [localhost:8080/challenge/37](http://localhost:8080/challenge/37) -- [localhost:8080/challenge/38](http://localhost:8080/challenge/38) -- [localhost:8080/challenge/39](http://localhost:8080/challenge/39) -- [localhost:8080/challenge/40](http://localhost:8080/challenge/40) -- [localhost:8080/challenge/41](http://localhost:8080/challenge/41) +- [localhost:8080/challenge/challenge-1](http://localhost:8080/challenge/challenge-1) +- [localhost:8080/challenge/challenge-2](http://localhost:8080/challenge/challenge-2) +- [localhost:8080/challenge/challenge-3](http://localhost:8080/challenge/challenge-3) +- [localhost:8080/challenge/challenge-4](http://localhost:8080/challenge/challenge-4) +- [localhost:8080/challenge/challenge-8](http://localhost:8080/challenge/challenge-8) +- [localhost:8080/challenge/challenge-12](http://localhost:8080/challenge/challenge-12) +- [localhost:8080/challenge/challenge-13](http://localhost:8080/challenge/challenge-13) +- [localhost:8080/challenge/challenge-14](http://localhost:8080/challenge/challenge-14) +- [localhost:8080/challenge/challenge-15](http://localhost:8080/challenge/challenge-15) +- [localhost:8080/challenge/challenge-16](http://localhost:8080/challenge/challenge-16) +- [localhost:8080/challenge/challenge-17](http://localhost:8080/challenge/challenge-17) +- [localhost:8080/challenge/challenge-18](http://localhost:8080/challenge/challenge-18) +- [localhost:8080/challenge/challenge-19](http://localhost:8080/challenge/challenge-19) +- [localhost:8080/challenge/challenge-20](http://localhost:8080/challenge/challenge-20) +- [localhost:8080/challenge/challenge-21](http://localhost:8080/challenge/challenge-21) +- [localhost:8080/challenge/challenge-22](http://localhost:8080/challenge/challenge-22) +- [localhost:8080/challenge/challenge-23](http://localhost:8080/challenge/challenge-23) +- [localhost:8080/challenge/challenge-24](http://localhost:8080/challenge/challenge-24) +- [localhost:8080/challenge/challenge-25](http://localhost:8080/challenge/challenge-25) +- [localhost:8080/challenge/challenge-26](http://localhost:8080/challenge/challenge-26) +- [localhost:8080/challenge/challenge-27](http://localhost:8080/challenge/challenge-27) +- [localhost:8080/challenge/challenge-28](http://localhost:8080/challenge/challenge-28) +- [localhost:8080/challenge/challenge-29](http://localhost:8080/challenge/challenge-29) +- [localhost:8080/challenge/challenge-30](http://localhost:8080/challenge/challenge-30) +- [localhost:8080/challenge/challenge-31](http://localhost:8080/challenge/challenge-31) +- [localhost:8080/challenge/challenge-32](http://localhost:8080/challenge/challenge-32) +- [localhost:8080/challenge/challenge-34](http://localhost:8080/challenge/challenge-34) +- [localhost:8080/challenge/challenge-35](http://localhost:8080/challenge/challenge-35) +- [localhost:8080/challenge/challenge-36](http://localhost:8080/challenge/challenge-36) +- [localhost:8080/challenge/challenge-37](http://localhost:8080/challenge/challenge-37) +- [localhost:8080/challenge/challenge-38](http://localhost:8080/challenge/challenge-38) +- [localhost:8080/challenge/challenge-39](http://localhost:8080/challenge/challenge-39) +- [localhost:8080/challenge/challenge-40](http://localhost:8080/challenge/challenge-40) +- [localhost:8080/challenge/challenge-41](http://localhost:8080/challenge/challenge-41) Note that these challenges are still very basic, and so are their explanations. Feel free to file a PR to make them look better ;-). @@ -169,9 +169,9 @@ The K8S setup currently is based on using Minikube for local fun: now you can use the provided IP address and port to further play with the K8s variant (instead of localhost). -- [localhost:8080/challenge/5](http://localhost:8080/challenge/5) -- [localhost:8080/challenge/6](http://localhost:8080/challenge/6) -- [localhost:8080/challenge/33](http://localhost:8080/challenge/33) +- [localhost:8080/challenge/challenge-5](http://localhost:8080/challenge/challenge-5) +- [localhost:8080/challenge/challenge-6](http://localhost:8080/challenge/challenge-6) +- [localhost:8080/challenge/challenge-33](http://localhost:8080/challenge/challenge-33) ### k8s based @@ -190,9 +190,9 @@ Want to run vanilla on your own k8s? Use the commands below: now you can use the provided IP address and port to further play with the K8s variant (instead of localhost). -- [localhost:8080/challenge/5](http://localhost:8080/challenge/5) -- [localhost:8080/challenge/6](http://localhost:8080/challenge/6) -- [localhost:8080/challenge/33](http://localhost:8080/challenge/33) +- [localhost:8080/challenge/challenge-5](http://localhost:8080/challenge/challenge-5) +- [localhost:8080/challenge/challenge-6](http://localhost:8080/challenge/challenge-6) +- [localhost:8080/challenge/challenge-33](http://localhost:8080/challenge/challenge-33) ### Okteto based @@ -258,7 +258,7 @@ Therefore, you can manipulate them by overriding the following settings in your - `hints_enabled=false` will turn off the `Show hints` button. - `reason_enabled=false` will turn of the `What's wrong?` explanation button. -- `spoiling_enabled=false` will turn off the `/spoil-x` endpoint (where `x` is the number of the challenge). +- `spoiling_enabled=false` will turn off the `/spoil/challenge-x` endpoint (where `x` is the short-name of the challenge). ## Enabling Swaggerdocs and UI @@ -474,7 +474,7 @@ Follow the steps below on adding a challenge: 1. First make sure that you have an [Issue](https://github.com/OWASP/wrongsecrets/issues) reported for which a challenge is really wanted. 2. Add the new challenge in the `org.owasp.wrongsecrets.challenges` folder. Make sure you add an explanation in `src/main/resources/explanations` and refer to it from your new Challenge class. 3. Add unit, integration and UI tests as appropriate to show that your challenge is working. -4. Don't forget to add `@Order` annotation to your challenge ;-). +4. Do not forget to configure the challenge in `src/main/resources/wrong-secrets-configuration.yaml` 5. Review the [CONTRIBUTING guide](CONTRIBUTING.md) for setting up your contributing environment and writing good commit messages. For more details please refer [_Contributing.md_](https://github.com/OWASP/wrongsecrets/blob/master/CONTRIBUTING.md#how-to-add-a-challenge). diff --git a/k8s-vault-minkube-start.sh b/k8s-vault-minkube-start.sh index 757fc47dd..43bb3de50 100755 --- a/k8s-vault-minkube-start.sh +++ b/k8s-vault-minkube-start.sh @@ -123,6 +123,6 @@ kubectl port-forward \ echo "Do minikube delete to stop minikube from running and cleanup to start fresh again" echo "wait 20 seconds so we can check if vault-k8s-container works" sleep 20 -curl http://localhost:8080/spoil-7 +curl http://localhost:8080/spoil/challenge-7 echo "logs from pod to make sure:" cat pod.log diff --git a/pom.xml b/pom.xml index 304e5a0e7..af57fb701 100644 --- a/pom.xml +++ b/pom.xml @@ -103,6 +103,10 @@ spring-boot-starter-thymeleaf + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + nz.net.ultraq.thymeleaf thymeleaf-layout-dialect @@ -117,6 +121,11 @@ org.springframework.boot spring-boot-starter + + org.testcontainers + testcontainers + test + org.springframework.boot spring-boot-starter-actuator @@ -243,19 +252,35 @@ ${dependency-check-maven.version} maven-plugin + + com.tngtech.archunit + archunit-junit5 + 1.1.0 + test + com.github.spotbugs spotbugs-annotations 4.8.0 - - - - - + + io.github.wimdeblauwe + testcontainers-cypress + 1.9.0 + test + + + + src/test/resources + + + e2e + src/test/e2e + + @@ -316,31 +341,6 @@ - - - start-app - - start - - pre-integration-test - - local,without-vault - - src/test/resources/ - src/test/resources/ - src/test/resources/alibabacreds.kdbx - src/test/resources/alibabacreds.kdbx - - - - - stop-app - - stop - - post-integration-test - - org.apache.maven.plugins @@ -473,33 +473,6 @@ generate-resources - - npm-install - - exec - - integration-test - - npm - - install - - - - - xcypress-test - - exec - - integration-test - - npm - - run - test:ci - - - diff --git a/src/main/java/org/owasp/wrongsecrets/AllControllerAdvice.java b/src/main/java/org/owasp/wrongsecrets/AllControllerAdvice.java index b3cfc5246..c3f788227 100644 --- a/src/main/java/org/owasp/wrongsecrets/AllControllerAdvice.java +++ b/src/main/java/org/owasp/wrongsecrets/AllControllerAdvice.java @@ -1,11 +1,10 @@ package org.owasp.wrongsecrets; import jakarta.servlet.http.HttpServletRequest; -import java.util.List; -import org.owasp.wrongsecrets.challenges.Challenge; +import java.util.stream.Collectors; import org.owasp.wrongsecrets.challenges.ChallengeUI; +import org.owasp.wrongsecrets.definitions.ChallengeDefinitionsConfiguration; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ModelAttribute; @@ -18,22 +17,40 @@ @ControllerAdvice public class AllControllerAdvice { - private final List challenges; + private final Challenges challenges; private final String version; + private final ScoreCard scoreCard; + private final ChallengeDefinitionsConfiguration challengeDefinitionsConfiguration; private final RuntimeEnvironment runtimeEnvironment; public AllControllerAdvice( - List challenges, + Challenges challenges, @Value("${APP_VERSION}") String version, + ScoreCard scoreCard, + ChallengeDefinitionsConfiguration challengeDefinitionsConfiguration, RuntimeEnvironment runtimeEnvironment) { - this.challenges = ChallengeUI.toUI(challenges, runtimeEnvironment); + this.challenges = challenges; this.version = version; + this.scoreCard = scoreCard; + this.challengeDefinitionsConfiguration = challengeDefinitionsConfiguration; this.runtimeEnvironment = runtimeEnvironment; } @ModelAttribute public void addChallenges(Model model) { - model.addAttribute("challenges", challenges); + model.addAttribute( + "challenges", + challengeDefinitionsConfiguration.challenges().stream() + .map( + def -> + ChallengeUI.toUI( + def, + scoreCard, + runtimeEnvironment, + challenges.difficulties(), + challenges.getDefinitions().environments(), + challenges.navigation(def))) + .collect(Collectors.toList())); } @ModelAttribute @@ -48,12 +65,7 @@ public void addRequest(Model model, HttpServletRequest request) { @ModelAttribute public void addRuntimeEnvironment(Model model) { - model.addAttribute("environment", runtimeEnvironment.getRuntimeEnvironment().name()); + model.addAttribute("environment", runtimeEnvironment.getRuntimeEnvironment().displayName()); model.addAttribute("ctf_enabled", runtimeEnvironment.runtimeInCTFMode()); } - - @Bean - public List uiChallenges() { - return challenges; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/ChallengeUiTemplateResolver.java b/src/main/java/org/owasp/wrongsecrets/ChallengeUiTemplateResolver.java new file mode 100644 index 000000000..f4ea94b54 --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/ChallengeUiTemplateResolver.java @@ -0,0 +1,93 @@ +/** + * ************************************************************************************************ + * + *

+ * + *

This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + * + *

Copyright (c) 2002 - 2014 Bruce Mayhew + * + *

This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + *

You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + *

Getting Source ============== + * + *

Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository + * for free software projects. + * + * @author WebGoat + * @version $Id: $Id + * @since October 28, 2003 + */ +package org.owasp.wrongsecrets; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.StringUtils; +import org.thymeleaf.IEngineConfiguration; +import org.thymeleaf.templateresolver.FileTemplateResolver; +import org.thymeleaf.templateresource.ITemplateResource; +import org.thymeleaf.templateresource.StringTemplateResource; + +/** + * Dynamically resolve a UI snippet for a specific challenge. + * + *

Thymeleaf will invoke this resolver based on the prefix and this implementation will resolve + * the file in the resource directory. + */ +@Slf4j +public class ChallengeUiTemplateResolver extends FileTemplateResolver { + + private static final String PREFIX = "ui-snippet:"; + private ResourceLoader resourceLoader; + private Map resources = new HashMap<>(); + + public ChallengeUiTemplateResolver(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + setResolvablePatterns(Set.of(PREFIX + "*")); + } + + @Override + protected ITemplateResource computeTemplateResource( + IEngineConfiguration configuration, + String ownerTemplate, + String template, + String resourceName, + String characterEncoding, + Map templateResolutionAttributes) { + var templateName = resourceName.substring(PREFIX.length()); + if (!StringUtils.hasText(templateName) || "null".equals(templateName)) { + return new StringTemplateResource(""); + } + byte[] resource = resources.get(templateName); + if (resource == null) { + try { + resource = + resourceLoader + .getResource("classpath:/" + templateName) + .getInputStream() + .readAllBytes(); + } catch (IOException e) { + log.error("Unable to find resource {}", templateName, e); + return new StringTemplateResource(""); + } + resources.put(templateName, resource); + } + return new StringTemplateResource(new String(resource, StandardCharsets.UTF_8)); + } +} diff --git a/src/main/java/org/owasp/wrongsecrets/Challenges.java b/src/main/java/org/owasp/wrongsecrets/Challenges.java new file mode 100644 index 000000000..10bb9ea4a --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/Challenges.java @@ -0,0 +1,106 @@ +package org.owasp.wrongsecrets; + +import static java.util.stream.Collectors.toMap; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import lombok.Getter; +import org.owasp.wrongsecrets.challenges.Challenge; +import org.owasp.wrongsecrets.definitions.ChallengeDefinition; +import org.owasp.wrongsecrets.definitions.ChallengeDefinitionsConfiguration; +import org.owasp.wrongsecrets.definitions.ChallengeName; +import org.owasp.wrongsecrets.definitions.Difficulty; +import org.owasp.wrongsecrets.definitions.Navigation; + +/** + * A collection of all challenges. This class glues a challenge definition together with the + * challenge class. + */ +public class Challenges { + + @Getter private final ChallengeDefinitionsConfiguration definitions; + private final Map urlNameToDefinition; + private final Map classNameToChallenge; + private final Map> challengeDefinitionToChallenge; + + public Challenges(ChallengeDefinitionsConfiguration definitions, List challenges) { + this.definitions = definitions; + + urlNameToDefinition = + definitions.challenges().stream() + .collect(toMap(definition -> definition.name().url(), Function.identity())); + classNameToChallenge = + challenges.stream() + .collect(toMap(challenge -> challenge.getClass().getName(), Function.identity())); + challengeDefinitionToChallenge = + definitions.challenges().stream() + .collect( + toMap( + definition -> definition, + definition -> + definition.sources().stream() + .map(source -> classNameToChallenge.get(source.className())) + .toList())); + } + + public Navigation navigation(ChallengeDefinition challengeDefinition) { + return new Navigation(definitions.challenges(), challengeDefinition); + } + + public int numberOfChallenges() { + return definitions.challenges().size(); + } + + public Optional findByShortName(String shortName) { + return definitions.challenges().stream() + .filter(challenge -> challenge.name().url().equals(shortName)) + .findFirst(); + } + + /** + * To improve error messages when we cannot find a challenge, let's see if we can find some + * matching ones. + */ + public List findMatchingChallenges(String shortChallengeName) { + return definitions.challenges().stream() + .filter(challenge -> challenge.name().partialMatches(shortChallengeName)) + .map(def -> def.name()) + .toList(); + } + + /** + * Find a challenge based on the name and the runtime environment. + * + * @param shortChallengeName the name of the challenge + * @param runtimeEnvironment the runtime environment + * @return the challenge if found + */ + public Optional findChallenge( + String shortChallengeName, RuntimeEnvironment runtimeEnvironment) { + var challengeDefinition = urlNameToDefinition.get(shortChallengeName); + + if (challengeDefinition == null) { + return Optional.empty(); + } + var source = challengeDefinition.source(runtimeEnvironment); + return source.map(s -> classNameToChallenge.get(s.className())); + } + + public List getChallenge(ChallengeDefinition definition) { + return challengeDefinitionToChallenge.get(definition); + } + + public List difficulties() { + return definitions.difficulties(); + } + + public boolean isFirstChallenge(ChallengeDefinition challengeDefinition) { + return challengeDefinition.equals(definitions.challenges().get(0)); + } + + public List getChallengeDefinitions() { + return definitions.challenges(); + } +} diff --git a/src/main/java/org/owasp/wrongsecrets/ConfigurationException.java b/src/main/java/org/owasp/wrongsecrets/ConfigurationException.java new file mode 100644 index 000000000..05d718df8 --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/ConfigurationException.java @@ -0,0 +1,19 @@ +package org.owasp.wrongsecrets; + +import com.google.common.base.Strings; +import java.util.function.Supplier; + +public class ConfigurationException extends RuntimeException { + public ConfigurationException(String message) { + super(message); + } + + public ConfigurationException(Supplier message) { + super(message.get()); + } + + public static Supplier configError( + String errorMessageTemplate, Object... errorMessageArgs) { + return () -> Strings.lenientFormat(errorMessageTemplate, errorMessageArgs); + } +} diff --git a/src/main/java/org/owasp/wrongsecrets/FailtoStartupException.java b/src/main/java/org/owasp/wrongsecrets/FailtoStartupException.java deleted file mode 100644 index 98079e695..000000000 --- a/src/main/java/org/owasp/wrongsecrets/FailtoStartupException.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.owasp.wrongsecrets; - -import org.springframework.boot.ExitCodeGenerator; - -/** Used to give a clear non-0 exit code when the Application cannot start. */ -public class FailtoStartupException extends RuntimeException implements ExitCodeGenerator { - - public FailtoStartupException(String message) { - super(message); - } - - @Override - public int getExitCode() { - return 1; - } -} diff --git a/src/main/java/org/owasp/wrongsecrets/InMemoryScoreCard.java b/src/main/java/org/owasp/wrongsecrets/InMemoryScoreCard.java index df8d3eb5a..c8fcfd00b 100644 --- a/src/main/java/org/owasp/wrongsecrets/InMemoryScoreCard.java +++ b/src/main/java/org/owasp/wrongsecrets/InMemoryScoreCard.java @@ -1,49 +1,57 @@ package org.owasp.wrongsecrets; +import static java.util.stream.Collectors.toMap; + import java.util.HashSet; -import java.util.List; +import java.util.Map; import java.util.Set; -import org.owasp.wrongsecrets.challenges.Challenge; +import java.util.stream.IntStream; +import org.owasp.wrongsecrets.definitions.ChallengeDefinition; +import org.owasp.wrongsecrets.definitions.Difficulty; /** In-memory implementation of the ScoreCard (E.g. no persistence). */ public class InMemoryScoreCard implements ScoreCard { - private final int maxNumberOfChallenges; - private final Set solvedChallenges = new HashSet<>(); - - public InMemoryScoreCard(int numberOfChallenge) { - maxNumberOfChallenges = numberOfChallenge; + private final Set solvedChallenges = new HashSet<>(); + private final Challenges challenges; + private final Map difficultyLevelsToInt; + + public InMemoryScoreCard(Challenges challenges) { + this.challenges = challenges; + var difficulties = challenges.difficulties(); + this.difficultyLevelsToInt = + IntStream.range(1, difficulties.size() + 1) + .boxed() + .collect(toMap(i -> difficulties.get(i - 1), i -> i)); } @Override - public void completeChallenge(Challenge challenge) { - solvedChallenges.add(challenge); + public void completeChallenge(ChallengeDefinition challengeDefinition) { + solvedChallenges.add(challengeDefinition); } @Override - public boolean getChallengeCompleted(Challenge challenge) { + public boolean getChallengeCompleted(ChallengeDefinition challenge) { return solvedChallenges.contains(challenge); } @Override public float getProgress() { - return ((float) 100 / maxNumberOfChallenges) * solvedChallenges.size(); + return ((float) 100 / challenges.numberOfChallenges()) * solvedChallenges.size(); } @Override public int getTotalReceivedPoints() { return solvedChallenges.stream() - .map(challenge -> challenge.difficulty() * (100 + (challenge.difficulty() - 1) * 25)) + .map( + challenge -> + difficultyLevelsToInt.get(challenge.difficulty()) + * (100 + (difficultyLevelsToInt.get(challenge.difficulty()) - 1) * 25)) .reduce(0, Integer::sum); } @Override - public List getCompletedChallenges() { - return solvedChallenges.stream().map(Challenge::getNumber).toList(); - } - - @Override - public void reset(Challenge challenge) { + public void reset(ChallengeDefinition challenge) { solvedChallenges.remove(challenge); } } diff --git a/src/main/java/org/owasp/wrongsecrets/MvcConfiguration.java b/src/main/java/org/owasp/wrongsecrets/MvcConfiguration.java index 30afd8f0a..e44ffa738 100644 --- a/src/main/java/org/owasp/wrongsecrets/MvcConfiguration.java +++ b/src/main/java/org/owasp/wrongsecrets/MvcConfiguration.java @@ -11,6 +11,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ResourceLoader; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect; import org.thymeleaf.spring6.SpringTemplateEngine; @@ -66,16 +67,27 @@ public AsciiDoctorTemplateResolver asciiDoctorTemplateResolver(TemplateGenerator return resolver; } + /** Loads the html for the complete lesson, see lesson_content.html */ + @Bean + public ChallengeUiTemplateResolver uiTemplateResolver(ResourceLoader resourceLoader) { + ChallengeUiTemplateResolver resolver = new ChallengeUiTemplateResolver(resourceLoader); + resolver.setOrder(0); + resolver.setCacheable(false); + resolver.setCharacterEncoding(UTF8); + return resolver; + } + @Bean public SpringTemplateEngine thymeleafTemplateEngine( ITemplateResolver springThymeleafTemplateResolver, + ITemplateResolver uiTemplateResolver, FileTemplateResolver asciiDoctorTemplateResolver) { SpringTemplateEngine engine = new SpringTemplateEngine(); engine.setEnableSpringELCompiler(true); engine.addDialect(new LayoutDialect()); engine.addDialect(new SpringSecurityDialect()); engine.setTemplateResolvers( - Set.of(asciiDoctorTemplateResolver, springThymeleafTemplateResolver)); + Set.of(asciiDoctorTemplateResolver, uiTemplateResolver, springThymeleafTemplateResolver)); return engine; } } diff --git a/src/main/java/org/owasp/wrongsecrets/RuntimeEnvironment.java b/src/main/java/org/owasp/wrongsecrets/RuntimeEnvironment.java index 71aa9447d..8037b144c 100644 --- a/src/main/java/org/owasp/wrongsecrets/RuntimeEnvironment.java +++ b/src/main/java/org/owasp/wrongsecrets/RuntimeEnvironment.java @@ -1,113 +1,71 @@ package org.owasp.wrongsecrets; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.*; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.stream.Collectors; import lombok.Getter; -import org.owasp.wrongsecrets.challenges.Challenge; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.owasp.wrongsecrets.definitions.ChallengeDefinition; +import org.owasp.wrongsecrets.definitions.ChallengeDefinitionsConfiguration; +import org.owasp.wrongsecrets.definitions.Environment; import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; /** * Class establishing whether a challenge can run or not depending on the given RuntimeEnvironment * and whether components are configured and the CTFmode is enabled or not. */ -@Component +@Slf4j +@ToString public class RuntimeEnvironment { @Value("${ctf_enabled}") private boolean ctfModeEnabled; @Value("${SPECIAL_K8S_SECRET}") - private String challenge5Value; // used to determine if k8s/vault challenges are overriden; + private String challenge5Value; // used to determine if k8s/vault challenges are overridden; @Value("${vaultpassword}") private String challenge7Value; @Value("${default_aws_value_challenge_9}") private String - defaultChallenge9Value; // used to determine if the csloud challenge values are overriden - - private static final Map> envToOverlappingEnvs = - Map.of( - FLY_DOCKER, - List.of(DOCKER, FLY_DOCKER), - HEROKU_DOCKER, - List.of(DOCKER, HEROKU_DOCKER), - DOCKER, - List.of(DOCKER, HEROKU_DOCKER, FLY_DOCKER), - GCP, - List.of(DOCKER, K8S, VAULT), - AWS, - List.of(DOCKER, K8S, VAULT), - AZURE, - List.of(DOCKER, K8S, VAULT), - VAULT, - List.of(DOCKER, K8S), - K8S, - List.of(DOCKER), - OKTETO_K8S, - List.of(K8S, DOCKER, OKTETO_K8S)); - - /** Enum with possible environments supported by the app. */ - public enum Environment { - DOCKER("Docker"), - HEROKU_DOCKER("Heroku(Docker)"), - FLY_DOCKER("Fly(Docker)"), - GCP("gcp"), - AWS("aws"), - AZURE("azure"), - VAULT("k8s-with-vault"), - K8S("k8s"), - OKTETO_K8S("Okteto(k8s)"); - - private final String id; - - Environment(String id) { - this.id = id; - } - - static Environment fromId(String id) { - return Arrays.stream(Environment.values()) - .filter(e -> e.id.equalsIgnoreCase(id)) - .findAny() - .get(); - } - } + defaultChallenge9Value; // used to determine if the cloud challenge values are overridden @Getter private final Environment runtimeEnvironment; - @Autowired - public RuntimeEnvironment(@Value("${K8S_ENV}") String currentRuntimeEnvironment) { - this.runtimeEnvironment = Environment.fromId(currentRuntimeEnvironment); + public RuntimeEnvironment( + String currentRuntimeEnvironment, ChallengeDefinitionsConfiguration challengeDefinitions) { + this.runtimeEnvironment = + challengeDefinitions.environments().stream() + .filter(env -> env.name().equalsIgnoreCase(currentRuntimeEnvironment)) + .findFirst() + .orElseThrow( + () -> { + log.error( + "Unable to determine the runtime environment. Make sure K8S_ENV contains one" + + " of the expected values: {}.", + challengeDefinitions.environments().stream() + .map(Environment::name) + .collect(Collectors.joining())); + throw new IllegalStateException( + "K8S_ENV does not contain one of the expected values"); + }); } public RuntimeEnvironment(Environment runtimeEnvironment) { this.runtimeEnvironment = runtimeEnvironment; } - public boolean canRun(Challenge challenge) { + public boolean canRun(ChallengeDefinition challengeDefinition) { if (isCloudUnlockedInCTFMode()) { return true; } if (isVaultUnlockedInCTFMode() && isK8sUnlockedInCTFMode()) { - return challenge.supportedRuntimeEnvironments().contains(runtimeEnvironment) - || challenge.supportedRuntimeEnvironments().contains(DOCKER) - || challenge.supportedRuntimeEnvironments().contains(K8S) - || challenge.supportedRuntimeEnvironments().contains(VAULT); + return challengeDefinition.supportedEnvironments().contains(runtimeEnvironment); } if (isK8sUnlockedInCTFMode()) { - return challenge.supportedRuntimeEnvironments().contains(runtimeEnvironment) - || challenge.supportedRuntimeEnvironments().contains(DOCKER) - || challenge.supportedRuntimeEnvironments().contains(K8S); + return challengeDefinition.supportedEnvironments().contains(runtimeEnvironment); } - return challenge.supportedRuntimeEnvironments().contains(runtimeEnvironment) - || !Collections.disjoint( - envToOverlappingEnvs.get(runtimeEnvironment), challenge.supportedRuntimeEnvironments()); + return challengeDefinition.supportedEnvironments().contains(this.runtimeEnvironment); } public boolean runtimeInCTFMode() { diff --git a/src/main/java/org/owasp/wrongsecrets/ScoreCard.java b/src/main/java/org/owasp/wrongsecrets/ScoreCard.java index af43b4e3e..f48caa759 100644 --- a/src/main/java/org/owasp/wrongsecrets/ScoreCard.java +++ b/src/main/java/org/owasp/wrongsecrets/ScoreCard.java @@ -1,7 +1,6 @@ package org.owasp.wrongsecrets; -import java.util.List; -import org.owasp.wrongsecrets.challenges.Challenge; +import org.owasp.wrongsecrets.definitions.ChallengeDefinition; /** Interface of a scorecard where a player's progress is stored into. */ public interface ScoreCard { @@ -11,7 +10,7 @@ public interface ScoreCard { * * @param challenge Challenge object which is completed */ - void completeChallenge(Challenge challenge); + void completeChallenge(ChallengeDefinition challenge); /** * Checks if the given challenge is marked as completed in the scorecard. @@ -19,7 +18,7 @@ public interface ScoreCard { * @param challenge Challenge object tested for completion * @return true if challenge solved correctly */ - boolean getChallengeCompleted(Challenge challenge); + boolean getChallengeCompleted(ChallengeDefinition challenge); /** * Gives a 0-100 implementation completeness score. @@ -35,17 +34,10 @@ public interface ScoreCard { */ int getTotalReceivedPoints(); - /** - * Gives all completed challenges - * - * @return Set of ints - */ - List getCompletedChallenges(); - /** * Resets the status of a given challenge its entry in the score-card. * - * @param challenge Challenge of which the status should be reset. + * @param challenge challenge of which the status should be reset. */ - void reset(Challenge challenge); + void reset(ChallengeDefinition challenge); } diff --git a/src/main/java/org/owasp/wrongsecrets/StartupListener.java b/src/main/java/org/owasp/wrongsecrets/StartupListener.java deleted file mode 100644 index 1fa99448d..000000000 --- a/src/main/java/org/owasp/wrongsecrets/StartupListener.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.owasp.wrongsecrets; - -import java.util.Arrays; -import java.util.stream.Collectors; -import lombok.experimental.UtilityClass; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; - -/** Helps application startup and breaks nicely if K8S_ENV is wrong. */ -@Slf4j -public class StartupListener implements ApplicationListener { - - @Override - public void onApplicationEvent(final @NotNull ApplicationEvent event) { - if (event instanceof ApplicationEnvironmentPreparedEvent envEvent) { - if (!StartupHelper.passedCorrectEnv(envEvent.getEnvironment().getProperty("K8S_ENV"))) { - log.error( - "K8S_ENV does not contain one of the expected values: {}.", - StartupHelper.envsToReadableString()); - throw new FailtoStartupException("K8S_ENV does not contain one of the expected values"); - } - } - } - - @UtilityClass - private static class StartupHelper { - - private boolean passedCorrectEnv(String k8sEnv) { - try { - RuntimeEnvironment.Environment.fromId(k8sEnv); - return true; - } catch (Exception e) { - return false; - } - } - - private String envsToReadableString() { - return Arrays.stream(RuntimeEnvironment.Environment.values()) - .map(Enum::toString) - .collect(Collectors.joining(", ")); - } - } -} diff --git a/src/main/java/org/owasp/wrongsecrets/WrongSecretsApplication.java b/src/main/java/org/owasp/wrongsecrets/WrongSecretsApplication.java index d910099be..b42407e3e 100644 --- a/src/main/java/org/owasp/wrongsecrets/WrongSecretsApplication.java +++ b/src/main/java/org/owasp/wrongsecrets/WrongSecretsApplication.java @@ -1,9 +1,8 @@ package org.owasp.wrongsecrets; -import java.util.List; -import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.challenges.Challenge; import org.owasp.wrongsecrets.challenges.kubernetes.Vaultpassword; +import org.owasp.wrongsecrets.definitions.ChallengeDefinitionsConfiguration; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -13,7 +12,6 @@ @SpringBootApplication @EnableConfigurationProperties(Vaultpassword.class) -@Slf4j public class WrongSecretsApplication { public static void main(String[] args) { @@ -22,8 +20,14 @@ public static void main(String[] args) { @Bean @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) - public InMemoryScoreCard scoreCard(List challenges) { - log.info("Initializing scorecard with {} challenges", challenges.size()); - return new InMemoryScoreCard(challenges.size()); + public InMemoryScoreCard scoreCard(Challenges challenges) { + return new InMemoryScoreCard(challenges); + } + + @Bean + public RuntimeEnvironment runtimeEnvironment( + @Value("${K8S_ENV}") String currentRuntimeEnvironment, + ChallengeDefinitionsConfiguration challengeDefinitions) { + return new RuntimeEnvironment(currentRuntimeEnvironment, challengeDefinitions); } } diff --git a/src/main/java/org/owasp/wrongsecrets/asciidoc/AsciiDoctorTemplateResolver.java b/src/main/java/org/owasp/wrongsecrets/asciidoc/AsciiDoctorTemplateResolver.java index d1edcca5f..6656ddf4b 100644 --- a/src/main/java/org/owasp/wrongsecrets/asciidoc/AsciiDoctorTemplateResolver.java +++ b/src/main/java/org/owasp/wrongsecrets/asciidoc/AsciiDoctorTemplateResolver.java @@ -42,6 +42,6 @@ protected ITemplateResource computeTemplateResource( } private String computeResourceName(String resourceName) { - return String.format("explanations/%s", resourceName.replace(".adoc", "")); + return String.format("%s", resourceName.replace(".adoc", "")); } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/Challenge.java b/src/main/java/org/owasp/wrongsecrets/challenges/Challenge.java index 55ce2b8ca..a0ba7e885 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/Challenge.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/Challenge.java @@ -1,28 +1,14 @@ package org.owasp.wrongsecrets.challenges; -import java.util.List; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.owasp.wrongsecrets.RuntimeEnvironment.Environment; -import org.owasp.wrongsecrets.ScoreCard; - -/** - * General Abstract Challenge class containing all the necessary members for a challenge. - * - * @see org.owasp.wrongsecrets.ScoreCard for tracking - */ -@RequiredArgsConstructor -@Getter -public abstract class Challenge { - - private final ScoreCard scoreCard; +/** General Abstract Challenge class containing all the necessary members for a challenge. */ +public interface Challenge { /** * Returns a Spoiler object containing the secret for the challenge. * * @return Spoiler with anser */ - public abstract Spoiler spoiler(); + Spoiler spoiler(); /** * method that needs to be overwritten by the Challenge implementation class to do the actual @@ -31,95 +17,5 @@ public abstract class Challenge { * @param answer String provided by the user * @return true if answer is Correct */ - protected abstract boolean answerCorrect(String answer); - - /** - * Gives the supported runtime envs in which the class can run. - * - * @return a list of Environment objects representing supported envs for the class - */ - public abstract List supportedRuntimeEnvironments(); - - /** - * returns the difficulty level. - * - * @return int with difficulty - */ - public abstract int difficulty(); - - /** - * returns the technology used. - * - * @see ChallengeTechnology.Tech - * @return a string from Tech.id - */ - public abstract String getTech(); - - /** - * boolean indicating a challenge needs to be run differently with a different explanation/steps - * when running on a shared platform. - * - * @return boolean with true if a different explanation is required when running on a shared - * platform - */ - public abstract boolean isLimitedWhenOnlineHosted(); - - /** - * boolean indicating if the challenge can be enabled when running in CTF mode. Note: All - * challenges should be able to run in non-CTF mode. - * - * @return true if the challenge can be run in CTF mode. - */ - public abstract boolean canRunInCTFMode(); - - /** - * Solving method which, if the correct answer is provided, will mark the challenge as solved in - * the scorecard. - * - * @param answer String provided by the user to validate. - * @return true if answer was correct. - */ - public boolean solved(String answer) { - var correctAnswer = answerCorrect(answer); - if (correctAnswer) { - scoreCard.completeChallenge(this); - } - return correctAnswer; - } - - /** - * Returns the name of the explanation file for adoc rendering. - * - * @return String with name of file for explanation - */ - public String getExplanation() { - return this.getClass().getSimpleName().toLowerCase(); - } - - /** - * Returns the name of the hints file for adoc rendering. - * - * @return String with name of file for hints - */ - public String getHint() { - return this.getClass().getSimpleName().toLowerCase() + "_hint"; - } - - /** - * Returns the name of the reason file for adoc rendering. - * - * @return String with name of file for reason - */ - public String getReason() { - return this.getClass().getSimpleName().toLowerCase() + "_reason"; - } - - /** - * Returns the number of the challenge extracted from the classname - * - * @return int of the challenge - */ - public String getNumber() { - return this.getClass().getSimpleName().replaceAll("[^0-9]", ""); - } + boolean answerCorrect(String answer); } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/ChallengeUI.java b/src/main/java/org/owasp/wrongsecrets/challenges/ChallengeUI.java index 70c2167cf..f896960aa 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/ChallengeUI.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/ChallengeUI.java @@ -1,41 +1,58 @@ package org.owasp.wrongsecrets.challenges; -import java.util.Comparator; import java.util.List; -import java.util.regex.Pattern; +import java.util.function.Function; import java.util.stream.Collectors; import lombok.Getter; import org.owasp.wrongsecrets.RuntimeEnvironment; +import org.owasp.wrongsecrets.ScoreCard; +import org.owasp.wrongsecrets.definitions.ChallengeDefinition; +import org.owasp.wrongsecrets.definitions.Difficulty; +import org.owasp.wrongsecrets.definitions.Environment; +import org.owasp.wrongsecrets.definitions.Navigation; +import org.owasp.wrongsecrets.definitions.Sources.Source; /** Wrapper class to move logic from Thymeleaf to keep logic in code instead of the html file. */ @Getter public class ChallengeUI { + private final DifficultyUI difficultyUI; + private final List environments; + private final Navigation navigation; + /** Wrapper class to express the difficulty level into a UI representation. */ - private record DifficultyUI(int difficulty) { + private record DifficultyUI(int difficulty, int totalOfDifficultyLevels) { public String minimal() { return "☆".repeat(difficulty); } public String scale() { - int numberOfDifficultyLevels = Difficulty.totalOfDifficultyLevels(); - String fullScale = "★".repeat(difficulty) + "☆".repeat(numberOfDifficultyLevels); - return fullScale.substring(0, numberOfDifficultyLevels); + String fullScale = "★".repeat(difficulty) + "☆".repeat(totalOfDifficultyLevels); + return fullScale.substring(0, totalOfDifficultyLevels); } } - private static final Pattern challengePattern = Pattern.compile("(\\D+)(\\d+)"); - - private final Challenge challenge; - private final int challengeNumber; + private final ChallengeDefinition challengeDefinition; + private final ScoreCard scoreCard; private final RuntimeEnvironment runtimeEnvironment; + private final List difficulties; public ChallengeUI( - Challenge challenge, int challengeNumber, RuntimeEnvironment runtimeEnvironment) { - this.challenge = challenge; - this.challengeNumber = challengeNumber; + ChallengeDefinition challengeDefinition, + ScoreCard scoreCard, + RuntimeEnvironment runtimeEnvironment, + List difficulties, + List environments, + Navigation navigation) { + this.challengeDefinition = challengeDefinition; + this.scoreCard = scoreCard; this.runtimeEnvironment = runtimeEnvironment; + this.difficulties = difficulties; + this.environments = environments; + this.navigation = navigation; + this.difficultyUI = + new DifficultyUI(challengeDefinition.difficulty(difficulties), difficulties.size()); } /** @@ -44,20 +61,16 @@ public ChallengeUI( * @return String with name of the challenge. */ public String getName() { - var matchers = challengePattern.matcher(challenge.getClass().getSimpleName()); - if (matchers.matches()) { - return matchers.group(1) + " " + matchers.group(2); - } - return "Unknown"; + return challengeDefinition.name().name(); } /** * gives back the number of the challenge. * - * @return int with challenge number. + * @return the html friendly url name for the challenge */ - public Integer getLink() { - return challengeNumber; + public String getLink() { + return challengeDefinition.name().url(); } /** @@ -66,7 +79,7 @@ public Integer getLink() { * @return string with tech. */ public String getTech() { - return challenge.getTech(); + return challengeDefinition.category().category(); } /** @@ -74,17 +87,26 @@ public String getTech() { * * @return int with next challenge number. */ - public Integer next() { - return challengeNumber + 1; + public String next() { + return navigation.next().map(c -> c.name().url()).orElse(null); } /** - * Returns the number of the previous challenge (e.g current-1). + * Returns the number of the previous challenge * * @return int with previous challenge number. */ - public Integer previous() { - return challengeNumber - 1; + public String previous() { + return navigation.previous().map(c -> c.name().url()).orElse(null); + } + + private String documentation(Function extractor) { + if (runtimeEnvironment.canRun(challengeDefinition)) { + return challengeDefinition.source(runtimeEnvironment).map(extractor).orElse(""); + } else { + // We cannot run the challenge but showing documentation should still be possible + return extractor.apply(challengeDefinition.sources().getFirst()); + } } /** @@ -93,7 +115,7 @@ public Integer previous() { * @return String with filename. */ public String getExplanation() { - return challenge.getExplanation(); + return documentation(s -> s.explanation().fileName()); } /** @@ -102,16 +124,7 @@ public String getExplanation() { * @return String with filename. */ public String getHint() { - List limitedOnlineEnvs = - List.of( - RuntimeEnvironment.Environment.HEROKU_DOCKER, - RuntimeEnvironment.Environment.FLY_DOCKER, - RuntimeEnvironment.Environment.OKTETO_K8S); - if (limitedOnlineEnvs.contains(runtimeEnvironment.getRuntimeEnvironment()) - && challenge.isLimitedWhenOnlineHosted()) { - return challenge.getHint() + "_limited"; - } - return challenge.getHint(); + return documentation(s -> s.hint().fileName()); } /** @@ -120,7 +133,7 @@ public String getHint() { * @return String with filename. */ public String getReason() { - return challenge.getReason(); + return documentation(s -> s.reason().fileName()); } /** @@ -129,10 +142,11 @@ public String getReason() { * @return String with required env. */ public String requiredEnv() { - return challenge.supportedRuntimeEnvironments().stream() - .map(Enum::name) + return challengeDefinition.supportedEnvironments().stream() + .map(e -> e.name()) .limit(1) - .collect(Collectors.joining()); + .collect(Collectors.joining()) + .toUpperCase(); } /** @@ -142,7 +156,7 @@ public String requiredEnv() { * @return stars */ public String getStarsOnScale() { - return new DifficultyUI(challenge.difficulty()).scale(); + return difficultyUI.scale(); } /** @@ -152,7 +166,7 @@ public String getStarsOnScale() { * @return label */ public String getDataLabel() { - String label = getName().trim().toLowerCase(); + String label = challengeDefinition.name().url().trim().toLowerCase(); if (!this.isChallengeEnabled()) { label = label + "_disabled"; } @@ -170,7 +184,7 @@ public String getDataLabel() { */ public boolean challengeCompleted() { if (!runtimeEnvironment.runtimeInCTFMode()) { - return challenge.getScoreCard().getChallengeCompleted(challenge); + return scoreCard.getChallengeCompleted(challengeDefinition); } return false; } @@ -181,7 +195,7 @@ public boolean challengeCompleted() { * @return stars */ public String getStars() { - return new DifficultyUI(challenge.difficulty()).minimal(); + return difficultyUI.minimal(); } /** @@ -191,26 +205,41 @@ public String getStars() { */ public boolean isChallengeEnabled() { if (runtimeEnvironment.runtimeInCTFMode()) { - return runtimeEnvironment.canRun(challenge) && challenge.canRunInCTFMode(); + return runtimeEnvironment.canRun(challengeDefinition) && challengeDefinition.ctf().enabled(); + } + return runtimeEnvironment.canRun(challengeDefinition); + } + + public String getUiSnippet() { + return challengeDefinition + .source(runtimeEnvironment) + .map(source -> source.uiSnippet()) + .orElse(""); + } + + public String getRuntimeEnvironmentCategory() { + var challengeEnvironmentsOverviewNames = + challengeDefinition.supportedEnvironments().stream().map(env -> env.overview()).toList(); + if (challengeEnvironmentsOverviewNames.size() == 1) { + return challengeEnvironmentsOverviewNames.iterator().next(); + } else { + // Now if we have multiple we select the lowest one for our global environment definition + var allEnvironmentsOverviewNames = environments.stream().map(env -> env.overview()).toList(); + return allEnvironmentsOverviewNames.stream() + .filter(name -> challengeEnvironmentsOverviewNames.contains(name)) + .findFirst() + .orElse("Unknown"); } - return runtimeEnvironment.canRun(challenge); } - /** - * returns the list of challengeUIs based on the status sof the runtime. - * - * @param challenges actual challenges to be used in app. - * @param environment the runtime env we are running on as an app. - * @return list of ChallengeUIs. - */ - public static List toUI(List challenges, RuntimeEnvironment environment) { - return challenges.stream() - .sorted( - Comparator.comparingInt( - challenge -> - Integer.parseInt( - challenge.getClass().getSimpleName().replace("Challenge", "")))) - .map(challenge -> new ChallengeUI(challenge, challenges.indexOf(challenge), environment)) - .toList(); + public static ChallengeUI toUI( + ChallengeDefinition definition, + ScoreCard scoreCard, + RuntimeEnvironment environment, + List difficulties, + List environments, + Navigation navigation) { + return new ChallengeUI( + definition, scoreCard, environment, difficulties, environments, navigation); } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/ChallengesAPIController.java b/src/main/java/org/owasp/wrongsecrets/challenges/ChallengesAPIController.java deleted file mode 100644 index 779bb7610..000000000 --- a/src/main/java/org/owasp/wrongsecrets/challenges/ChallengesAPIController.java +++ /dev/null @@ -1,153 +0,0 @@ -package org.owasp.wrongsecrets.challenges; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.swagger.v3.oas.annotations.Operation; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import lombok.extern.slf4j.Slf4j; -import net.minidev.json.JSONArray; -import net.minidev.json.JSONObject; -import org.asciidoctor.Asciidoctor; -import org.asciidoctor.Options; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; -import org.owasp.wrongsecrets.asciidoc.TemplateGenerator; -import org.springframework.http.MediaType; -import org.springframework.util.ResourceUtils; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * Used to request and generate the required json for setting up a CTF through juiceshop CTF CLI. - */ -@Slf4j -@RestController -public class ChallengesAPIController { - - private final ScoreCard scoreCard; - private final List challenges; - - private final List descriptions; - - private final List hints; - - private final TemplateGenerator templateGenerator; - - private final RuntimeEnvironment runtimeEnvironment; - - public ChallengesAPIController( - ScoreCard scoreCard, - List challenges, - RuntimeEnvironment runtimeEnvironment, - TemplateGenerator templateGenerator) { - this.scoreCard = scoreCard; - this.challenges = challenges; - this.descriptions = new ArrayList<>(); - this.hints = new ArrayList<>(); - this.runtimeEnvironment = runtimeEnvironment; - this.templateGenerator = templateGenerator; - } - - @GetMapping( - value = {"/api/Challenges", "/api/challenges"}, - produces = MediaType.APPLICATION_JSON_VALUE) - @Operation( - summary = "Gives all challenges back in a jsonArray, to be used with the Juiceshop CTF cli") - public String getChallenges() { - if (descriptions.size() == 0) { - initializeHintsAndDescriptions(); - } - JSONObject json = new JSONObject(); - JSONArray jsonArray = new JSONArray(); - for (int i = 0; i < challenges.size(); i++) { - JSONObject jsonChallenge = new JSONObject(); - jsonChallenge.put("id", i + 1); - jsonChallenge.put("name", challenges.get(i).getName()); - jsonChallenge.put("key", challenges.get(i).getExplanation()); - jsonChallenge.put( - "category", getCategory(challenges.get(i)) + " - " + challenges.get(i).getTech()); - jsonChallenge.put("description", descriptions.get(i)); - jsonChallenge.put("hint", hints.get(i)); - jsonChallenge.put( - "solved", scoreCard.getChallengeCompleted(challenges.get(i).getChallenge())); - jsonChallenge.put("disabledEnv", getDisabledEnv(challenges.get(i))); - jsonChallenge.put("difficulty", challenges.get(i).getChallenge().difficulty()); - jsonArray.add(jsonChallenge); - } - json.put("status", "success"); - json.put("data", jsonArray); - String result = json.toJSONString(); - log.info("returning {}", result); - return result; - } - - private String getCategory(ChallengeUI challengeUI) { - return switch (challengeUI.getChallenge().supportedRuntimeEnvironments().get(0)) { - case DOCKER, HEROKU_DOCKER, FLY_DOCKER -> "Docker"; - case GCP, AWS, AZURE -> "Cloud"; - case VAULT -> "Vault"; - case K8S, OKTETO_K8S -> "Kubernetes"; - }; - } - - private void initializeHintsAndDescriptions() { - log.info("Initialize hints and descriptions"); - challenges.forEach( - challengeUI -> { // note requires mvn install to generate the html files! - try { - String hint = - templateGenerator.generate( - "explanations/" + challengeUI.getExplanation() + "_hint"); - hints.add(hint); - String description = - templateGenerator.generate("explanations/" + challengeUI.getExplanation()); - descriptions.add(description); - } catch (IOException e) { - String rawHint = - extractResource( - "classpath:explanations/" + challengeUI.getExplanation() + "_hint.adoc"); - try (Asciidoctor asciidoctor = Asciidoctor.Factory.create()) { - String hint = asciidoctor.convert(rawHint, Options.builder().build()); - hints.add(hint); - } - String rawDescription = - extractResource("classpath:explanations/" + challengeUI.getExplanation() + ".adoc"); - try (Asciidoctor asciidoctor = Asciidoctor.Factory.create()) { - String description = asciidoctor.convert(rawDescription, Options.builder().build()); - descriptions.add(description); - } - throw new RuntimeException(e); - } - }); - } - - @SuppressFBWarnings( - value = "URLCONNECTION_SSRF_FD", - justification = "Read from specific classpath") - private String extractResource(String resourceName) { - try { - var resource = ResourceUtils.getURL(resourceName); - final StringBuilder resourceStringbuilder = new StringBuilder(); - try (BufferedReader bufferedReader = - new BufferedReader( - new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8))) { - bufferedReader.lines().forEach(resourceStringbuilder::append); - return resourceStringbuilder.toString(); - } - - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private String getDisabledEnv(ChallengeUI challenge) { - if (runtimeEnvironment.canRun(challenge.getChallenge())) { - return runtimeEnvironment.getRuntimeEnvironment().name(); - } - return null; - } -} diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/ChallengesController.java b/src/main/java/org/owasp/wrongsecrets/challenges/ChallengesController.java index b15d3ca52..ba63afc22 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/ChallengesController.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/ChallengesController.java @@ -1,21 +1,26 @@ package org.owasp.wrongsecrets.challenges; +import static org.owasp.wrongsecrets.ConfigurationException.configError; + import com.google.common.base.Strings; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.stream.Collectors; +import java.util.Optional; +import java.util.function.Supplier; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import org.owasp.wrongsecrets.Challenges; +import org.owasp.wrongsecrets.ConfigurationException; import org.owasp.wrongsecrets.RuntimeEnvironment; import org.owasp.wrongsecrets.ScoreCard; -import org.owasp.wrongsecrets.challenges.docker.Challenge0; -import org.owasp.wrongsecrets.challenges.docker.Challenge30; import org.owasp.wrongsecrets.challenges.docker.Challenge37; import org.owasp.wrongsecrets.challenges.docker.Challenge8; +import org.owasp.wrongsecrets.challenges.docker.challenge30.Challenge30; +import org.owasp.wrongsecrets.definitions.ChallengeDefinition; +import org.owasp.wrongsecrets.definitions.ChallengeName; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.security.crypto.codec.Hex; @@ -32,8 +37,8 @@ public class ChallengesController { private final ScoreCard scoreCard; - private final List challenges; private final RuntimeEnvironment runtimeEnvironment; + private final Challenges challenges; @Value("${hints_enabled}") private boolean hintsEnabled; @@ -63,7 +68,7 @@ public class ChallengesController { public ChallengesController( ScoreCard scoreCard, - List challenges, + Challenges challenges, RuntimeEnvironment runtimeEnvironment, @Value("${spoiling_enabled}") boolean spoilingEnabled) { this.scoreCard = scoreCard; @@ -72,52 +77,92 @@ public ChallengesController( this.spoilingEnabled = spoilingEnabled; } - private void checkValidChallengeNumber(int id) { - // If the id is either negative or larger than the amount of challenges, return false. - if (id < 0 || id >= challenges.size()) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "challenge not found"); - } - } - /** * return a spoil of the secret Please note that there is no way to enable this in ctfMode: spoils * can never be returned during a CTF By default, in normal operations, spoils are enabled, unless * `spoilingEnabled` is set to false. * * @param model exchanged with the FE - * @param id id of the challenge * @return either a notification or a spoil */ - @GetMapping("/spoil-{id}") + @GetMapping("/spoil/{name}") @Hidden - public String spoiler(Model model, @PathVariable Integer id) { + public String spoiler(@PathVariable String name, Model model) { if (ctfModeEnabled) { model.addAttribute("spoiler", new Spoiler("Spoils are disabled in CTF mode")); } else if (!spoilingEnabled) { model.addAttribute("spoiler", new Spoiler("Spoils are disabled in the configuration")); } else { - var challenge = challenges.get(id).getChallenge(); - model.addAttribute("spoiler", challenge.spoiler()); + Optional spoilerFromRuntimeEnvironment = + challenges.findChallenge(name, runtimeEnvironment).map(c -> c.spoiler()); + Supplier spoilerFromRandomChallenge = + () -> { + var challengeDefinition = findByShortName(name); + return challenges.getChallenge(challengeDefinition).getFirst().spoiler(); + }; + + // We always want to show the spoiler even if we run in a non-supported environment + model.addAttribute( + "spoiler", spoilerFromRuntimeEnvironment.orElseGet(spoilerFromRandomChallenge)); } return "spoil"; } - @GetMapping("/challenge/{id}") - @Operation(description = "Returns the data for a given challenge's form interaction") - public String challenge(Model model, @PathVariable Integer id) { - checkValidChallengeNumber(id); - var challenge = challenges.get(id); + private void addChallengeUI(Model model, ChallengeDefinition challengeDefinition) { + model.addAttribute( + "challenge", + ChallengeUI.toUI( + challengeDefinition, + scoreCard, + runtimeEnvironment, + challenges.difficulties(), + challenges.getDefinitions().environments(), + challenges.navigation(challengeDefinition))); + } + /** + * checks whether challenge is enabled based on used runtimemode and CTF enablement. + * + * @return boolean true if the challenge can run. + */ + private boolean isChallengeEnabled(ChallengeDefinition challengeDefinition) { + if (runtimeEnvironment.runtimeInCTFMode()) { + return runtimeEnvironment.canRun(challengeDefinition) && challengeDefinition.ctf().enabled(); + } + return runtimeEnvironment.canRun(challengeDefinition); + } + + private ChallengeDefinition findByShortName(String shortName) { + return challenges + .findByShortName(shortName) + .orElseThrow( + () -> + new ResponseStatusException( + HttpStatus.NOT_FOUND, + configError( + "Challenge with short name '%s' not found, did you mean to use one of" + + " those %s?", + shortName, + challenges.findMatchingChallenges(shortName).stream() + .map(ChallengeName::url) + .toList()) + .get())); + } + + @GetMapping("/challenge/{name}") + @Operation(description = "Returns the data for a given challenge's form interaction") + public String challenge(Model model, @PathVariable String name) { + var challengeDefinition = findByShortName(name); model.addAttribute("challengeForm", new ChallengeForm("")); - model.addAttribute("challenge", challenge); + addChallengeUI(model, challengeDefinition); model.addAttribute("answerCorrect", null); model.addAttribute("answerIncorrect", null); model.addAttribute("solution", null); - if (!challenge.isChallengeEnabled()) { + if (!isChallengeEnabled(challengeDefinition)) { model.addAttribute("answerIncorrect", "This challenge has been disabled."); } - if (ctfModeEnabled && challenge.getChallenge() instanceof Challenge0) { + if (ctfModeEnabled && challenges.isFirstChallenge(challengeDefinition)) { if (!Strings.isNullOrEmpty(ctfServerAddress) && !ctfServerAddress.equals("not_set")) { model.addAttribute( "answerCorrect", @@ -133,41 +178,53 @@ public String challenge(Model model, @PathVariable Integer id) { } } enrichWithHintsAndReasons(model); - includeScoringStatus(model, challenge.getChallenge()); - addWarning(challenge.getChallenge(), model); + includeScoringStatus(model, challengeDefinition); + addWarning(challengeDefinition, model); fireEnding(model); return "challenge"; } - @PostMapping(value = "/challenge/{id}", params = "action=reset") + @PostMapping(value = "/challenge/{name}", params = "action=reset") @Operation(description = "Resets the state of a given challenge") public String reset( - @ModelAttribute ChallengeForm challengeForm, @PathVariable Integer id, Model model) { - checkValidChallengeNumber(id); - var challenge = challenges.get(id); - scoreCard.reset(challenge.getChallenge()); - - model.addAttribute("challenge", challenge); - includeScoringStatus(model, challenge.getChallenge()); - addWarning(challenge.getChallenge(), model); + @ModelAttribute ChallengeForm challengeForm, @PathVariable String name, Model model) { + var challengeDefinition = findByShortName(name); + scoreCard.reset(challengeDefinition); + + addChallengeUI(model, challengeDefinition); + includeScoringStatus(model, challengeDefinition); + addWarning(challengeDefinition, model); enrichWithHintsAndReasons(model); return "challenge"; } - @PostMapping(value = "/challenge/{id}", params = "action=submit") - @Operation(description = "Post your answer to the challenge for a given challenge ID") + @PostMapping(value = "/challenge/{name}", params = "action=submit") + @Operation(description = "Post your answer to the challenge for a given challenge") public String postController( - @ModelAttribute ChallengeForm challengeForm, Model model, @PathVariable Integer id) { - checkValidChallengeNumber(id); - var challenge = challenges.get(id); + @ModelAttribute ChallengeForm challengeForm, Model model, @PathVariable String name) { + var challengeDefinition = findByShortName(name); - if (!challenge.isChallengeEnabled()) { + if (!isChallengeEnabled(challengeDefinition)) { model.addAttribute("answerIncorrect", "This challenge has been disabled."); } else { - if (challenge.getChallenge().solved(challengeForm.solution())) { + var challenge = + challenges + .findChallenge(name, runtimeEnvironment) + .orElseThrow( + () -> + new ConfigurationException( + configError( + "Challenge '%s' not found for environment: '%s'", + name, runtimeEnvironment.getRuntimeEnvironment().name()))); + + if (challenge.answerCorrect(challengeForm.solution())) { + scoreCard.completeChallenge(challengeDefinition); + // TODO extract this to a separate method probably have separate handler classes in the + // configuration otherwise this is not maintainable, probably give the challenge a CTF + // method hook which you can override and do these kind of things in there. if (ctfModeEnabled) { if (!Strings.isNullOrEmpty(ctfServerAddress) && !ctfServerAddress.equals("not_set")) { - if (challenge.getChallenge() instanceof Challenge8) { + if (challenge instanceof Challenge8) { if (!Strings.isNullOrEmpty(keyToProvideToHost) && !keyToProvideToHost.equals( "not_set")) { // this means that it was overriden with a code that needs to be @@ -180,7 +237,7 @@ public String postController( + "for which you get your code: " + keyToProvideToHost); } - } else if (challenge.getChallenge() instanceof Challenge30) { + } else if (challenge instanceof Challenge30) { if (!Strings.isNullOrEmpty(keyToProvideToHostForChallenge30) && !keyToProvideToHostForChallenge30.equals( "not_set")) { // this means that it was overriden with a code that needs to be @@ -193,7 +250,7 @@ public String postController( + "for which you get your code: " + keyToProvideToHostForChallenge30); } - } else if (challenge.getChallenge() instanceof Challenge37) { + } else if (challenge instanceof Challenge37) { if (!Strings.isNullOrEmpty(getKeyToProvideToHostChallenge37) && !keyToProvideToHostForChallenge30.equals( "not_set")) { // this means that it was overriden with a code that needs to be @@ -214,7 +271,7 @@ public String postController( + ctfServerAddress); } } else { - String code = generateCode(challenge); + String code = generateCode(challengeDefinition); model.addAttribute( "answerCorrect", "Your answer is correct! " + "fill in the following code in CTF scoring: " + code); @@ -226,31 +283,30 @@ public String postController( model.addAttribute("answerIncorrect", "Your answer is incorrect, try harder ;-)"); } } - - model.addAttribute("challenge", challenge); - - includeScoringStatus(model, challenge.getChallenge()); - + addChallengeUI(model, challengeDefinition); + includeScoringStatus(model, challengeDefinition); enrichWithHintsAndReasons(model); fireEnding(model); return "challenge"; } - private String generateCode(ChallengeUI challenge) { + // TODO extract this to the challenge definition @see ChallengeAPIController with nested if + // statement + private String generateCode(ChallengeDefinition challenge) { SecretKeySpec secretKeySpec = new SecretKeySpec(ctfKey.getBytes(StandardCharsets.UTF_8), "HmacSHA1"); try { Mac mac = Mac.getInstance("HmacSHA1"); mac.init(secretKeySpec); - byte[] result = mac.doFinal(challenge.getName().getBytes(StandardCharsets.UTF_8)); + byte[] result = mac.doFinal(challenge.name().name().getBytes(StandardCharsets.UTF_8)); return new String(Hex.encode(result)); } catch (NoSuchAlgorithmException | InvalidKeyException e) { throw new RuntimeException(e); } } - private void includeScoringStatus(Model model, Challenge challenge) { + private void includeScoringStatus(Model model, ChallengeDefinition challenge) { model.addAttribute("totalPoints", scoreCard.getTotalReceivedPoints()); model.addAttribute("progress", "" + scoreCard.getProgress()); @@ -259,13 +315,14 @@ private void includeScoringStatus(Model model, Challenge challenge) { } } - private void addWarning(Challenge challenge, Model model) { + private void addWarning(ChallengeDefinition challenge, Model model) { if (!runtimeEnvironment.canRun(challenge)) { var warning = - challenge.supportedRuntimeEnvironments().stream() - .map(Enum::name) + challenge.supportedEnvironments().stream() .limit(1) - .collect(Collectors.joining()); + .map(env -> env.missingEnvironment().contents()) + .findFirst() + .orElse(null); model.addAttribute("missingEnvWarning", warning); } } @@ -277,9 +334,8 @@ private void enrichWithHintsAndReasons(Model model) { private void fireEnding(Model model) { var notCompleted = - challenges.stream() - .filter(ChallengeUI::isChallengeEnabled) - .map(ChallengeUI::getChallenge) + challenges.getDefinitions().challenges().stream() + .filter(def -> isChallengeEnabled(def)) .filter(this::challengeNotCompleted) .count(); if (notCompleted == 0) { @@ -287,7 +343,7 @@ private void fireEnding(Model model) { } } - private boolean challengeNotCompleted(Challenge challenge) { + private boolean challengeNotCompleted(ChallengeDefinition challenge) { return !scoreCard.getChallengeCompleted(challenge); } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/ChallengesCtfController.java b/src/main/java/org/owasp/wrongsecrets/challenges/ChallengesCtfController.java new file mode 100644 index 000000000..8de68bf98 --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/challenges/ChallengesCtfController.java @@ -0,0 +1,87 @@ +package org.owasp.wrongsecrets.challenges; + +import io.swagger.v3.oas.annotations.Operation; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import org.owasp.wrongsecrets.Challenges; +import org.owasp.wrongsecrets.RuntimeEnvironment; +import org.owasp.wrongsecrets.ScoreCard; +import org.owasp.wrongsecrets.definitions.ChallengeDefinition; +import org.owasp.wrongsecrets.definitions.ChallengeDefinitionsConfiguration; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Used to request and generate the required json for setting up a CTF through juiceshop CTF CLI. + */ +@Slf4j +@RestController +public class ChallengesCtfController { + + private final ScoreCard scoreCard; + private final Challenges challenges; + private final ChallengeDefinitionsConfiguration wrongSecretsConfiguration; + private final RuntimeEnvironment runtimeEnvironment; + + public ChallengesCtfController( + ScoreCard scoreCard, + Challenges challenges, + RuntimeEnvironment runtimeEnvironment, + ChallengeDefinitionsConfiguration wrongSecretsConfiguration) { + this.scoreCard = scoreCard; + this.challenges = challenges; + this.wrongSecretsConfiguration = wrongSecretsConfiguration; + this.runtimeEnvironment = runtimeEnvironment; + } + + @GetMapping( + value = {"/api/Challenges", "/api/challenges"}, + produces = MediaType.APPLICATION_JSON_VALUE) + @Operation( + summary = "Gives all challenges back in a jsonArray, to be used with the Juiceshop CTF cli") + public String getChallenges() { + List definitions = challenges.getDefinitions().challenges(); + JSONObject json = new JSONObject(); + JSONArray jsonArray = new JSONArray(); + for (int i = 0; i < definitions.size(); i++) { + ChallengeDefinition definition = definitions.get(i); + JSONObject jsonChallenge = new JSONObject(); + jsonChallenge.put("id", i + 1); + jsonChallenge.put("name", definition.name().name()); + jsonChallenge.put("key", definition.name().url()); + jsonChallenge.put("category", getCategory() + " - " + definition.category().category()); + jsonChallenge.put( + "description", + definition.source(runtimeEnvironment).map(s -> s.explanation().contents()).orElse(null)); + jsonChallenge.put( + "hint", definition.source(runtimeEnvironment).map(s -> s.hint().contents()).orElse(null)); + jsonChallenge.put("solved", scoreCard.getChallengeCompleted(definition)); + jsonChallenge.put("disabledEnv", getDisabledEnv(definition)); + jsonChallenge.put("difficulty", definition.difficulty().difficulty()); + jsonArray.add(jsonChallenge); + } + json.put("status", "success"); + json.put("data", jsonArray); + String result = json.toJSONString(); + log.trace("returning {}", result); + return result; + } + + private String getCategory() { + return this.wrongSecretsConfiguration.environments().stream() + .filter(e -> e.equals(runtimeEnvironment.getRuntimeEnvironment())) + .findFirst() + .map(e -> e.ctf()) + .orElse("Unknown"); + } + + private String getDisabledEnv(ChallengeDefinition challengeDefinition) { + if (runtimeEnvironment.canRun(challengeDefinition)) { + return runtimeEnvironment.getRuntimeEnvironment().name(); + } + return null; + } +} diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/Difficulty.java b/src/main/java/org/owasp/wrongsecrets/challenges/Difficulty.java deleted file mode 100644 index 10bcce0bc..000000000 --- a/src/main/java/org/owasp/wrongsecrets/challenges/Difficulty.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.owasp.wrongsecrets.challenges; - -/** Representation of the difficulty levels. */ -public class Difficulty { - - public static final int EASY = 1; - public static final int NORMAL = 2; - public static final int HARD = 3; - public static final int EXPERT = 4; - public static final int MASTER = 5; - - private static final int[] allLevels = new int[] {EASY, NORMAL, HARD, EXPERT, MASTER}; - - public static int totalOfDifficultyLevels() { - return allLevels.length; - } -} diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge10.java b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge10.java index 4c8f37d9f..5b59d5ce4 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge10.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge10.java @@ -1,40 +1,27 @@ package org.owasp.wrongsecrets.challenges.cloud; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.AWS; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.AZURE; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.GCP; - import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; +import org.owasp.wrongsecrets.challenges.Challenge; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** Cloud challenge that leverages the CSI secrets driver of the cloud you are running in. */ @Component @Slf4j -@Order(10) -public class Challenge10 extends CloudChallenge { +public class Challenge10 implements Challenge { private final String awsDefaultValue; private final String challengeAnswer; public Challenge10( - ScoreCard scoreCard, @Value("${secretmountpath}") String filePath, @Value("${default_aws_value_challenge_10}") String awsDefaultValue, - @Value("${FILENAME_CHALLENGE10}") String fileName, - RuntimeEnvironment runtimeEnvironment) { - super(scoreCard, runtimeEnvironment); + @Value("${FILENAME_CHALLENGE10}") String fileName) { this.awsDefaultValue = awsDefaultValue; this.challengeAnswer = getCloudChallenge9and10Value(filePath, fileName); } @@ -65,26 +52,4 @@ private String getCloudChallenge9and10Value(String filePath, String fileName) { return awsDefaultValue; } } - - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(GCP, AWS, AZURE); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EXPERT; - } - - /** {@inheritDoc} Uses CSI Driver */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.CSI.id; - } - - @Override - public boolean canRunInCTFMode() { - return true; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge9.java b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge9.java index 6a78238e5..d8636038e 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge9.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge9.java @@ -1,29 +1,19 @@ package org.owasp.wrongsecrets.challenges.cloud; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.AWS; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.AZURE; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.GCP; - import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; +import org.owasp.wrongsecrets.challenges.Challenge; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** Cloud challenge which focuses on Terraform and secrets. */ @Component @Slf4j -@Order(9) -public class Challenge9 extends CloudChallenge { +public class Challenge9 implements Challenge { private final String awsDefaultValue; private final String challengeAnswer; @@ -31,20 +21,15 @@ public class Challenge9 extends CloudChallenge { /** * Cloud challenge which focuses on Terraform and secrets. * - * @param scoreCard required for score keeping * @param filePath used to mount in the secrets store where teh actual secret lands in from the * cloud * @param awsDefaultValue used to indicate whether a cloud setup is enabled. * @param fileName name of the actual secret file mounted on the filePath - * @param runtimeEnvironment runtime env required to run this in. */ public Challenge9( - ScoreCard scoreCard, @Value("${secretmountpath}") String filePath, @Value("${default_aws_value_challenge_9}") String awsDefaultValue, - @Value("${FILENAME_CHALLENGE9}") String fileName, - RuntimeEnvironment runtimeEnvironment) { - super(scoreCard, runtimeEnvironment); + @Value("${FILENAME_CHALLENGE9}") String fileName) { this.awsDefaultValue = awsDefaultValue; this.challengeAnswer = getCloudChallenge9and10Value(filePath, fileName); } @@ -75,26 +60,4 @@ private String getCloudChallenge9and10Value(String filePath, String fileName) { return awsDefaultValue; } } - - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(GCP, AWS, AZURE); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.HARD; - } - - /** {@inheritDoc} Uses Terraform */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.TERRAFORM.id; - } - - @Override - public boolean canRunInCTFMode() { - return true; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/CloudChallenge.java b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/CloudChallenge.java deleted file mode 100644 index e635f18f5..000000000 --- a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/CloudChallenge.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.owasp.wrongsecrets.challenges.cloud; - -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; -import org.owasp.wrongsecrets.challenges.Challenge; - -/** - * Abstract class used to provide convinient wrapper helpers for cloud type detection for the cloud - * challenges. - */ -public abstract class CloudChallenge extends Challenge { - - private final RuntimeEnvironment runtimeEnvironment; - - protected CloudChallenge(ScoreCard scoreCard, RuntimeEnvironment runtimeEnvironment) { - super(scoreCard); - this.runtimeEnvironment = runtimeEnvironment; - } - - /** - * boolean showing whether we run in AWS. - * - * @return true if we are on AWS - */ - public boolean isAWS() { - return this.runtimeEnvironment.getRuntimeEnvironment() == RuntimeEnvironment.Environment.AWS; - } - - /** - * boolean showing whether we run in GCP. - * - * @return true if we are on GCP - */ - public boolean isGCP() { - return this.runtimeEnvironment.getRuntimeEnvironment() == RuntimeEnvironment.Environment.GCP; - } - - /** - * boolean showing whether we run in Azure. - * - * @return true if we are on Azure - */ - public boolean isAzure() { - return this.runtimeEnvironment.getRuntimeEnvironment() == RuntimeEnvironment.Environment.AZURE; - } - - @Override - public String getExplanation() { - return getData(super.getExplanation()); - } - - @Override - public String getHint() { - return getData(super.getHint()); - } - - @Override - public String getReason() { - return getData(super.getReason()); - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - - private String getData(String defaultAWsPath) { - RuntimeEnvironment.Environment env = runtimeEnvironment.getRuntimeEnvironment(); - return switch (env) { - case GCP -> String.format("%s%s", defaultAWsPath, "-gcp"); - case AZURE -> String.format("%s%s", defaultAWsPath, "-azure"); - default -> String.format("%s", defaultAWsPath); // Default is AWS - }; - } -} diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge11.java b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/challenge11/Challenge11Aws.java similarity index 52% rename from src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge11.java rename to src/main/java/org/owasp/wrongsecrets/challenges/cloud/challenge11/Challenge11Aws.java index 72d636bf6..74a11b6f9 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge11.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/challenge11/Challenge11Aws.java @@ -1,28 +1,16 @@ -package org.owasp.wrongsecrets.challenges.cloud; +package org.owasp.wrongsecrets.challenges.cloud.challenge11; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.AWS; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.AZURE; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.GCP; - -import com.google.api.gax.rpc.ApiException; -import com.google.cloud.secretmanager.v1.AccessSecretVersionResponse; -import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; -import com.google.cloud.secretmanager.v1.SecretVersionName; import com.google.common.base.Strings; +import com.google.common.base.Supplier; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; +import org.owasp.wrongsecrets.challenges.Challenge; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.ssm.SsmClient; @@ -37,101 +25,55 @@ /** Cloud challenge which uses IAM privilelge escalation (differentiating per cloud). */ @Component @Slf4j -@Order(11) -public class Challenge11 extends CloudChallenge { +public class Challenge11Aws implements Challenge { private final String awsRoleArn; private final String awsRegion; private final String tokenFileLocation; private final String awsDefaultValue; - private final String gcpDefaultValue; - private final String azureDefaultValue; - private final String challengeAnswer; - private final String projectId; - private final String azureVaultUri; - private final String azureWrongSecret3; - + private final Supplier challengeAnswer; private final String ctfValue; - private final boolean ctfEnabled; - public Challenge11( - ScoreCard scoreCard, + public Challenge11Aws( @Value("${AWS_ROLE_ARN}") String awsRoleArn, @Value("${AWS_WEB_IDENTITY_TOKEN_FILE}") String tokenFileLocation, @Value("${AWS_REGION}") String awsRegion, - @Value("${default_gcp_value}") String gcpDefaultValue, @Value("${default_aws_value}") String awsDefaultValue, - @Value("${default_azure_value}") String azureDefaultValue, - @Value("${spring.cloud.azure.keyvault.secret.property-sources[0].endpoint}") - String azureVaultUri, - @Value("${wrongsecret-3}") String azureWrongSecret3, // Exclusively auto-wired for Azure - @Value("${GOOGLE_CLOUD_PROJECT}") String projectId, @Value("${default_aws_value_challenge_11}") String ctfValue, - @Value("${ctf_enabled}") boolean ctfEnabled, - RuntimeEnvironment runtimeEnvironment) { - super(scoreCard, runtimeEnvironment); + @Value("${ctf_enabled}") boolean ctfEnabled) { this.awsRoleArn = awsRoleArn; this.tokenFileLocation = tokenFileLocation; this.awsRegion = awsRegion; this.awsDefaultValue = awsDefaultValue; - this.gcpDefaultValue = gcpDefaultValue; - this.azureDefaultValue = azureDefaultValue; - this.projectId = projectId; - this.azureVaultUri = azureVaultUri; - this.azureWrongSecret3 = azureWrongSecret3; this.ctfValue = ctfValue; this.ctfEnabled = ctfEnabled; - this.challengeAnswer = getChallenge11Value(runtimeEnvironment); + this.challengeAnswer = getChallenge11Value(); } /** {@inheritDoc} */ @Override public Spoiler spoiler() { - return new Spoiler(challengeAnswer); + return new Spoiler(challengeAnswer.get()); } /** {@inheritDoc} */ @Override public boolean answerCorrect(String answer) { - return challengeAnswer.equals(answer); - } - - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(AWS, GCP, AZURE); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EXPERT; + return challengeAnswer.get().equals(answer); } - /** {@inheritDoc} Uses IAM Privilege escalation */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.IAM.id; - } - - private String getChallenge11Value(RuntimeEnvironment runtimeEnvironment) { + private Supplier getChallenge11Value() { if (!ctfEnabled) { - if (runtimeEnvironment != null && runtimeEnvironment.getRuntimeEnvironment() != null) { - return switch (runtimeEnvironment.getRuntimeEnvironment()) { - case AWS -> getAWSChallenge11Value(); - case GCP -> getGCPChallenge11Value(); - case AZURE -> getAzureChallenge11Value(); - default -> "please_use_supported_cloud_env"; - }; - } + return () -> getAWSChallenge11Value(); } else if (!Strings.isNullOrEmpty(ctfValue) && !Strings.isNullOrEmpty(awsDefaultValue) && !ctfValue.equals(awsDefaultValue)) { - return ctfValue; + return () -> ctfValue; } log.info("CTF enabled, skipping challenge11"); - return "please_use_supported_cloud_env"; + return () -> "please_use_supported_cloud_env"; } @SuppressFBWarnings( @@ -184,41 +126,4 @@ private String getAWSChallenge11Value() { } return awsDefaultValue; } - - private String getGCPChallenge11Value() { - log.info("pre-checking GCP data"); - if (isGCP()) { - log.info("Getting credentials from GCP"); - // Based on https://cloud.google.com/secret-manager/docs/reference/libraries - try (SecretManagerServiceClient client = SecretManagerServiceClient.create()) { - log.info("Fetching secret form Google Secret Manager..."); - SecretVersionName secretVersionName = - SecretVersionName.of(projectId, "wrongsecret-3", "latest"); - AccessSecretVersionResponse response = client.accessSecretVersion(secretVersionName); - return response.getPayload().getData().toStringUtf8(); - } catch (ApiException e) { - log.error("Exception getting secret: ", e); - } catch (IOException e) { - log.error("Could not get the web identity token, due to ", e); - } - } else { - log.info("Skipping credentials from GCP"); - } - return gcpDefaultValue; - } - - private String getAzureChallenge11Value() { - log.info("pre-checking Azure data"); - if (isAzure()) { - log.info(String.format("Using Azure Key Vault URI: %s", azureVaultUri)); - return azureWrongSecret3; - } - log.error("Fetching secret from Azure did not work, returning default"); - return azureDefaultValue; - } - - @Override - public boolean canRunInCTFMode() { - return false; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/challenge11/Challenge11Azure.java b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/challenge11/Challenge11Azure.java new file mode 100644 index 000000000..f2f7fa052 --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/challenge11/Challenge11Azure.java @@ -0,0 +1,70 @@ +package org.owasp.wrongsecrets.challenges.cloud.challenge11; + +import com.google.common.base.Strings; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import lombok.extern.slf4j.Slf4j; +import org.owasp.wrongsecrets.RuntimeEnvironment; +import org.owasp.wrongsecrets.challenges.Challenge; +import org.owasp.wrongsecrets.challenges.Spoiler; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** Cloud challenge which uses IAM privilelge escalation (differentiating per cloud). */ +@Component +@Slf4j +public class Challenge11Azure implements Challenge { + + private final String azureDefaultValue; + private final Supplier challengeAnswer; + private final String azureVaultUri; + private final String azureWrongSecret3; + private final String ctfValue; + private final boolean ctfEnabled; + + public Challenge11Azure( + @Value("${default_azure_value}") String azureDefaultValue, + @Value("${spring.cloud.azure.keyvault.secret.property-sources[0].endpoint}") + String azureVaultUri, + @Value("${wrongsecret-3}") String azureWrongSecret3, // Exclusively auto-wired for Azure + @Value("${default_aws_value_challenge_11}") String ctfValue, + @Value("${ctf_enabled}") boolean ctfEnabled, + RuntimeEnvironment runtimeEnvironment) { + this.azureDefaultValue = azureDefaultValue; + this.azureVaultUri = azureVaultUri; + this.azureWrongSecret3 = azureWrongSecret3; + this.ctfValue = ctfValue; + this.ctfEnabled = ctfEnabled; + this.challengeAnswer = Suppliers.memoize(getChallenge11Value(runtimeEnvironment)); + } + + /** {@inheritDoc} */ + @Override + public Spoiler spoiler() { + return new Spoiler(challengeAnswer.get()); + } + + /** {@inheritDoc} */ + @Override + public boolean answerCorrect(String answer) { + return challengeAnswer.get().equals(answer); + } + + private Supplier getChallenge11Value(RuntimeEnvironment runtimeEnvironment) { + if (!ctfEnabled) { + return () -> getAzureChallenge11Value(); + } else if (!Strings.isNullOrEmpty(ctfValue) + && !Strings.isNullOrEmpty(azureDefaultValue) + && !ctfValue.equals(azureDefaultValue)) { + return () -> ctfValue; + } + + log.info("CTF enabled, skipping challenge11"); + return () -> "please_use_supported_cloud_env"; + } + + private String getAzureChallenge11Value() { + log.info(String.format("Using Azure Key Vault URI: %s", azureVaultUri)); + return azureWrongSecret3; + } +} diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/challenge11/Challenge11Gcp.java b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/challenge11/Challenge11Gcp.java new file mode 100644 index 000000000..580116e7a --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/challenge11/Challenge11Gcp.java @@ -0,0 +1,81 @@ +package org.owasp.wrongsecrets.challenges.cloud.challenge11; + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.secretmanager.v1.AccessSecretVersionResponse; +import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; +import com.google.cloud.secretmanager.v1.SecretVersionName; +import com.google.common.base.Strings; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import java.io.IOException; +import lombok.extern.slf4j.Slf4j; +import org.owasp.wrongsecrets.challenges.Challenge; +import org.owasp.wrongsecrets.challenges.Spoiler; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** Cloud challenge which uses IAM privilelge escalation (differentiating per cloud). */ +@Component +@Slf4j +public class Challenge11Gcp implements Challenge { + + private final String gcpDefaultValue; + private final Supplier challengeAnswer; + private final String projectId; + private final String ctfValue; + private final boolean ctfEnabled; + + public Challenge11Gcp( + @Value("${default_gcp_value}") String gcpDefaultValue, + @Value("${GOOGLE_CLOUD_PROJECT}") String projectId, + @Value("${default_aws_value_challenge_11}") String ctfValue, + @Value("${ctf_enabled}") boolean ctfEnabled) { + this.gcpDefaultValue = gcpDefaultValue; + this.projectId = projectId; + this.ctfValue = ctfValue; + this.ctfEnabled = ctfEnabled; + this.challengeAnswer = Suppliers.memoize(getChallenge11Value()); + } + + /** {@inheritDoc} */ + @Override + public Spoiler spoiler() { + return new Spoiler(challengeAnswer.get()); + } + + /** {@inheritDoc} */ + @Override + public boolean answerCorrect(String answer) { + return challengeAnswer.get().equals(answer); + } + + private Supplier getChallenge11Value() { + if (!ctfEnabled) { + return () -> getGCPChallenge11Value(); + } else if (!Strings.isNullOrEmpty(ctfValue) + && !Strings.isNullOrEmpty(gcpDefaultValue) + && !ctfValue.equals(gcpDefaultValue)) { + return () -> ctfValue; + } + + log.info("CTF enabled, skipping challenge11"); + return () -> "please_use_supported_cloud_env"; + } + + private String getGCPChallenge11Value() { + log.info("Getting credentials from GCP"); + // Based on https://cloud.google.com/secret-manager/docs/reference/libraries + try (SecretManagerServiceClient client = SecretManagerServiceClient.create()) { + log.info("Fetching secret form Google Secret Manager..."); + SecretVersionName secretVersionName = + SecretVersionName.of(projectId, "wrongsecret-3", "latest"); + AccessSecretVersionResponse response = client.accessSecretVersion(secretVersionName); + return response.getPayload().getData().toStringUtf8(); + } catch (ApiException e) { + log.error("Exception getting secret: ", e); + } catch (IOException e) { + log.error("Could not get the web identity token, due to ", e); + } + return gcpDefaultValue; + } +} diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge0.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge0.java index 68c8c6c55..e9656ca65 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge0.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge0.java @@ -1,24 +1,12 @@ package org.owasp.wrongsecrets.challenges.docker; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.DOCKER; - -import java.util.List; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** Introduction challenge to get a user introduced with the setup. */ @Component -@Order(0) -public class Challenge0 extends Challenge { - - public Challenge0(ScoreCard scoreCard) { - super(scoreCard); - } +public class Challenge0 implements Challenge { /** {@inheritDoc} */ @Override @@ -28,37 +16,10 @@ public Spoiler spoiler() { /** {@inheritDoc} */ @Override - protected boolean answerCorrect(String answer) { + public boolean answerCorrect(String answer) { return getData().equals(answer); } - @Override - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EASY; - } - - @Override - public String getTech() { - return "Intro"; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - - @Override - public boolean canRunInCTFMode() { - return true; - } - private String getData() { return "The first answer"; } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge1.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge1.java index abaee43af..88bb8631b 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge1.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge1.java @@ -1,30 +1,12 @@ package org.owasp.wrongsecrets.challenges.docker; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.DOCKER; - -import java.util.List; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** Challenge to find the hardcoded password in code. */ @Component -@Order(1) -public class Challenge1 extends Challenge { - - public Challenge1(ScoreCard scoreCard) { - super(scoreCard); - } - - @Override - public boolean canRunInCTFMode() { - return true; - } +public class Challenge1 implements Challenge { /** {@inheritDoc} */ @Override @@ -37,26 +19,4 @@ public Spoiler spoiler() { public boolean answerCorrect(String answer) { return WrongSecretsConstants.password.equals(answer); } - - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EASY; - } - - /** {@inheritDoc} Git based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.GIT.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge12.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge12.java index c6c4d7042..afd22baf8 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge12.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge12.java @@ -4,37 +4,23 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** Challenge focused on filesystem issues in docker container due to workdir copying. */ @Slf4j @Component -@Order(12) -public class Challenge12 extends Challenge { +public class Challenge12 implements Challenge { private final String dockerMountPath; - public Challenge12( - ScoreCard scoreCard, @Value("${challengedockermtpath}") String dockerMountPath) { - super(scoreCard); + public Challenge12(@Value("${challengedockermtpath}") String dockerMountPath) { this.dockerMountPath = dockerMountPath; } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -48,29 +34,6 @@ public boolean answerCorrect(String answer) { return getActualData().equals(answer); } - @Override - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.HARD; - } - - /** {@inheritDoc} Docker based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.DOCKER.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - @SuppressFBWarnings( value = "PATH_TRAVERSAL_IN", justification = "The location of the dockerMountPath is based on an Env Var") diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge13.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge13.java index 2be07e65c..4ca47c183 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge13.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge13.java @@ -4,28 +4,21 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Base64; -import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** Challenge focused on showing CI/CD issues through Github Actions. */ @Slf4j @Component -@Order(13) -public class Challenge13 extends Challenge { +public class Challenge13 implements Challenge { private final String plainText; private final String cipherText; @@ -41,48 +34,17 @@ public Spoiler spoiler() { } public Challenge13( - ScoreCard scoreCard, - @Value("${plainText13}") String plainText, - @Value("${cipherText13}") String cipherText) { - super(scoreCard); + @Value("${plainText13}") String plainText, @Value("${cipherText13}") String cipherText) { this.plainText = plainText; this.cipherText = cipherText; } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override - protected boolean answerCorrect(String answer) { + public boolean answerCorrect(String answer) { return isKeyCorrect(answer); } - /** {@inheritDoc} */ - @Override - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.HARD; - } - - /** {@inheritDoc} CI/CD based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.CICD.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - private boolean isKeyCorrect(String base64EncodedKey) { if (Strings.isNullOrEmpty(base64EncodedKey) || !isBase64(base64EncodedKey) diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge14.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge14.java index f622a6a87..4044643e1 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge14.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge14.java @@ -6,7 +6,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.List; import lombok.extern.slf4j.Slf4j; import org.linguafranca.pwdb.Database; import org.linguafranca.pwdb.kdbx.KdbxCreds; @@ -14,42 +13,29 @@ import org.linguafranca.pwdb.kdbx.simple.SimpleEntry; import org.linguafranca.pwdb.kdbx.simple.SimpleGroup; import org.linguafranca.pwdb.kdbx.simple.SimpleIcon; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about having a weak password for your password manager. */ @Slf4j @Component -@Order(14) -public class Challenge14 extends Challenge { +public class Challenge14 implements Challenge { private final String keepassxPassword; private final String defaultKeepassValue; private final String filePath; public Challenge14( - ScoreCard scoreCard, @Value("${keepasxpassword}") String keepassxPassword, @Value("${KEEPASS_BROKEN}") String defaultKeepassValue, @Value("${keepasspath}") String filePath) { - super(scoreCard); this.keepassxPassword = keepassxPassword; this.defaultKeepassValue = defaultKeepassValue; this.filePath = filePath; } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -58,33 +44,10 @@ public Spoiler spoiler() { /** {@inheritDoc} */ @Override - protected boolean answerCorrect(String answer) { + public boolean answerCorrect(String answer) { return isanswerCorrectInKeeyPassx(answer); } - /** {@inheritDoc} */ - @Override - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EXPERT; - } - - /** {@inheritDoc} Password manager based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.PASSWORD_MANAGER.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - @SuppressFBWarnings("PATH_TRAVERSAL_IN") private String findAnswer() { if (Strings.isNullOrEmpty(keepassxPassword)) { diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge15.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge15.java index a830f6465..e50ac67ea 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge15.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge15.java @@ -4,42 +4,29 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Base64; -import java.util.List; import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about AWS keys in git history, with actual canarytokens. */ @Slf4j @Component -@Order(15) -public class Challenge15 extends Challenge { +public class Challenge15 implements Challenge { private final String ciphterText; private final String encryptionKey; - public Challenge15(ScoreCard scoreCard, @Value("${challenge15ciphertext}") String ciphterText) { - super(scoreCard); + public Challenge15(@Value("${challenge15ciphertext}") String ciphterText) { this.ciphterText = ciphterText; encryptionKey = Base64.getEncoder().encodeToString("this is it for now".getBytes(StandardCharsets.UTF_8)); } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -48,34 +35,11 @@ public Spoiler spoiler() { /** {@inheritDoc} */ @Override - protected boolean answerCorrect(String answer) { + public boolean answerCorrect(String answer) { String correctString = quickDecrypt(ciphterText); return answer.equals(correctString) || minimummatch_found(answer); } - @Override - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.NORMAL; - } - - /** {@inheritDoc} Git based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.GIT.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - private boolean minimummatch_found(String answer) { if (!Strings.isNullOrEmpty(answer)) { if (answer.length() < 19) { diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge16.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge16.java index bcf4380f2..9097d2d02 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge16.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge16.java @@ -4,37 +4,23 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about having a secret obfuscated in the front-end code. */ @Slf4j @Component -@Order(16) -public class Challenge16 extends Challenge { +public class Challenge16 implements Challenge { private final String dockerMountPath; - public Challenge16( - ScoreCard scoreCard, @Value("${challengedockermtpath}") String dockerMountPath) { - super(scoreCard); + public Challenge16(@Value("${challengedockermtpath}") String dockerMountPath) { this.dockerMountPath = dockerMountPath; } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -48,29 +34,6 @@ public boolean answerCorrect(String answer) { return getActualData().equals(answer); } - @Override - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.HARD; - } - - /** {@inheritDoc} Frontend based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.FRONTEND.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - @SuppressFBWarnings( value = "PATH_TRAVERSAL_IN", justification = "The location of the dockerMountPath is based on an Env Var") diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge17.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge17.java index ac6511159..dd563a4eb 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge17.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge17.java @@ -4,37 +4,23 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about having secrets in copied in bash history as part of a container. */ @Slf4j @Component -@Order(17) -public class Challenge17 extends Challenge { +public class Challenge17 implements Challenge { private final String dockerMountPath; - public Challenge17( - ScoreCard scoreCard, @Value("${challengedockermtpath}") String dockerMountPath) { - super(scoreCard); + public Challenge17(@Value("${challengedockermtpath}") String dockerMountPath) { this.dockerMountPath = dockerMountPath; } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -47,29 +33,6 @@ public boolean answerCorrect(String answer) { return getActualData().equals(answer); } - /** {@inheritDoc} */ - @Override - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.HARD; - } - - /** {@inheritDoc} Docker based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.DOCKER.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - @SuppressFBWarnings( value = "PATH_TRAVERSAL_IN", justification = "The location of the dockerMountPath is based on an Env Var") diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge18.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge18.java index 81047b5d5..9fffc50ea 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge18.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge18.java @@ -1,44 +1,29 @@ package org.owasp.wrongsecrets.challenges.docker; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.DOCKER; - import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; -import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.security.crypto.codec.Hex; import org.springframework.stereotype.Component; /** This challenge is about finding the value of a secret through weak hash mechanisms. */ -@Component -@Order(18) @Slf4j -public class Challenge18 extends Challenge { +@Component +public class Challenge18 implements Challenge { private final String hashPassword; private static final String md5Hash = "MD5"; private static final String sha1Hash = "SHA1"; - public Challenge18(ScoreCard scoreCard, @Value("aHVudGVyMg==") String hashPassword) { - super(scoreCard); + public Challenge18(@Value("aHVudGVyMg==") String hashPassword) { this.hashPassword = hashPassword; } - @Override - public boolean canRunInCTFMode() { - return true; - } - private String base64Decode(String base64) { byte[] decodedBytes = Base64.getDecoder().decode(base64); return new String(decodedBytes, StandardCharsets.UTF_8); @@ -69,26 +54,4 @@ public boolean answerCorrect(String answer) { || calculateHash(sha1Hash, base64Decode(hashPassword)) .equals(calculateHash(sha1Hash, answer)); } - - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.MASTER; - } - - /** {@inheritDoc} Cryptography based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.CRYPTOGRAPHY.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge19.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge19.java index 3714d5a42..53285d78a 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge19.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge19.java @@ -1,38 +1,23 @@ package org.owasp.wrongsecrets.challenges.docker; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.DOCKER; - -import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.owasp.wrongsecrets.challenges.docker.binaryexecution.BinaryExecutionHelper; import org.owasp.wrongsecrets.challenges.docker.binaryexecution.MuslDetectorImpl; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about finding a secret hardcoded in a C binary. */ -@Component -@Order(19) @Slf4j -public class Challenge19 extends Challenge { +@Component +public class Challenge19 implements Challenge { private final BinaryExecutionHelper binaryExecutionHelper; - public Challenge19(ScoreCard scoreCard) { - super(scoreCard); + public Challenge19() { this.binaryExecutionHelper = new BinaryExecutionHelper(19, new MuslDetectorImpl()); } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -46,26 +31,4 @@ public boolean answerCorrect(String answer) { .executeCommand(answer, "wrongsecrets-c") .equals("This is correct! Congrats!"); } - - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EXPERT; - } - - /** {@inheritDoc} Binary based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.BINARY.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge2.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge2.java index b4b6d4e0e..01bab1057 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge2.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge2.java @@ -1,16 +1,8 @@ package org.owasp.wrongsecrets.challenges.docker; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.DOCKER; - -import java.util.List; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** @@ -19,28 +11,19 @@ * The javadoc is generated using ChatGPT. */ @Component -@Order(2) -public class Challenge2 extends Challenge { +public class Challenge2 implements Challenge { private final String hardcodedPassword; /** * Constructor for creating a new Challenge2 object. * - * @param scoreCard The scorecard object used for tracking points. * @param hardcodedPassword The hardcoded password for the challenge. */ - public Challenge2(ScoreCard scoreCard, @Value("${password}") String hardcodedPassword) { - super(scoreCard); + public Challenge2(@Value("${password}") String hardcodedPassword) { this.hardcodedPassword = hardcodedPassword; } - /** {@inheritDoc} This challenge can always run in CTF mode. */ - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -58,27 +41,4 @@ public Spoiler spoiler() { public boolean answerCorrect(String answer) { return hardcodedPassword.equals(answer); } - - /** {@inheritDoc} This challenge supports the Docker runtime environment. */ - public List supportedRuntimeEnvironments() { - return List.of(DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EASY; - } - - /** {@inheritDoc} Git based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.GIT.id; - } - - /** {@inheritDoc} This challenge is not limited when hosted online. */ - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge20.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge20.java index f33294304..0b2d983a1 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge20.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge20.java @@ -1,38 +1,23 @@ package org.owasp.wrongsecrets.challenges.docker; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.DOCKER; - -import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.owasp.wrongsecrets.challenges.docker.binaryexecution.BinaryExecutionHelper; import org.owasp.wrongsecrets.challenges.docker.binaryexecution.MuslDetectorImpl; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about finding a secret hardcoded in a C++ binary. */ -@Component -@Order(20) @Slf4j -public class Challenge20 extends Challenge { +@Component +public class Challenge20 implements Challenge { private final BinaryExecutionHelper binaryExecutionHelper; - public Challenge20(ScoreCard scoreCard) { - super(scoreCard); + public Challenge20() { this.binaryExecutionHelper = new BinaryExecutionHelper(20, new MuslDetectorImpl()); } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -46,26 +31,4 @@ public boolean answerCorrect(String answer) { .executeCommand(answer, "wrongsecrets-cplus") .equals("This is correct! Congrats!"); } - - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EXPERT; - } - - /** {@inheritDoc} Binary based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.BINARY.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge21.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge21.java index 46267e296..8c25ed482 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge21.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge21.java @@ -1,38 +1,23 @@ package org.owasp.wrongsecrets.challenges.docker; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.DOCKER; - -import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.owasp.wrongsecrets.challenges.docker.binaryexecution.BinaryExecutionHelper; import org.owasp.wrongsecrets.challenges.docker.binaryexecution.MuslDetectorImpl; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about finding a secret hardcoded in a Golang binary. */ -@Component -@Order(21) @Slf4j -public class Challenge21 extends Challenge { +@Component +public class Challenge21 implements Challenge { private final BinaryExecutionHelper binaryExecutionHelper; - public Challenge21(ScoreCard scoreCard) { - super(scoreCard); + public Challenge21() { this.binaryExecutionHelper = new BinaryExecutionHelper(21, new MuslDetectorImpl()); } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -44,26 +29,4 @@ public Spoiler spoiler() { public boolean answerCorrect(String answer) { return binaryExecutionHelper.executeGoCommand(answer).equals("This is correct! Congrats!"); } - - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.MASTER; - } - - /** {@inheritDoc} Binary based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.BINARY.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge22.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge22.java index edafcdf7c..2cfef6442 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge22.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge22.java @@ -1,38 +1,23 @@ package org.owasp.wrongsecrets.challenges.docker; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.DOCKER; - -import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.owasp.wrongsecrets.challenges.docker.binaryexecution.BinaryExecutionHelper; import org.owasp.wrongsecrets.challenges.docker.binaryexecution.MuslDetectorImpl; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about finding a secret hardcoded in a Rust binary. */ -@Component -@Order(22) @Slf4j -public class Challenge22 extends Challenge { +@Component +public class Challenge22 implements Challenge { private final BinaryExecutionHelper binaryExecutionHelper; - public Challenge22(ScoreCard scoreCard) { - super(scoreCard); + public Challenge22() { this.binaryExecutionHelper = new BinaryExecutionHelper(22, new MuslDetectorImpl()); } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -46,26 +31,4 @@ public boolean answerCorrect(String answer) { .executeCommand(answer, "wrongsecrets-rust") .equals("This is correct! Congrats!"); } - - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.MASTER; - } - - /** {@inheritDoc} Binary based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.BINARY.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge23.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge23.java index 393c885c1..d2a506f20 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge23.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge23.java @@ -1,28 +1,17 @@ package org.owasp.wrongsecrets.challenges.docker; import java.nio.charset.StandardCharsets; -import java.util.List; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.encoders.Base64; import org.bouncycastle.util.encoders.Hex; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about finding a secret hardcoded in comments in a front-end. */ @Slf4j @Component -@Order(23) -public class Challenge23 extends Challenge { - - public Challenge23(ScoreCard scoreCard) { - super(scoreCard); - } +public class Challenge23 implements Challenge { /** {@inheritDoc} */ @Override @@ -37,34 +26,6 @@ public boolean answerCorrect(String answer) { return getActualData().equals(answer); } - @Override - public boolean canRunInCTFMode() { - return true; - } - - @Override - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EASY; - } - - /** {@inheritDoc} Frontend based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.FRONTEND.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - public String getActualData() { return new String( Base64.decode( diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge24.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge24.java index 9b542c56b..d43a383c9 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge24.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge24.java @@ -1,32 +1,16 @@ package org.owasp.wrongsecrets.challenges.docker; import java.nio.charset.StandardCharsets; -import java.util.List; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.encoders.Hex; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about using a publicly specified key to safeguard data. */ @Slf4j @Component -@Order(24) -public class Challenge24 extends Challenge { - - public Challenge24(ScoreCard scoreCard) { - super(scoreCard); - } - - @Override - public boolean canRunInCTFMode() { - return true; - } +public class Challenge24 implements Challenge { /** {@inheritDoc} */ @Override @@ -40,29 +24,6 @@ public boolean answerCorrect(String answer) { return getActualData().equals(answer); } - @Override - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.NORMAL; - } - - /** {@inheritDoc} Cryptography based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.CRYPTOGRAPHY.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - public String getActualData() { return new String( Hex.decode( diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge25.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge25.java index f2d7378c9..289699bb0 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge25.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge25.java @@ -2,40 +2,27 @@ import java.nio.charset.StandardCharsets; import java.security.spec.AlgorithmParameterSpec; -import java.util.List; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.encoders.Base64; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about finding a secret hardcoded in a web3 contract. */ @Slf4j @Component -@Order(25) -public class Challenge25 extends Challenge { +public class Challenge25 implements Challenge { private final String cipherText; - public Challenge25(ScoreCard scoreCard, @Value("${challenge25ciphertext}") String cipherText) { - super(scoreCard); + public Challenge25(@Value("${challenge25ciphertext}") String cipherText) { this.cipherText = cipherText; } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -49,29 +36,6 @@ public boolean answerCorrect(String answer) { return answer.equals(correctString); } - @Override - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.NORMAL; - } - - /** {@inheritDoc} Web3 based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.WEB3.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - private String quickDecrypt(String cipherText) { try { final Cipher decryptor = Cipher.getInstance("AES/GCM/NoPadding"); diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge26.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge26.java index 360ede4fa..1bbfa60e4 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge26.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge26.java @@ -2,40 +2,27 @@ import java.nio.charset.StandardCharsets; import java.security.spec.AlgorithmParameterSpec; -import java.util.List; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.encoders.Base64; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about finding a secret hardcoded in a web3 contract. */ @Slf4j @Component -@Order(26) -public class Challenge26 extends Challenge { +public class Challenge26 implements Challenge { private final String cipherText; - public Challenge26(ScoreCard scoreCard, @Value("${challenge26ciphertext}") String cipherText) { - super(scoreCard); + public Challenge26(@Value("${challenge26ciphertext}") String cipherText) { this.cipherText = cipherText; } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -49,29 +36,6 @@ public boolean answerCorrect(String answer) { return answer.equals(correctString); } - /** {@inheritDoc} */ - @Override - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.NORMAL; - } - - /** {@inheritDoc} Web3 based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.WEB3.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - private String quickDecrypt(String cipherText) { try { final Cipher decryptor = Cipher.getInstance("AES/GCM/NoPadding"); diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge27.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge27.java index 515e5346d..998164525 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge27.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge27.java @@ -2,40 +2,27 @@ import java.nio.charset.StandardCharsets; import java.security.spec.AlgorithmParameterSpec; -import java.util.List; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.encoders.Base64; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about finding a secret hardcoded in a web3 contract based on hashing. */ @Slf4j @Component -@Order(26) -public class Challenge27 extends Challenge { +public class Challenge27 implements Challenge { private final String cipherText; - public Challenge27(ScoreCard scoreCard, @Value("${challenge27ciphertext}") String cipherText) { - super(scoreCard); + public Challenge27(@Value("${challenge27ciphertext}") String cipherText) { this.cipherText = cipherText; } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -49,29 +36,6 @@ public boolean answerCorrect(String answer) { return answer.equals(correctString); } - @Override - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - - /** {@inheritDoc} This is a normal level challenge */ - @Override - public int difficulty() { - return Difficulty.NORMAL; - } - - /** {@inheritDoc} Web3 based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.WEB3.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - private String quickDecrypt(String cipherText) { try { final Cipher decryptor = Cipher.getInstance("AES/GCM/NoPadding"); diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge28.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge28.java index 7ad7f97cf..f78cf1a69 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge28.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge28.java @@ -1,32 +1,15 @@ package org.owasp.wrongsecrets.challenges.docker; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.DOCKER; -import java.util.List; import org.bouncycastle.util.encoders.Base64; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about finding a secret in a Github issue. */ @Component -@Order(28) -public class Challenge28 extends Challenge { - - public Challenge28(ScoreCard scoreCard) { - super(scoreCard); - } - - @Override - public boolean canRunInCTFMode() { - return true; - } +public class Challenge28 implements Challenge { /** {@inheritDoc} */ @Override @@ -40,28 +23,6 @@ public boolean answerCorrect(String answer) { return getSecretKey().equals(answer); } - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(DOCKER); - } - - /** {@inheritDoc} This is an easy challenge */ - @Override - public int difficulty() { - return Difficulty.EASY; - } - - /** {@inheritDoc} Documentation based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.DOCUMENTATION.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - private String getSecretKey() { return new String( Base64.decode( diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge29.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge29.java index b31a06e2e..a97e548f5 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge29.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge29.java @@ -1,7 +1,5 @@ package org.owasp.wrongsecrets.challenges.docker; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.DOCKER; - import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -10,32 +8,16 @@ import java.security.KeyFactory; import java.security.PrivateKey; import java.security.spec.PKCS8EncodedKeySpec; -import java.util.List; import javax.crypto.Cipher; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about finding a secret in a Github issue (screenshot). */ -@Component @Slf4j -@Order(29) -public class Challenge29 extends Challenge { - - public Challenge29(ScoreCard scoreCard) { - super(scoreCard); - } - - @Override - public boolean canRunInCTFMode() { - return true; - } +@Component +public class Challenge29 implements Challenge { @Override public Spoiler spoiler() { @@ -47,27 +29,6 @@ public boolean answerCorrect(String answer) { return decryptActualAnswer().equals(answer); } - public List supportedRuntimeEnvironments() { - return List.of(DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EASY; - } - - /** {@inheritDoc} Documentation based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.DOCUMENTATION.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - private byte[] decode(byte[] encoded, PrivateKey privateKey) throws Exception { Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); cipher.init(Cipher.DECRYPT_MODE, privateKey); diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge3.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge3.java index b5891fc92..8c6f3ed9e 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge3.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge3.java @@ -1,39 +1,22 @@ package org.owasp.wrongsecrets.challenges.docker; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.DOCKER; - -import java.util.List; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** * This challenge can be run in CTF mode and is limited to using Docker as a runtime environment. */ @Component -@Order(3) -public class Challenge3 extends Challenge { +public class Challenge3 implements Challenge { private final String hardcodedEnvPassword; - public Challenge3( - ScoreCard scoreCard, @Value("${DOCKER_ENV_PASSWORD}") String hardcodedEnvPassword) { - super(scoreCard); + public Challenge3(@Value("${DOCKER_ENV_PASSWORD}") String hardcodedEnvPassword) { this.hardcodedEnvPassword = hardcodedEnvPassword; } - /** {@inheritDoc} */ - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -45,26 +28,4 @@ public Spoiler spoiler() { public boolean answerCorrect(String answer) { return hardcodedEnvPassword.equals(answer); } - - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EASY; - } - - /** {@inheritDoc} Docker based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.DOCKER.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge31.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge31.java index 31f333b85..dbb730d07 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge31.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge31.java @@ -1,27 +1,15 @@ package org.owasp.wrongsecrets.challenges.docker; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.DOCKER; - import java.nio.charset.StandardCharsets; import java.util.Base64; -import java.util.List; import java.util.UUID; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; import org.owasp.wrongsecrets.challenges.Spoiler; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about finding a secret in website */ @Component -@Order(31) -public class Challenge31 extends Challenge { - - public Challenge31(ScoreCard scoreCard) { - super(scoreCard); - } +public class Challenge31 implements Challenge { private String getanswer() { String str = "vozvtbeY6++kjJz3tPn84LeM77I="; @@ -49,11 +37,6 @@ private String getanswer() { return new String(xoredBytes, StandardCharsets.UTF_8); } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -65,26 +48,4 @@ public Spoiler spoiler() { public boolean answerCorrect(String answer) { return getanswer().equals(answer); } - - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return 1; - } - - /** {@inheritDoc} Documentation based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.DOCUMENTATION.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge32.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge32.java index d1df02a1a..c9b9b3611 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge32.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge32.java @@ -2,20 +2,14 @@ import java.nio.charset.StandardCharsets; import java.security.spec.AlgorithmParameterSpec; -import java.util.List; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.encoders.Base64; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** @@ -24,17 +18,7 @@ */ @Slf4j @Component -@Order(32) -public class Challenge32 extends Challenge { - - public Challenge32(ScoreCard scoreCard) { - super(scoreCard); - } - - @Override - public boolean canRunInCTFMode() { - return true; - } +public class Challenge32 implements Challenge { @Override public Spoiler spoiler() { @@ -46,28 +30,6 @@ public boolean answerCorrect(String answer) { return getSolution().equals(answer); } - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.NORMAL; - } - - /** {@inheritDoc} This is an AI type of challenge */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.AI.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - - @Override - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - private String getSolution() { return decrypt( decrypt( diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge34.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge34.java index bd4f30b17..d7f8eef3e 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge34.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge34.java @@ -1,15 +1,9 @@ package org.owasp.wrongsecrets.challenges.docker; import com.google.common.base.Strings; -import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; -import org.springframework.core.annotation.Order; import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; import org.springframework.stereotype.Component; @@ -19,20 +13,10 @@ */ @Slf4j @Component -@Order(34) -public class Challenge34 extends Challenge { +public class Challenge34 implements Challenge { private String key; - public Challenge34(ScoreCard scoreCard) { - super(scoreCard); - } - - @Override - public boolean canRunInCTFMode() { - return true; - } - @Override public Spoiler spoiler() { return new Spoiler(getKey()); @@ -43,28 +27,6 @@ public boolean answerCorrect(String answer) { return getKey().equals(answer); } - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.NORMAL; - } - - /** {@inheritDoc} This is a crypto (hashing) type of challenge */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.CRYPTOGRAPHY.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - - @Override - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - private String getKey() { if (Strings.isNullOrEmpty(key)) { key = generateKey(); diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge35.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge35.java index df99341be..2a9e92acb 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge35.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge35.java @@ -4,7 +4,6 @@ import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.List; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; @@ -13,29 +12,14 @@ import javax.crypto.spec.SecretKeySpec; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.encoders.Base64; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This is a challenge based on the idea of leaking a secret trough a vulnerability report. */ @Slf4j @Component -@Order(35) -public class Challenge35 extends Challenge { - - public Challenge35(ScoreCard scoreCard) { - super(scoreCard); - } - - @Override - public boolean canRunInCTFMode() { - return true; - } +public class Challenge35 implements Challenge { @Override public Spoiler spoiler() { @@ -47,28 +31,6 @@ public boolean answerCorrect(String answer) { return getKey().equals(answer); } - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EASY; - } - - /** {@inheritDoc} This is a Documentation type of challenge */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.DOCUMENTATION.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - - @Override - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - private String getKey() { String ciphertext = "zRR77ETjg5GsXv3az1TZU73xiFWYHbVceJBvBbjChxLyMjHkF6kFdwIXIduVBHAT"; try { diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge36.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge36.java index 0aaf264d7..865d69315 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge36.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge36.java @@ -1,16 +1,10 @@ package org.owasp.wrongsecrets.challenges.docker; -import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.owasp.wrongsecrets.challenges.docker.binaryexecution.BinaryExecutionHelper; import org.owasp.wrongsecrets.challenges.docker.binaryexecution.MuslDetectorImpl; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** @@ -18,22 +12,15 @@ */ @Slf4j @Component -@Order(36) // make sure this number is the same as your challenge -public class Challenge36 extends Challenge { +public class Challenge36 implements Challenge { private final BinaryExecutionHelper binaryExecutionHelper; private String executable; - public Challenge36(ScoreCard scoreCard) { - super(scoreCard); + public Challenge36() { this.executable = "wrongsecrets-advanced-c"; this.binaryExecutionHelper = new BinaryExecutionHelper(36, new MuslDetectorImpl()); } - @Override - public boolean canRunInCTFMode() { - return true; - } - @Override public Spoiler spoiler() { return new Spoiler(binaryExecutionHelper.executeCommand("spoil", executable)); @@ -45,27 +32,4 @@ public boolean answerCorrect(String answer) { .executeCommand(answer, executable) .equals("This is correct! Congrats!"); } - - /** {@inheritDoc} This is a Docker based challenge */ - @Override - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - - /** {@inheritDoc} This is a 5 star challenge */ - @Override - public int difficulty() { - return Difficulty.MASTER; - } - - /** {@inheritDoc} This is a binary based challenge. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.BINARY.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge37.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge37.java index 04363cc8f..f9ff2f775 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge37.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge37.java @@ -2,19 +2,13 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.util.List; import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.encoders.Base64; import org.owasp.wrongsecrets.BasicAuthentication; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.context.annotation.Bean; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** @@ -23,14 +17,12 @@ */ @Slf4j @Component -@Order(37) -public class Challenge37 extends Challenge { +public class Challenge37 implements Challenge { private String secret; private static final String password = "YjNCbGJpQnpaWE5oYldVPQo="; - public Challenge37(ScoreCard scoreCard) { - super(scoreCard); + public Challenge37() { secret = UUID.randomUUID().toString(); } @@ -45,11 +37,6 @@ public BasicAuthentication challenge37BasicAuth() { "authenticated/**"); } - @Override - public boolean canRunInCTFMode() { - return true; - } - @Override public Spoiler spoiler() { return new Spoiler(secret); @@ -60,27 +47,6 @@ public boolean answerCorrect(String answer) { return secret.equals(answer); } - @Override - public int difficulty() { - return Difficulty.NORMAL; - } - - /** {@inheritDoc} This is a CICD type of challenge */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.CICD.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - - @Override - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - public String getPassword() { return new String(Base64.decode(Base64.decode(Base64.decode(password))), StandardCharsets.UTF_8) diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge38.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge38.java index 6125eb97d..0fa727b79 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge38.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge38.java @@ -1,28 +1,12 @@ package org.owasp.wrongsecrets.challenges.docker; -import java.util.List; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This is a challenge based on leaking secrets with the misuse of Git notes */ @Component -@Order(38) -public class Challenge38 extends Challenge { - - public Challenge38(ScoreCard scoreCard) { - super(scoreCard); - } - - @Override - public boolean canRunInCTFMode() { - return true; - } +public class Challenge38 implements Challenge { @Override public Spoiler spoiler() { @@ -34,28 +18,6 @@ public boolean answerCorrect(String answer) { return getSolution().equals(answer); } - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EASY; - } - - /** {@inheritDoc} Git based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.GIT.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - - @Override - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - private String getSolution() { return unobfuscate("UOZFGZTLOLLXHTKEGGS"); } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge39.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge39.java index b7a50108c..94083a4d7 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge39.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge39.java @@ -4,16 +4,11 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Base64; -import java.util.List; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.annotation.Order; @@ -24,21 +19,14 @@ @Slf4j @Component @Order(39) -public class Challenge39 extends Challenge { +public class Challenge39 implements Challenge { private final Resource resource; - public Challenge39( - ScoreCard scoreCard, @Value("classpath:executables/secrchallenge.md") Resource resource) { - super(scoreCard); + public Challenge39(@Value("classpath:executables/secrchallenge.md") Resource resource) { this.resource = resource; } - @Override - public boolean canRunInCTFMode() { - return true; - } - @Override public Spoiler spoiler() { return new Spoiler(getSolution()); @@ -49,28 +37,6 @@ public boolean answerCorrect(String answer) { return getSolution().equals(answer); } - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EASY; - } - - /** {@inheritDoc} Cryptography based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.CRYPTOGRAPHY.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - - @Override - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - @SuppressFBWarnings( value = {"CIPHER_INTEGRITY", "ECB_MODE"}, justification = "This is to allow for easy ECB online decryptors") diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge4.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge4.java index 37f1f8cb9..a751a3607 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge4.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge4.java @@ -1,36 +1,20 @@ package org.owasp.wrongsecrets.challenges.docker; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.DOCKER; - -import java.util.List; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about having a secrets stored as a Docker ARG var. */ @Component -@Order(4) -public class Challenge4 extends Challenge { +public class Challenge4 implements Challenge { private final String argBasedPassword; - public Challenge4(ScoreCard scoreCard, @Value("${ARG_BASED_PASSWORD}") String argBasedPassword) { - super(scoreCard); + public Challenge4(@Value("${ARG_BASED_PASSWORD}") String argBasedPassword) { this.argBasedPassword = argBasedPassword; } - @Override - public boolean canRunInCTFMode() { - return true; - } - - /** {@inheritDoc} */ @Override public Spoiler spoiler() { return new Spoiler(argBasedPassword); @@ -41,26 +25,4 @@ public Spoiler spoiler() { public boolean answerCorrect(String answer) { return argBasedPassword.equals(answer) || argBasedPassword.equals("'" + answer + "'"); } - - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.NORMAL; - } - - /** {@inheritDoc} Docker based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.DOCKER.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge40.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge40.java index 7e809d8c9..40eff8408 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge40.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge40.java @@ -6,19 +6,13 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Base64; -import java.util.List; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; @@ -28,22 +22,14 @@ */ @Slf4j @Component -@Order(40) -public class Challenge40 extends Challenge { +public class Challenge40 implements Challenge { private final Resource resource; - public Challenge40( - ScoreCard scoreCard, @Value("classpath:executables/secrchallenge.json") Resource resource) { - super(scoreCard); + public Challenge40(@Value("classpath:executables/secrchallenge.json") Resource resource) { this.resource = resource; } - @Override - public boolean canRunInCTFMode() { - return true; - } - @Override public Spoiler spoiler() { return new Spoiler(getSolution()); @@ -54,28 +40,6 @@ public boolean answerCorrect(String answer) { return getSolution().equals(answer); } - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EASY; - } - - /** {@inheritDoc} Cryptography based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.CRYPTOGRAPHY.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - - @Override - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - @SuppressFBWarnings( value = {"CIPHER_INTEGRITY", "ECB_MODE"}, justification = "This is to allow for easy ECB online decryptors") diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge41.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge41.java index 7abdfc7a1..0604df573 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge41.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge41.java @@ -5,13 +5,8 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; -import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.annotation.Order; @@ -22,47 +17,19 @@ @Slf4j @Component @Order(41) -public class Challenge41 extends Challenge { +public class Challenge41 implements Challenge { private final String password; - public Challenge41(ScoreCard scoreCard, @Value("${challenge41password}") String password) { - super(scoreCard); + public Challenge41(@Value("${challenge41password}") String password) { this.password = password; } - @Override - public boolean canRunInCTFMode() { - return true; - } - @Override public Spoiler spoiler() { return new Spoiler(base64Decode(password)); } - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.HARD; - } - - /** {@inheritDoc} Cryptography based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.CRYPTOGRAPHY.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - - @Override - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } - @Override public boolean answerCorrect(String answer) { try { diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge8.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge8.java index e1fd5c3c3..797dcf6fd 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge8.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge8.java @@ -1,27 +1,18 @@ package org.owasp.wrongsecrets.challenges.docker; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.DOCKER; - import com.google.api.client.util.Strings; import java.security.SecureRandom; -import java.util.List; import java.util.Random; import lombok.extern.slf4j.Slf4j; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** Challenge which leaks the data in the logs instead of anywhere else. */ @Slf4j @Component -@Order(8) -public class Challenge8 extends Challenge { +public class Challenge8 implements Challenge { private static final String alphabet = "0123456789QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"; @@ -29,9 +20,7 @@ public class Challenge8 extends Challenge { private final Random secureRandom = new SecureRandom(); private final String randomValue; - public Challenge8( - ScoreCard scoreCard, @Value("${challenge_acht_ctf_host_value}") String serverCode) { - super(scoreCard); + public Challenge8(@Value("${challenge_acht_ctf_host_value}") String serverCode) { if (!Strings.isNullOrEmpty(serverCode) && !serverCode.equals("not_set")) { randomValue = serverCode; } else { @@ -40,11 +29,6 @@ public Challenge8( log.info("Initializing challenge 8 with random value {}", randomValue); } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -57,28 +41,6 @@ public boolean answerCorrect(String answer) { return randomValue.equals(answer); } - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(DOCKER); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.NORMAL; - } - - /** {@inheritDoc} Challenge is wrapped around logging */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.LOGGING.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return true; - } - private String generateRandomString() { final int length = 10; StringBuilder builder = new StringBuilder(length); diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/authchallenge/AuthenticatedRestControllerChallenge37.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/authchallenge/AuthenticatedRestControllerChallenge37.java index 204920b9d..0ef054fa3 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/authchallenge/AuthenticatedRestControllerChallenge37.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/authchallenge/AuthenticatedRestControllerChallenge37.java @@ -1,21 +1,19 @@ package org.owasp.wrongsecrets.challenges.docker.authchallenge; import io.swagger.v3.oas.annotations.Operation; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.owasp.wrongsecrets.challenges.docker.Challenge37; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @Slf4j +@AllArgsConstructor @RestController public class AuthenticatedRestControllerChallenge37 { private final Challenge37 challenge37; - public AuthenticatedRestControllerChallenge37(Challenge37 challenge37) { - this.challenge37 = challenge37; - } - @Operation(summary = "Endpoint for interaction at challenge 37") @GetMapping("/authenticated/challenge37") public String getAuthSecret() { diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge30.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/challenge30/Challenge30.java similarity index 54% rename from src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge30.java rename to src/main/java/org/owasp/wrongsecrets/challenges/docker/challenge30/Challenge30.java index 0322a011e..da63c0f86 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge30.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/challenge30/Challenge30.java @@ -1,24 +1,17 @@ -package org.owasp.wrongsecrets.challenges.docker; +package org.owasp.wrongsecrets.challenges.docker.challenge30; import com.google.common.base.Strings; import java.security.SecureRandom; -import java.util.List; import java.util.Random; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** * This is a localstorage based challenge to educate people on the use of localstorage and secrets. */ @Component -@Order(30) -public class Challenge30 extends Challenge { +public class Challenge30 implements Challenge { private final Random secureRandom = new SecureRandom(); private static final String alphabet = "0123456789QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"; @@ -32,15 +25,6 @@ private String generateRandomString(int length) { return new String(builder); } - public Challenge30(ScoreCard scoreCard) { - super(scoreCard); - } - - @Override - public boolean canRunInCTFMode() { - return true; - } - @Override public Spoiler spoiler() { if (Strings.isNullOrEmpty(solution)) { @@ -56,26 +40,4 @@ public boolean answerCorrect(String answer) { } return solution.equals(answer); } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.NORMAL; - } - - /** {@inheritDoc} This is a front-end / web type of challenge */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.FRONTEND.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - - @Override - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.DOCKER); - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/ChallengeRestController.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/challenge30/ChallengeRestController.java similarity index 85% rename from src/main/java/org/owasp/wrongsecrets/challenges/ChallengeRestController.java rename to src/main/java/org/owasp/wrongsecrets/challenges/docker/challenge30/ChallengeRestController.java index 615462fbb..a8935f0f8 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/ChallengeRestController.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/challenge30/ChallengeRestController.java @@ -1,7 +1,6 @@ -package org.owasp.wrongsecrets.challenges; +package org.owasp.wrongsecrets.challenges.docker.challenge30; import io.swagger.v3.oas.annotations.Hidden; -import org.owasp.wrongsecrets.challenges.docker.Challenge30; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge33.java b/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge33.java index 6996fff6b..1a2817ed1 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge33.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge33.java @@ -1,25 +1,17 @@ package org.owasp.wrongsecrets.challenges.kubernetes; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.K8S; - import com.google.common.base.Strings; import java.nio.charset.StandardCharsets; import java.security.spec.AlgorithmParameterSpec; -import java.util.List; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.encoders.Base64; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** @@ -27,13 +19,11 @@ */ @Slf4j @Component -@Order(33) -public class Challenge33 extends Challenge { +public class Challenge33 implements Challenge { private final String secretSecret; - public Challenge33(ScoreCard scoreCard, @Value("${CHALLENGE33}") String secretSecret) { - super(scoreCard); + public Challenge33(@Value("${CHALLENGE33}") String secretSecret) { this.secretSecret = secretSecret; } @@ -43,35 +33,10 @@ public Spoiler spoiler() { } @Override - protected boolean answerCorrect(String answer) { + public boolean answerCorrect(String answer) { return getSolution().equals(answer); } - @Override - public List supportedRuntimeEnvironments() { - return List.of(K8S); - } - - @Override - public int difficulty() { - return Difficulty.NORMAL; - } - - @Override - public String getTech() { - return ChallengeTechnology.Tech.SECRETS.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } - - @Override - public boolean canRunInCTFMode() { - return true; - } - private String getSolution() { if ("if_you_see_this_please_use_k8s".equals(secretSecret) || Strings.isNullOrEmpty(secretSecret)) { diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge5.java b/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge5.java index 7fde520f4..57f385fd4 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge5.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge5.java @@ -1,36 +1,20 @@ package org.owasp.wrongsecrets.challenges.kubernetes; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.K8S; - -import java.util.List; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about having a secrets stored as a K8s Configmap. */ @Component -@Order(5) -public class Challenge5 extends Challenge { +public class Challenge5 implements Challenge { private final String configmapK8sSecret; - public Challenge5( - ScoreCard scoreCard, @Value("${SPECIAL_K8S_SECRET}") String configmapK8sSecret) { - super(scoreCard); + public Challenge5(@Value("${SPECIAL_K8S_SECRET}") String configmapK8sSecret) { this.configmapK8sSecret = configmapK8sSecret; } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -42,26 +26,4 @@ public Spoiler spoiler() { public boolean answerCorrect(String answer) { return configmapK8sSecret.equals(answer); } - - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(K8S); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.NORMAL; - } - - /** {@inheritDoc} Configmaps based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.CONFIGMAPS.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return true; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge6.java b/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge6.java index 756cd9a5f..ed316d3d1 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge6.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge6.java @@ -1,36 +1,20 @@ package org.owasp.wrongsecrets.challenges.kubernetes; -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.K8S; - -import java.util.List; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about having a secrets stored as a K8s Secret. */ @Component -@Order(6) -public class Challenge6 extends Challenge { +public class Challenge6 implements Challenge { private final String secretK8sSecret; - public Challenge6( - ScoreCard scoreCard, @Value("${SPECIAL_SPECIAL_K8S_SECRET}") String secretK8sSecret) { - super(scoreCard); + public Challenge6(@Value("${SPECIAL_SPECIAL_K8S_SECRET}") String secretK8sSecret) { this.secretK8sSecret = secretK8sSecret; } - @Override - public boolean canRunInCTFMode() { - return true; - } - /** {@inheritDoc} */ @Override public Spoiler spoiler() { @@ -42,26 +26,4 @@ public Spoiler spoiler() { public boolean answerCorrect(String answer) { return secretK8sSecret.equals(answer); } - - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(K8S); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.NORMAL; - } - - /** {@inheritDoc} K8s secrets based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.SECRETS.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return true; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge7.java b/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge7.java index b4085fd03..31dede81a 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge7.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/kubernetes/Challenge7.java @@ -1,39 +1,24 @@ package org.owasp.wrongsecrets.challenges.kubernetes; import com.google.common.base.Strings; -import java.util.List; -import org.owasp.wrongsecrets.RuntimeEnvironment; -import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; -import org.owasp.wrongsecrets.challenges.ChallengeTechnology; -import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** This challenge is about having a secrets stored in a misconfigured Hashicorp Vault. */ @Component -@Order(7) -public class Challenge7 extends Challenge { +public class Challenge7 implements Challenge { private final Vaultpassword vaultPassword; private final String vaultPasswordString; public Challenge7( - ScoreCard scoreCard, - Vaultpassword vaultPassword, - @Value("${vaultpassword}") String vaultPasswordString) { - super(scoreCard); + Vaultpassword vaultPassword, @Value("${vaultpassword}") String vaultPasswordString) { this.vaultPassword = vaultPassword; this.vaultPasswordString = vaultPasswordString; } - @Override - public boolean canRunInCTFMode() { - return true; - } - private String getAnswer() { return vaultPassword != null && !Strings.isNullOrEmpty(vaultPassword.getPasssword()) ? vaultPassword.getPasssword() @@ -51,26 +36,4 @@ public Spoiler spoiler() { public boolean answerCorrect(String answer) { return getAnswer().equals(answer); } - - /** {@inheritDoc} */ - public List supportedRuntimeEnvironments() { - return List.of(RuntimeEnvironment.Environment.VAULT); - } - - /** {@inheritDoc} */ - @Override - public int difficulty() { - return Difficulty.EXPERT; - } - - /** {@inheritDoc} Vault based. */ - @Override - public String getTech() { - return ChallengeTechnology.Tech.VAULT.id; - } - - @Override - public boolean isLimitedWhenOnlineHosted() { - return false; - } } diff --git a/src/main/java/org/owasp/wrongsecrets/definitions/Category.java b/src/main/java/org/owasp/wrongsecrets/definitions/Category.java new file mode 100644 index 000000000..147a03181 --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/definitions/Category.java @@ -0,0 +1,8 @@ +package org.owasp.wrongsecrets.definitions; + +/** + * Defines the category of a challenge. + * + * @param category the category of the challenge as defined in the yaml configuration + */ +public record Category(String category) {} diff --git a/src/main/java/org/owasp/wrongsecrets/definitions/ChallengeConfig.java b/src/main/java/org/owasp/wrongsecrets/definitions/ChallengeConfig.java new file mode 100644 index 000000000..70fc5cba2 --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/definitions/ChallengeConfig.java @@ -0,0 +1,77 @@ +package org.owasp.wrongsecrets.definitions; + +import java.io.IOException; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FilenameUtils; +import org.owasp.wrongsecrets.Challenges; +import org.owasp.wrongsecrets.asciidoc.TemplateGenerator; +import org.owasp.wrongsecrets.challenges.Challenge; +import org.owasp.wrongsecrets.definitions.Sources.TextWithFileLocation; +import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; + +@Configuration +@Slf4j +@EnableConfigurationProperties(ChallengeDefinitionsConfiguration.class) +public class ChallengeConfig { + + /** + * Define a converter for {@link TextWithFileLocation} which reads the source + * location from the configuration and reads the file contents. This way we only have to read the + * files once. + * + * @param templateGenerator the template generator + * @return {@link StringToChallengeNameConverter + */ + @ConfigurationPropertiesBinding + @Bean + public TextWithFileLocationConverter textConverter(TemplateGenerator templateGenerator) { + return new TextWithFileLocationConverter(templateGenerator); + } + + @ConfigurationPropertiesBinding + @Bean + public StringToChallengeNameConverter nameConverter() { + return new StringToChallengeNameConverter(); + } + + private record TextWithFileLocationConverter(TemplateGenerator templateGenerator) + implements Converter { + + @Override + public TextWithFileLocation convert(String source) { + return new TextWithFileLocation(source, read(source)); + } + + private String read(String name) { + try { + return templateGenerator.generate(FilenameUtils.removeExtension(name)); + } catch (IOException e) { + return ""; + } + } + } + + private record StringToChallengeNameConverter() implements Converter { + + @Override + public ChallengeName convert(String name) { + return new ChallengeName(name, name.strip().replace(" ", "-").toLowerCase()); + } + } + + @Bean + public Challenges challenges( + ChallengeDefinitionsConfiguration challengeDefinitions, List challenges) { + log.info( + "Loaded {} definitions and {} challenges", + challengeDefinitions.challenges().size(), + challenges.size()); + + return new Challenges(challengeDefinitions, challenges); + } +} diff --git a/src/main/java/org/owasp/wrongsecrets/definitions/ChallengeDefinition.java b/src/main/java/org/owasp/wrongsecrets/definitions/ChallengeDefinition.java new file mode 100644 index 000000000..07579bccd --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/definitions/ChallengeDefinition.java @@ -0,0 +1,69 @@ +package org.owasp.wrongsecrets.definitions; + +import static java.util.stream.Collectors.toMap; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import org.owasp.wrongsecrets.RuntimeEnvironment; +import org.owasp.wrongsecrets.challenges.Challenge; +import org.owasp.wrongsecrets.definitions.Sources.Source; + +/** + * We can define a challenge as follows: + * + *

+ *       challenges:
+ *     - name: Challenge 0
+ *       sources:
+ *         - class-name: "org.owasp.wrongsecrets.challenges.docker.Challenge0"
+ *           hint: "explanation/challenge0_hint.adoc"
+ *           explanation: "explanation/challenge0.adoc"
+ *           reason: "explanation/challenge0_reason.adoc"
+ *           environments: *all_envs
+ *       difficulty: *easy
+ *       category: *intro
+ *       ctf:
+ *         enabled: true
+ * 
+ * + * During runtime a {@link ChallengeDefinition} is linked to a {@link Challenge} instance. Be aware + * a challenge can have multiple sources. This is useful for having different implementations for + * different environments. For example, we can have a challenge which is implemented for AWS and + * Azure. This way you can define multiple challenge classes and the correct one is chosen during + * runtime. + */ +public record ChallengeDefinition( + ChallengeName name, + List sources, + Difficulty difficulty, + Category category, + Ctf ctf, + Sources environmentToSource) { + + public ChallengeDefinition { + environmentToSource = + new Sources( + sources.stream().collect(toMap(s -> s.environments(), s -> s)).entrySet().stream() + .flatMap( + entry -> entry.getKey().stream().map(env -> Map.entry(env, entry.getValue()))) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue))); + } + + public Sources challengeSources() { + return environmentToSource; + } + + public int difficulty(List difficulties) { + return difficulties.indexOf(difficulty) + 1; + } + + public Optional source(RuntimeEnvironment runtimeEnvironment) { + return environmentToSource.source(runtimeEnvironment); + } + + public Set supportedEnvironments() { + return environmentToSource().sources().keySet(); + } +} diff --git a/src/main/java/org/owasp/wrongsecrets/definitions/ChallengeDefinitionsConfiguration.java b/src/main/java/org/owasp/wrongsecrets/definitions/ChallengeDefinitionsConfiguration.java new file mode 100644 index 000000000..4d19fe62d --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/definitions/ChallengeDefinitionsConfiguration.java @@ -0,0 +1,18 @@ +package org.owasp.wrongsecrets.definitions; + +import java.util.List; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Represents the configuration part of the application. It consists of the global configuration and + * the challenges, which we call {@link ChallengeDefinition}. + * + *

The complete configuration is read from a yaml file and it uses {@link + * ConfigurationProperties}. This way we get full support for YAML anchors and references. + */ +@ConfigurationProperties(prefix = "configurations") +public record ChallengeDefinitionsConfiguration( + List difficulties, + List technologies, + List environments, + List challenges) {} diff --git a/src/main/java/org/owasp/wrongsecrets/definitions/ChallengeName.java b/src/main/java/org/owasp/wrongsecrets/definitions/ChallengeName.java new file mode 100644 index 000000000..fb7ebb0a6 --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/definitions/ChallengeName.java @@ -0,0 +1,21 @@ +package org.owasp.wrongsecrets.definitions; + +import java.util.regex.Pattern; + +/** + * Name of a challenge, @see {@link ChallengeConfig#nameConverter()} for more information. + * + * @param name the name of the challenge from the configuration, the full class name + * @param url the url under which the challenge is available. + */ +public record ChallengeName(String name, String url) { + private static final Pattern digit = Pattern.compile(".*\\d.*"); + + public boolean partialMatches(String nameToMatch) { + var digitMatcher = digit.matcher(nameToMatch); + if (digitMatcher.matches()) { + return name.contains(digitMatcher.group()) || url.contains(digitMatcher.group()); + } + return false; + } +} diff --git a/src/main/java/org/owasp/wrongsecrets/definitions/Ctf.java b/src/main/java/org/owasp/wrongsecrets/definitions/Ctf.java new file mode 100644 index 000000000..664c1e679 --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/definitions/Ctf.java @@ -0,0 +1,9 @@ +package org.owasp.wrongsecrets.definitions; + +/** + * Defines if a challenge is a CTF challenge or not. Later on we might add more types of challenges + * which needs to have more configuration, that's why we have this as a record. + * + * @param enabled true if the challenge is a CTF challenge + */ +public record Ctf(boolean enabled) {} diff --git a/src/main/java/org/owasp/wrongsecrets/definitions/Difficulty.java b/src/main/java/org/owasp/wrongsecrets/definitions/Difficulty.java new file mode 100644 index 000000000..2e6d032cd --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/definitions/Difficulty.java @@ -0,0 +1,8 @@ +package org.owasp.wrongsecrets.definitions; + +/** + * Defines the difficulty of a challenge, as defined in the yaml configuration. + * + * @param difficulty + */ +public record Difficulty(String difficulty) {} diff --git a/src/main/java/org/owasp/wrongsecrets/definitions/Environment.java b/src/main/java/org/owasp/wrongsecrets/definitions/Environment.java new file mode 100644 index 000000000..1f8cee075 --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/definitions/Environment.java @@ -0,0 +1,17 @@ +package org.owasp.wrongsecrets.definitions; + +/** + * Defines an environment for a challenge. + * + * @param name the name of the environment used in the code + * @param ctf the name of the environment when used in the CTF context + * @param overview the name of the environment when used in the overview + * @param displayName the name of the environment when used in the UI + * @param missingEnvironment the location of the missing environment message + */ +public record Environment( + String name, + String ctf, + String overview, + String displayName, + Sources.TextWithFileLocation missingEnvironment) {} diff --git a/src/main/java/org/owasp/wrongsecrets/definitions/Navigation.java b/src/main/java/org/owasp/wrongsecrets/definitions/Navigation.java new file mode 100644 index 000000000..d2b31f36e --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/definitions/Navigation.java @@ -0,0 +1,35 @@ +package org.owasp.wrongsecrets.definitions; + +import java.util.List; +import java.util.Optional; + +/** + * Encapsulate the navigation + * + *

This makes it possible for having a + * Map without changing + * the calling side + */ +public record Navigation(List challenges, ChallengeDefinition current) { + + public Optional next() { + return navigate(1); + } + + public Optional previous() { + return navigate(-1); + } + + private Optional navigate(int direction) { + int index = challenges.indexOf(current); + if (index == -1) { + return Optional.empty(); + } + if (index == challenges.size() - 1 || direction == -1 + ? index == 0 + : index == challenges.size() - 1) { + return Optional.empty(); + } + return Optional.of(challenges.get((index + direction) % challenges.size())); + } +} diff --git a/src/main/java/org/owasp/wrongsecrets/definitions/Sources.java b/src/main/java/org/owasp/wrongsecrets/definitions/Sources.java new file mode 100644 index 000000000..7950a816f --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/definitions/Sources.java @@ -0,0 +1,44 @@ +package org.owasp.wrongsecrets.definitions; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.owasp.wrongsecrets.ChallengeUiTemplateResolver; +import org.owasp.wrongsecrets.RuntimeEnvironment; + +/** + * We allow to define multiple sources for a challenge. This is useful for having different + * implementations for different environments. For example, we can have a challenge which is + * implemented for AWS and Azure. This way you can define multiple challenge classes and the correct + * one is chosen based on the runtime environment. The runtime environment is defined as environment + * variable or system property or in application.properties. + */ +public record Sources(Map sources) { + public Optional source(RuntimeEnvironment runtimeEnvironment) { + return Optional.ofNullable(sources.get(runtimeEnvironment.getRuntimeEnvironment())); + } + + /** + * Represent a single source for a challenge. + * + * @param className the name of the class which implements the challenge + * @param environments the environments for which this challenge should run on + * @param explanation the filename of the explanation + * @param reason the filename of the reason + * @param hint the filename of the hint + * @param hintLimited the filename of the hint when the user has limited access + * @param uiSnippet the filename of the UI snippet, this can be used to include JavaScript and is + * used by {@link ChallengeUiTemplateResolver} + */ + public record Source( + String className, + String url, + List environments, + TextWithFileLocation explanation, + TextWithFileLocation reason, + TextWithFileLocation hint, + TextWithFileLocation hintLimited, + String uiSnippet) {} + + public record TextWithFileLocation(String fileName, String contents) {} +} diff --git a/src/main/java/org/owasp/wrongsecrets/definitions/Technology.java b/src/main/java/org/owasp/wrongsecrets/definitions/Technology.java new file mode 100644 index 000000000..7c2c22532 --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/definitions/Technology.java @@ -0,0 +1,4 @@ +package org.owasp.wrongsecrets.definitions; + +/** Defines a technology for a challenge. */ +public record Technology(String technology) {} diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 67b228fef..000000000 --- a/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1 +0,0 @@ -org.springframework.context.ApplicationListener=org.owasp.wrongsecrets.StartupListener diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8191dac0c..90e2a7f58 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,6 +3,7 @@ #spring.devtools.restart.enabled=true spring.web.resources.cache.period=PT2H server.compression.enabled=true +spring.config.import=classpath:/wrong-secrets-configuration.yaml password=ThisEnvironmentIsAnotherPlaceToHide SPECIAL_K8S_SECRET=if_you_see_this_please_use_k8s diff --git a/src/main/resources/challenges/challenge-16/challenge-16.js b/src/main/resources/challenges/challenge-16/challenge-16.js new file mode 100644 index 000000000..8dff762eb --- /dev/null +++ b/src/main/resources/challenges/challenge-16/challenge-16.js @@ -0,0 +1,11 @@ + + + diff --git a/src/main/resources/challenges/challenge-30/challenge-30.js b/src/main/resources/challenges/challenge-30/challenge-30.js new file mode 100644 index 000000000..5b8d63faa --- /dev/null +++ b/src/main/resources/challenges/challenge-30/challenge-30.js @@ -0,0 +1,5 @@ + diff --git a/src/main/resources/challenges/missing_cloud.adoc b/src/main/resources/challenges/missing_cloud.adoc new file mode 100644 index 000000000..b1dd9ea29 --- /dev/null +++ b/src/main/resources/challenges/missing_cloud.adoc @@ -0,0 +1,2 @@ +We are running outside a properly configured Cloud environment. Please run this in an AWS/Azure/GCP environment as +explained in the https://github.com/OWASP/wrongsecrets#cloud-challenges[README.md] diff --git a/src/main/resources/challenges/missing_docker.adoc b/src/main/resources/challenges/missing_docker.adoc new file mode 100644 index 000000000..388d009a7 --- /dev/null +++ b/src/main/resources/challenges/missing_docker.adoc @@ -0,0 +1,4 @@ +We are running outside a docker container. +Please run this in a container as explained in the https://github.com/OWASP/wrongsecrets#basic-docker-exercises[README] + + diff --git a/src/main/resources/challenges/missing_k8s.adoc b/src/main/resources/challenges/missing_k8s.adoc new file mode 100644 index 000000000..4e7315753 --- /dev/null +++ b/src/main/resources/challenges/missing_k8s.adoc @@ -0,0 +1,3 @@ +We are running outside a K8s cluster. +Please run this in the K8s cluster as explained in the +https://github.com/OWASP/wrongsecrets#basic-k8s-exercise[README.md] diff --git a/src/main/resources/challenges/missing_vault.adoc b/src/main/resources/challenges/missing_vault.adoc new file mode 100644 index 000000000..006e40ad1 --- /dev/null +++ b/src/main/resources/challenges/missing_vault.adoc @@ -0,0 +1,2 @@ +We are running outside a K8s cluster with Vault. Please run this in the K8s cluster as explained in the +https://github.com/OWASP/wrongsecrets#vault-exercises-with-minikube[README.md] diff --git a/src/main/resources/templates/challenge.html b/src/main/resources/templates/challenge.html index 1c0012f99..af35e21d7 100644 --- a/src/main/resources/templates/challenge.html +++ b/src/main/resources/templates/challenge.html @@ -3,14 +3,16 @@

-

+

+

-

You need to guess the secret +

You need to guess the secret that is hidden in Java, Docker, Kubernetes, Vault, AWS or GCP.

-
+
@@ -21,12 +23,16 @@ th:text="${answerIncorrect}" th:attr="data-cy='incorrect-alert'">
- Answer to solution : + Answer to solution :
- +
@@ -36,28 +42,34 @@
-
-
+
-
-
+
@@ -72,22 +84,8 @@ th:text="${totalPoints}">
- - - - diff --git a/src/main/resources/templates/fragments/navigation.html b/src/main/resources/templates/fragments/navigation.html index f90f96001..239bb1605 100644 --- a/src/main/resources/templates/fragments/navigation.html +++ b/src/main/resources/templates/fragments/navigation.html @@ -3,13 +3,13 @@ diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 598875a20..edb745fc9 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -29,22 +29,9 @@ }); - - - - + + +
- + + -