diff --git a/.gitignore b/.gitignore index 3e44c76330..806fa30105 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,7 @@ scripts/sort_contibutors/contributors_file.* #optimizations .github/scripts/binary_version.txt + +# Cha-llen-ge 5-4 +# Se-cret en-cry-pted : h0vRDUS3VDN2gwfd1oftcMtD/YlKE0YfHLTvyk/lrY4= +# K-E-Y : 6b3b913c09d62238b9b8c0cc78904e7e1f9a99fd0a04ebae50a8a9881d452bbd diff --git a/.ssh/config b/.ssh/config new file mode 100644 index 0000000000..d26cafa1b4 --- /dev/null +++ b/.ssh/config @@ -0,0 +1,5 @@ +Host ourdemohost +HostName 76.223.11.49 +User wrongsecrets@owasp.com +Port 4444 +IdentityFile .ssh/wrongsecrets.keys diff --git a/.ssh/wrongsecrets.keys b/.ssh/wrongsecrets.keys new file mode 100644 index 0000000000..ca8c0d835e --- /dev/null +++ b/.ssh/wrongsecrets.keys @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACAA1sjJcm0xU4TxVyhUjoAxpUWjZlKneZcIoaKQSyFO7wAAAKAWqeghFqno +IQAAAAtzc2gtZWQyNTUxOQAAACAA1sjJcm0xU4TxVyhUjoAxpUWjZlKneZcIoaKQSyFO7w +AAAEBipwrG6wv7JN3oxHf0NmU96RXN+MzESqsPC7q+eiJ+CADWyMlybTFThPFXKFSOgDGl +RaNmUqd5lwihopBLIU7vAAAAFndyb25nc2VjcmV0c0Bvd2FzcC5jb20BAgMEBQYH +-----END OPENSSH PRIVATE KEY----- diff --git a/.ssh/wrongsecrets.keys.pub b/.ssh/wrongsecrets.keys.pub new file mode 100644 index 0000000000..27ea85c087 --- /dev/null +++ b/.ssh/wrongsecrets.keys.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIADWyMlybTFThPFXKFSOgDGlRaNmUqd5lwihopBLIU7v wrongsecrets@owasp.com diff --git a/Dockerfile b/Dockerfile index bcc123e4c3..5feaac89d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ ENV DOCKER_ENV_PASSWORD="This is it" ENV AZURE_KEY_VAULT_ENABLED=false ENV SPRINGDOC_UI=false ENV SPRINGDOC_DOC=false +ENV BASTIONHOSTPATH="/home/wrongsecrets/.ssh" RUN echo "2vars" RUN echo "$ARG_BASED_PASSWORD" @@ -39,6 +40,7 @@ COPY --chown=wrongsecrets src/main/resources/executables/wrongsecrets*linux-musl COPY --chown=wrongsecrets src/main/resources/executables/wrongsecrets-golang-linux /home/wrongsecrets/ COPY --chown=wrongsecrets src/test/resources/alibabacreds.kdbx /var/tmp/helpers COPY --chown=wrongsecrets src/test/resources/RSAprivatekey.pem /var/tmp/helpers/ +COPY --chown=wrongsecrets .ssh/ /home/wrongsecrets/.ssh/ COPY --from=builder /builder/extracted/dependencies/ ./ COPY --from=builder /builder/extracted/spring-boot-loader/ ./ diff --git a/Dockerfile.web b/Dockerfile.web index b308e0275b..a2b65f0b55 100644 --- a/Dockerfile.web +++ b/Dockerfile.web @@ -31,8 +31,9 @@ ENV challenge_rando_key_ctf_to_provide_to_host_value=$CHALLENGE_RANDO_KEY_CTF_TO ENV default_aws_value_challenge_9=$CHALLENGE_9_VALUE ENV default_aws_value_challenge_10=$CHALLENGE_10_VALUE ENV default_aws_value_challenge_11=$CHALLENGE_11_VALUE +ENV BASTIONHOSTPATH="/home/wrongsecrets/.ssh" COPY .github/scripts/ /var/helpers COPY src/test/resources/alibabacreds.kdbx /var/helpers COPY src/test/resources/RSAprivatekey.pem /var/helpers - +COPY .ssh/ /home/wrongsecrets/.ssh/ CMD java -Xms128m -Xmx128m -Xss512k -jar -Dserver.port=$PORT -XX:MaxRAMPercentage=75 -XX:MinRAMPercentage=25 -Dspring.profiles.active=without-vault -Dspringdoc.swagger-ui.enabled=${SPRINGDOC_UI} -Dspringdoc.api-docs.enabled=${SPRINGDOC_DOC} application.jar diff --git a/README.md b/README.md index 9194858722..c895faa413 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Welcome to the OWASP WrongSecrets game! The game is packed with real life examples of how to _not_ store secrets in your software. Each of these examples is captured in a challenge, which you need to solve using various tools and techniques. Solving these challenges will help you recognize common mistakes & can help you to reflect on your own secrets management strategy. -Can you solve all the 51 challenges? +Can you solve all the 55 challenges? Try some of them on [our Heroku demo environment](https://wrongsecrets.herokuapp.com/). @@ -74,7 +74,7 @@ Copyright (c) 2020-2025 Jeroen Willemsen and WrongSecrets contributors. ## Basic docker exercises -_Can be used for challenges 1-4, 8, 12-32, 34, 35-43, 49-52_ +_Can be used for challenges 1-4, 8, 12-32, 34, 35-43, 49-52, 54-55_ For the basic docker exercises you currently require: @@ -131,6 +131,8 @@ Now you can try to find the secrets by means of solving the challenge offered at - [localhost:8080/challenge/challenge-50](http://localhost:8080/challenge/challenge-50) - [localhost:8080/challenge/challenge-51](http://localhost:8080/challenge/challenge-51) - [localhost:8080/challenge/challenge-52](http://localhost:8080/challenge/challenge-52) +- [localhost:8080/challenge/challenge-54](http://localhost:8080/challenge/challenge-54) +- [localhost:8080/challenge/challenge-55](http://localhost:8080/challenge/challenge-55) @@ -160,7 +162,7 @@ If you want to host WrongSecrets on Railway, you can do so by deploying [this on ## Basic K8s exercise -_Can be used for challenges 1-6, 8, 12-43, 48-53_ +_Can be used for challenges 1-6, 8, 12-43, 48-55_ ### Minikube based @@ -237,7 +239,7 @@ now you can use the provided IP address and port to further play with the K8s va ## Vault exercises with minikube -_Can be used for challenges 1-8, 12-53_ +_Can be used for challenges 1-8, 12-55_ Make sure you have the following installed: - minikube with docker (or comment out line 8 and work at your own k8s setup), @@ -255,7 +257,7 @@ This is because if you run the start script again it will replace the secret in ## Cloud Challenges -_Can be used for challenges 1-53_ +_Can be used for challenges 1-55_ **READ THIS**: Given that the exercises below contain IAM privilege escalation exercises, never run this on an account which is related to your production environment or can influence your account-over-arching diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge54.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge54.java new file mode 100644 index 0000000000..1b867984c2 --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge54.java @@ -0,0 +1,51 @@ +package org.owasp.wrongsecrets.challenges.docker; + +import static org.owasp.wrongsecrets.Challenges.ErrorResponses.DECRYPTION_ERROR; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.Base64; +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import lombok.extern.slf4j.Slf4j; +import org.owasp.wrongsecrets.challenges.FixedAnswerChallenge; +import org.springframework.stereotype.Component; + +/** Challenge with a secret in .gitignore */ +@Component +@Slf4j +public class Challenge54 extends FixedAnswerChallenge { + + @Override + public String getAnswer() { + return decryptAES(); + } + + @SuppressFBWarnings( + value = {"CIPHER_INTEGRITY", "PADDING_ORACLE"}, + justification = + "Though using AES CBC is a bad idea now that we have GCM-SIV, we want to use a simple" + + " example") + private String decryptAES() { + final String encryptedSecret = "qQJhKBO20XX1y8/AJVM4PwME0Sl+l/3/76cP6zIRLJo="; + final String passphrase = "key_to_decrypt_the_secret"; + try { + + MessageDigest sha = MessageDigest.getInstance("SHA-256"); + byte[] keyBytes = sha.digest(passphrase.getBytes(StandardCharsets.UTF_8)); + SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES"); + byte[] ivBytes = "0123456789abcdef".getBytes(StandardCharsets.UTF_8); // 16 chars = 128 bits + IvParameterSpec ivSpec = new IvParameterSpec(ivBytes); + + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec); + byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedSecret)); + return new String(decryptedBytes, StandardCharsets.UTF_8); + } catch (Exception e) { + log.warn("Exception with Challenge 54", e); + return DECRYPTION_ERROR; + } + } +} diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge55.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge55.java new file mode 100644 index 0000000000..d0529f2611 --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge55.java @@ -0,0 +1,64 @@ +package org.owasp.wrongsecrets.challenges.docker; + +import static org.owasp.wrongsecrets.Challenges.ErrorResponses.FILE_MOUNT_ERROR; + +import com.google.common.base.Strings; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +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; + +/** Challenge with a secret in .ssh/authorized_keys */ +@Slf4j +@Component +public class Challenge55 implements Challenge { + + private final String basionhostpath; + private String actualData; + + public Challenge55(@Value("${basionhostpath}") String basionhostpath) { + this.basionhostpath = basionhostpath; + } + + @Override + public Spoiler spoiler() { + return new Spoiler(getActualData()); + } + + @Override + public boolean answerCorrect(String answer) { + return !Strings.isNullOrEmpty(answer) + && answer.length() > 10 + && (answer.contains(getActualData()) + || getActualData() + .replace("\r", "") + .replace("\n", "") + .replace(" ", "") + .contains(answer.replace("\r", "").replace("\n", "").replace(" ", ""))); + } + + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The location of the basionhostpath is based on an Env Var") + private String getActualData() { + if (Strings.isNullOrEmpty(actualData)) { + try { + actualData = + Files.readString( + Paths.get(basionhostpath, "wrongsecrets.keys"), StandardCharsets.UTF_8); + } catch (Exception e) { + log.warn( + "Exception during file reading, defaulting to default without a docker container" + + " environment", + e); + return FILE_MOUNT_ERROR; + } + } + return actualData; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 42cccbc117..9711af2940 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -80,6 +80,7 @@ management.health.livenessState.enabled=true management.health.readinessState.enabled=true management.endpoints.web.exposure.include=auditevents,info,health chalenge_docker_mount_secret=/var/run/secrets2 +basionhostpath=.ssh #--- spring.config.activate.on-profile=kubernetes-vault wrongsecretvalue=wrongsecret @@ -96,6 +97,7 @@ spring.cloud.vault.kubernetes.service-account-token-file=/var/run/secrets/kubern #--- spring.config.activate.on-profile=local challengedockermtpath=./ +basionhostpath=.ssh asciidoctor.enabled=true #--- spring.config.activate.on-profile=local-vault diff --git a/src/main/resources/explanations/challenge54.adoc b/src/main/resources/explanations/challenge54.adoc new file mode 100644 index 0000000000..ce0c9a98fa --- /dev/null +++ b/src/main/resources/explanations/challenge54.adoc @@ -0,0 +1,17 @@ +=== .gitignore Secret Challenge + +`.gitignore` files help avoid accidental commit of sensitive or irrelevant data into source control. However, sometimes developers mistakenly add sensitive data or secrets as comments or hidden entries within `.gitignore`. + +In this challenge, a developer left behind an encrypted secret in a `.gitignore` file comment. Even though encrypted, it highlights how easy it is to forget critical secrets in accessible locations. + +Your goal is to find and decrypt this forgotten secret. + +==== Note +The secret is encrypted using AES-256-CBC and with an IV. Use the key found in ".gitignore" to decrypt it. +We used the following command for encryption: + +```bash +echo -n "" | openssl enc -aes-256-cbc -K \ + -iv 30313233343536373839616263646566 \ + -nosalt -base64 -e +``` diff --git a/src/main/resources/explanations/challenge54_hint.adoc b/src/main/resources/explanations/challenge54_hint.adoc new file mode 100644 index 0000000000..3d1b16d047 --- /dev/null +++ b/src/main/resources/explanations/challenge54_hint.adoc @@ -0,0 +1,13 @@ +The secret is hidden within the `.gitignore` file as an AES-256-CBC encrypted. + +Follow these steps to decrypt the secret: + +1. Locate the encrypted comment and the key to decrypt it in `.gitignore`. +2. Use OpenSSL to decrypt: ++ +[source,bash] +---- + echo "h0vRDUS3VDN2gwfd1oftcMtD/YlKE0YfHLTvyk/lrY4=" | openssl enc -aes-256-cbc -K 6b3b913c09d62238b9b8c0cc78904e7e1f9a99fd0a04ebae50a8a9881d452bbd \ + -iv 30313233343536373839616263646566 \ + -nosalt -base64 -d +---- diff --git a/src/main/resources/explanations/challenge54_reason.adoc b/src/main/resources/explanations/challenge54_reason.adoc new file mode 100644 index 0000000000..e5494b574a --- /dev/null +++ b/src/main/resources/explanations/challenge54_reason.adoc @@ -0,0 +1,8 @@ +=== Why placing secrets in .gitignore is a security risk? + +Developers regularly update configuration files like `.gitignore`, occasionally leaving sensitive information behind—such as passwords, tokens, or critical file paths. These secrets, even encrypted, pose risks: + +- Attackers could discover and decrypt the secrets. +- Forgotten secrets in public files indicate poor security practices and weak secret management. + +This challenge demonstrates the importance of code reviews and ensuring secrets are never accidentally committed or left behind. diff --git a/src/main/resources/explanations/challenge55.adoc b/src/main/resources/explanations/challenge55.adoc new file mode 100644 index 0000000000..1c7eb64055 --- /dev/null +++ b/src/main/resources/explanations/challenge55.adoc @@ -0,0 +1,7 @@ +=== .ssh Bastion? + +We have seen many so-called "bastion containers". Here people create a Docker container and embed the "secret" to authenticate to the host inside the container. This secret is often an SSH keypair used to authenticate against a host. In some cases the keypair is committed to git and in some cases it is injected into the container before teh container is pushed to a registry. + +In order to make the bastion container work, we have to embed an SSH configuration file (`.ssh/config`) to simplify the SSH connection setup by storing host-specific configurations. + +Can you find the private key? Paste its contents into the answer box below. diff --git a/src/main/resources/explanations/challenge55_hint.adoc b/src/main/resources/explanations/challenge55_hint.adoc new file mode 100644 index 0000000000..86760eab56 --- /dev/null +++ b/src/main/resources/explanations/challenge55_hint.adoc @@ -0,0 +1,14 @@ +The private key is located inside the .ssh part of the container and accidentally comitted to git in this case ;-). + +You can find the key by: + +1. obtaining it from git in the [`.ssh/wrongsecrets.keys`]() file: +- Clone the repo with `git clone https://github.com/OWASP/wrongsecrets`. +- Go into the projects root and cd into `.ssh` folder and find the `wrongsecrets.keys` file. + +2. obtaining it from the docker container: +- Start the container locally with `docker run jeroenwillemsen/wrongsecrets:` + - Find the container id by doing `docker ps` in a next terminal + - Do `docker exec -it sh` + - go to /home/wrongsecrets/.ssh/ + - do `cat wrongsecrets.keys` diff --git a/src/main/resources/explanations/challenge55_reason.adoc b/src/main/resources/explanations/challenge55_reason.adoc new file mode 100644 index 0000000000..1f135e8891 --- /dev/null +++ b/src/main/resources/explanations/challenge55_reason.adoc @@ -0,0 +1,7 @@ +=== Risks of sensitive data in SSH configuration files + +Developers frequently store SSH connection configurations in `.ssh/config` files. However, this convenience may lead to the accidental inclusion of sensitive information, such as passwords or keys, which could become forgotten over time. + +Forgotten secrets indicate inadequate security checks and poor secret management practices. + +This challenge emphasizes the importance of secure storage practices and periodic auditing of sensitive configuration files. diff --git a/src/main/resources/wrong-secrets-configuration.yaml b/src/main/resources/wrong-secrets-configuration.yaml index 8c30e66d81..69b146e9f0 100644 --- a/src/main/resources/wrong-secrets-configuration.yaml +++ b/src/main/resources/wrong-secrets-configuration.yaml @@ -840,3 +840,29 @@ configurations: category: *secrets ctf: enabled: false + + - name: Challenge 54 + short-name: "challenge-54" + sources: + - class-name: "org.owasp.wrongsecrets.challenges.docker.Challenge54" + explanation: "explanations/challenge54.adoc" + hint: "explanations/challenge54_hint.adoc" + reason: "explanations/challenge54_reason.adoc" + environments: *all_envs + difficulty: *normal + category: *secrets + ctf: + enabled: true + + - name: Challenge 55 + short-name: "challenge-55" + sources: + - class-name: "org.owasp.wrongsecrets.challenges.docker.Challenge55" + explanation: "explanations/challenge55.adoc" + hint: "explanations/challenge55_hint.adoc" + reason: "explanations/challenge55_reason.adoc" + environments: *all_envs + difficulty: *easy + category: *secrets + ctf: + enabled: true diff --git a/src/test/java/org/owasp/wrongsecrets/challenges/docker/Challenge54Test.java b/src/test/java/org/owasp/wrongsecrets/challenges/docker/Challenge54Test.java new file mode 100644 index 0000000000..04485bb080 --- /dev/null +++ b/src/test/java/org/owasp/wrongsecrets/challenges/docker/Challenge54Test.java @@ -0,0 +1,26 @@ +package org.owasp.wrongsecrets.challenges.docker; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class Challenge54Test { + + @Test + void rightAnswerShouldSolveChallenge() throws Exception { + var challenge = new Challenge54(); + + String clearSecret = "(<:GITIGN0RE_SECRET:>)"; + + assertThat(challenge.answerCorrect(clearSecret)).isTrue(); + } + + @Test + void incorrectAnswerShouldNotSolveChallenge() throws Exception { + var challenge = new Challenge54(); + + String wrongSecret = "wrong answer"; + + assertThat(challenge.answerCorrect(wrongSecret)).isFalse(); + } +} diff --git a/src/test/java/org/owasp/wrongsecrets/challenges/docker/Challenge55Test.java b/src/test/java/org/owasp/wrongsecrets/challenges/docker/Challenge55Test.java new file mode 100644 index 0000000000..c359efc849 --- /dev/null +++ b/src/test/java/org/owasp/wrongsecrets/challenges/docker/Challenge55Test.java @@ -0,0 +1,44 @@ +package org.owasp.wrongsecrets.challenges.docker; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.owasp.wrongsecrets.Challenges; +import org.owasp.wrongsecrets.challenges.Spoiler; + +class Challenge55Test { + + @Test + void solveChallenge55WithoutFile(@TempDir Path dir) { + var challenge = new Challenge55(dir.toString()); + + assertThat(challenge.answerCorrect("secretvalueWitFile")).isFalse(); + assertThat(challenge.answerCorrect(Challenges.ErrorResponses.FILE_MOUNT_ERROR)).isTrue(); + } + + @Test + void soslveChallenge55WithFile(@TempDir Path dir) throws Exception { + var testFile = new File(dir.toFile(), "wrongsecrets.keys"); + var secret = "some privatey key"; + Files.writeString(testFile.toPath(), secret); + + var challenge = new Challenge55(dir.toString()); + assertThat(challenge.answerCorrect("some privatey key")).isTrue(); + } + + @Test + void spoilShouldReturnCorrectAnswer(@TempDir Path dir) throws IOException { + var testFile = new File(dir.toFile(), "wrongsecrets.keys"); + var secret = "secretvalueWitFile"; + Files.writeString(testFile.toPath(), secret); + + var challenge = new Challenge55(dir.toString()); + + assertThat(challenge.spoiler()).isEqualTo(new Spoiler("secretvalueWitFile")); + } +}