Skip to content

Commit

Permalink
Merge pull request #370 from commjoen/ctf-improved
Browse files Browse the repository at this point in the history
Ctf improved - layout fixes & support for lesser hackable CTFs
  • Loading branch information
commjoen authored Aug 9, 2022
2 parents 7e1063a + 2a088f8 commit 0702491
Show file tree
Hide file tree
Showing 16 changed files with 409 additions and 51 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/minikube-vault-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v3
- uses: innovationnorway/setup-vault@v1
with:
version: '~1.9'
version: '>1.9'
- name: Start minikube
uses: medyagh/setup-minikube@master
with:
Expand Down
17 changes: 16 additions & 1 deletion Dockerfile.web
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
FROM jeroenwillemsen/wrongsecrets:1.5.0-no-vault
FROM jeroenwillemsen/wrongsecrets:ctfd-safe-4-no-vault

ARG argBasedVersion="1.5.0"
ARG CANARY_URLS="http://canarytokens.com/terms/about/s7cfbdakys13246ewd8ivuvku/post.jsp,http://canarytokens.com/terms/about/y0all60b627gzp19ahqh7rl6j/post.jsp"
ARG CTF_ENABLED=false
ARG HINTS_ENABLED=true
#ONLY OVERRIDE THE ARGS BELOW WHEN YOU ARE SETTING UP A CTF!
ARG CTF_KEY=TRwzkRJnHOTckssAeyJbysWgP!Qc2T
ARG CHALLENGE_5_VALUE=if_you_see_this_please_use_k8s
ARG CHALLENGE_6_VALUE=if_you_see_this_please_use_k8s
ARG CHALLENGE_7_VALUE=if_you_see_this_please_use_K8S_and_Vault
ARG CHALLENGE_9_VALUE=if_you_see_this_please_use_AWS_Setup
ARG CHALLENGE_10_VALUE=if_you_see_this_please_use
ARG CHALLENGE_11_VALUE=if_you_see_this_please_use
ENV APP_VERSION=$argBasedVersion
ENV K8S_ENV=Heroku(Docker)
ENV canarytokenURLs=$CANARY_URLS
ENV ctf_enabled=$CTF_ENABLED
ENV ctf_key=$CTF_KEY
ENV hints_enabled=$HINTS_ENABLED
ENV challengedockermtpath="/var/helpers"
ENV keepasspath="/var/helpers/alibabacreds.kdbx"
ENV SPECIAL_K8S_SECRET=$CHALLENGE_5_VALUE
ENV SPECIAL_SPECIAL_K8S_SECRET=$CHALLENGE_6_VALUE
ENV vaultpassword=$CHALLENGE_7_VALUE
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
COPY .github/scripts/ /var/helpers
COPY src/test/resources/alibabacreds.kdbx /var/helpers
CMD java -Xms128m -Xmx128m -Xss512k -jar -Dserver.port=$PORT -XX:MaxRAMPercentage=75 -XX:MinRAMPercentage=25 -Dspring.profiles.active=without-vault application.jar
38 changes: 38 additions & 0 deletions ctf-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# CTF Instructions

So you want to play a CTF with WrongSecrets? This is the place to read up all about it.
Our CTF setup makes use of the [Juice Shop CTF CLI extension](https://github.com/juice-shop/juice-shop-ctf), which you
can read all about at [here](https://pwning.owasp-juice.shop/part1/ctf.html).

The difference between Juiceshop and WrongSecrets, is that WrongSecrets is more of a secrets-hunter game. Thiss means
that your contestants will try to find the CTF key soon after a few challenges. That is why we should separate out the
actual container for which the CTF scores are generated, from the container where the challenges live in.

You can see this practice already here in our repository: Our standard [Dockerfile](/Dockerfile) does not contain any
CTF entries, our Heroku [Dockerfile.web](/Dockerfile.web) does contain them.
So make sure you host your actual scoring Dockerfile.web at a place where your contestants cannot enter the container (
image) in order to extract the CTF key.

## Setting up CTFs

There are 3 flavors of CTF to be setup: Docker/Heroku, K8S, Cloud based.

### Docker or Heroku CTF

When doing a Docker or Heroku based CTF, you can follow
the [instructions in the readme](https://github.com/commjoen/wrongsecrets#ctfd-support).
If you want to use your own CTF key, you can build a container with the following
arguments `CTF_ENABLED=true,HINTS_ENABLED=false,CTF_KEY=<YOURNEWKEYHERE>`. Just make sure you provide the same key
to `juice-shop-ctf` when you run it.

Want to make it a little more exciting? Override the Dockerfile with your preferred values, so that copying from online
hosted solutions no longer works!

### K8s based CTF

TODO as #https://github.com/commjoen/wrongsecrets/issues/372

### Cloud based CTF

TODO as #https://github.com/commjoen/wrongsecrets/issues/372

43 changes: 35 additions & 8 deletions src/main/java/org/owasp/wrongsecrets/RuntimeEnvironment.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,23 @@
@Component
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;

@Value("${default_aws_value_challenge_9}")
private String defaultChallenge9Value; //used to determine if the cloud challenge values are overriden

private static final Map<Environment, List<Environment>> envToOverlappingEnvs = Map.of(
HEROKU_DOCKER, List.of(DOCKER, HEROKU_DOCKER),
DOCKER, List.of(DOCKER, HEROKU_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)
HEROKU_DOCKER, List.of(DOCKER, HEROKU_DOCKER),
DOCKER, List.of(DOCKER, HEROKU_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)
);

public enum Environment {
Expand All @@ -49,6 +58,16 @@ static Environment fromId(String id) {
@Getter
private final Environment runtimeEnvironment;

private boolean isK8sUnlockedInCTFMode() {
String defaultValueChallenge5 = "if_you_see_this_please_use_k8s";
return ctfModeEnabled && !challenge5Value.equals(defaultValueChallenge5);
}

private boolean isCloudUnlockedInCTFMode() {
String defaultValueAWSValue = "if_you_see_this_please_use_AWS_Setup";
return ctfModeEnabled && !defaultChallenge9Value.equals(defaultValueAWSValue);
}

@Autowired
public RuntimeEnvironment(@Value("${K8S_ENV}") String currentRuntimeEnvironment) {
this.runtimeEnvironment = Environment.fromId(currentRuntimeEnvironment);
Expand All @@ -59,8 +78,16 @@ public RuntimeEnvironment(Environment runtimeEnvironment) {
}

public boolean canRun(Challenge challenge) {
if (isCloudUnlockedInCTFMode()) {
return true;
}
if (isK8sUnlockedInCTFMode()) {
return challenge.supportedRuntimeEnvironments().contains(runtimeEnvironment)
|| challenge.supportedRuntimeEnvironments().contains(DOCKER) || challenge.supportedRuntimeEnvironments().contains(K8S)
|| challenge.supportedRuntimeEnvironments().contains(VAULT);
}
return challenge.supportedRuntimeEnvironments().contains(runtimeEnvironment)
|| !Collections.disjoint(envToOverlappingEnvs.get(runtimeEnvironment), challenge.supportedRuntimeEnvironments());
|| !Collections.disjoint(envToOverlappingEnvs.get(runtimeEnvironment), challenge.supportedRuntimeEnvironments());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ public ResponseEntity<String> processCanaryToken(@RequestBody CanaryToken canary
}
log.info("Canarytoken called, with manage_url {}", canaryToken.getManageUrl());
log.info("Total number of canary callback calls: {}", canaryCounter.getTotalCount());
/*
todo:
- follow 3 of baeldung.com/spring-server-sent-events, but make sure you register the emitter per connection
- and in a map lookup which emiter you can use for the given connection to send the event.
*/
return new ResponseEntity<>("all good", HttpStatus.ACCEPTED);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,17 @@ public String requiredEnv() {
.collect(Collectors.joining());
}

public int difficulty() {
return challenge.difficulty();
}

public boolean isChallengeEnabled() {
return runtimeEnvironment.canRun(challenge);
}

public static List<ChallengeUI> toUI(List<Challenge> challenges, RuntimeEnvironment environment) {
return challenges.stream()
.sorted(Comparator.comparingInt(challenge -> Integer.parseInt(challenge.getClass().getSimpleName().replace("Challenge",""))))
.sorted(Comparator.comparingInt(challenge -> Integer.parseInt(challenge.getClass().getSimpleName().replace("Challenge", ""))))
.map(challenge -> new ChallengeUI(challenge, challenges.indexOf(challenge) + 1, environment))
.toList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,16 @@
import org.owasp.wrongsecrets.RuntimeEnvironment;
import org.owasp.wrongsecrets.ScoreCard;
import org.owasp.wrongsecrets.asciidoc.TemplateGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.yaml.snakeyaml.Yaml;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

@Slf4j
@RestController
Expand All @@ -35,11 +32,14 @@ public class ChallengesAPIController {

private final TemplateGenerator templateGenerator;

private final RuntimeEnvironment runtimeEnvironment;

public ChallengesAPIController(ScoreCard scoreCard, List<ChallengeUI> 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;
}

Expand Down Expand Up @@ -117,8 +117,8 @@ private String extractResource(String resourceName) {
}

private String getDisabledEnv(ChallengeUI challenge) {
if (!challenge.getChallenge().supportedRuntimeEnvironments().contains(RuntimeEnvironment.Environment.DOCKER)) {
return "Docker";
if (runtimeEnvironment.canRun(challenge.getChallenge())) {
return runtimeEnvironment.getRuntimeEnvironment().name();
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class Challenge10 extends CloudChallenge {

public Challenge10(ScoreCard scoreCard,
@Value("${secretmountpath}") String filePath,
@Value("${default_aws_value}") String awsDefaultValue,
@Value("${default_aws_value_challenge_10}") String awsDefaultValue,
RuntimeEnvironment runtimeEnvironment) {
super(scoreCard, runtimeEnvironment);
this.awsDefaultValue = awsDefaultValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public class Challenge11 extends CloudChallenge {
private final String azureVaultUri;
private final String azureWrongSecret3;

private final String ctfValue;

private final boolean ctfEnabled;

public Challenge11(ScoreCard scoreCard,
@Value("${AWS_ROLE_ARN}") String awsRoleArn,
@Value("${AWS_WEB_IDENTITY_TOKEN_FILE}") String tokenFileLocation,
Expand All @@ -56,6 +60,8 @@ public Challenge11(ScoreCard scoreCard,
@Value("${azure.keyvault.uri}") String azureVaultUri,
@Value("${wrongsecret-3}") String azureWrongSecret3, // Exclusively auto-wired for Azure
@Value("${GCP_PROJECT_ID}") String projectId,
@Value("${default_aws_value_challenge_11}") String ctfValue,
@Value("${ctf_enabled}") boolean ctfEnabled,
RuntimeEnvironment runtimeEnvironment) {
super(scoreCard, runtimeEnvironment);
this.awsRoleArn = awsRoleArn;
Expand All @@ -67,6 +73,8 @@ public Challenge11(ScoreCard scoreCard,
this.projectId = projectId;
this.azureVaultUri = azureVaultUri;
this.azureWrongSecret3 = azureWrongSecret3;
this.ctfValue = ctfValue;
this.ctfEnabled = ctfEnabled;
this.challengeAnswer = getChallenge11Value(runtimeEnvironment);
}

Expand Down Expand Up @@ -96,6 +104,9 @@ public String getTech() {

private String getChallenge11Value(RuntimeEnvironment runtimeEnvironment) {
if (runtimeEnvironment != null && runtimeEnvironment.getRuntimeEnvironment() != null) {
if (ctfEnabled && ctfValue != awsDefaultValue) {
return ctfValue;
}
return switch (runtimeEnvironment.getRuntimeEnvironment()) {
case AWS -> getAWSChallenge11Value();
case GCP -> getGCPChallenge11Value();
Expand All @@ -112,27 +123,27 @@ private String getAWSChallenge11Value() {
try { //based on https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/sts/src/main/java/com/example/sts
String webIDentityToken = Files.readString(Paths.get(tokenFileLocation));
StsClient stsClient = StsClient.builder()
.region(Region.of(awsRegion))
.build();
.region(Region.of(awsRegion))
.build();
AssumeRoleWithWebIdentityRequest webIdentityRequest = AssumeRoleWithWebIdentityRequest.builder()
.roleArn(awsRoleArn)
.roleSessionName("WrongsecretsApp")
.webIdentityToken(webIDentityToken)
.build();
.roleArn(awsRoleArn)
.roleSessionName("WrongsecretsApp")
.webIdentityToken(webIDentityToken)
.build();

AssumeRoleWithWebIdentityResponse tokenResponse = stsClient.assumeRoleWithWebIdentity(webIdentityRequest);
log.info("The token value is " + tokenResponse.credentials().sessionToken());
SsmClient ssmClient = SsmClient.builder()
.region(Region.of(awsRegion))
.credentialsProvider(StsAssumeRoleWithWebIdentityCredentialsProvider.builder()
.stsClient(stsClient)
.refreshRequest(webIdentityRequest)
.build())
.build();
.region(Region.of(awsRegion))
.credentialsProvider(StsAssumeRoleWithWebIdentityCredentialsProvider.builder()
.stsClient(stsClient)
.refreshRequest(webIdentityRequest)
.build())
.build();
GetParameterRequest parameterRequest = GetParameterRequest.builder()
.name("wrongsecretvalue")
.withDecryption(true)
.build();
.name("wrongsecretvalue")
.withDecryption(true)
.build();
GetParameterResponse parameterResponse = ssmClient.getParameter(parameterRequest);
log.info("The parameter value is " + parameterResponse.parameter().value());
ssmClient.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class Challenge9 extends CloudChallenge {

public Challenge9(ScoreCard scoreCard,
@Value("${secretmountpath}") String filePath,
@Value("${default_aws_value}") String awsDefaultValue,
@Value("${default_aws_value_challenge_9}") String awsDefaultValue,
RuntimeEnvironment runtimeEnvironment) {
super(scoreCard, runtimeEnvironment);
this.awsDefaultValue = awsDefaultValue;
Expand Down
18 changes: 18 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ ARG_BASED_PASSWORD=if_you_see_this_please_use_docker_instead
DOCKER_ENV_PASSWORD=if_you_see_this_please_use_docker_instead
vaultpassword=if_you_see_this_please_use_K8S_and_Vault
default_aws_value=if_you_see_this_please_use_AWS_Setup
default_aws_value_challenge_9=if_you_see_this_please_use_AWS_Setup
default_aws_value_challenge_10=if_you_see_this_please_use_AWS_Setup
default_aws_value_challenge_11=if_you_see_this_please_use_AWS_Setup
default_gcp_value=if_you_see_this_please_use_GCP_Setup
default_azure_value=if_you_see_this_please_use_Azure_Setup
AWS_ROLE_ARN=if_you_see_this_please_use_AWS_Setup
Expand Down Expand Up @@ -68,3 +71,18 @@ spring.config.activate.on-profile=without-vault
wrongsecretvalue=wrongsecret
spring.cloud.vault.enabled=false
asciidoctor.enabled=false
#---
spring.config.activate.on-profile=without-vault-ctf-emulation
wrongsecretvalue=wrongsecret
spring.cloud.vault.enabled=false
asciidoctor.enabled=false
ctf_enabled=true
ctf_key=randomtextforkey
vaultpassword=ACTUAL_ANSWER_CHALLENGE7
secretmountpath=nothere
SPECIAL_K8S_SECRET=ACTUAL_ANSWER_CHALLENGE5
SPECIAL_SPECIAL_K8S_SECRET=ACTUAL_ANSWER_CHALLENGE6
default_aws_value_challenge_9=ACTUAL_ANSWER_CHALLENGE9
default_aws_value_challenge_10=ACTUAL_ANSWER_CHALLENGE10
default_aws_value_challenge_11=ACTUAL_ANSWER_CHALLENGE_11
K8S_ENV=Heroku(Docker)
4 changes: 2 additions & 2 deletions src/main/resources/templates/challenge.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<body>
<div class="container" layout:fragment="content">
<!--/*@thymesVar id="challenge" type="org.owasp.wrongsecrets.challenges.ChallengeUI"*/-->
<h1 class="mt-2" th:text="${challenge.name}"/>
<p class="h1 mt-2"> <span th:text="${challenge.name}"/> <span th:block th:each="i: ${#numbers.sequence(1, challenge.difficulty)}"><span></span></span></p>
<p><span th:text="'Welcome to challenge ' + ${challenge.challengeNumber} + '.'"></span> You need to guess the secret
that is hidden in <a href="https://github.com/commjoen/wrongsecrets">Java</a>, <a
href="https://hub.docker.com/r/jeroenwillemsen/wrongsecrets">Docker</a>, Kubernetes, Vault, AWS or GCP.</p>
Expand Down Expand Up @@ -84,7 +84,7 @@ <h1 class="mt-2" th:text="${challenge.name}"/>
</div>
<div th:if="${missingEnvWarning} eq 'AWS' or ${missingEnvWarning} eq 'GCP'" class="alert alert-danger"
role="alert">
We are running outside a properly configured AWS environment. Please run this in an AWS/GCP environment as
We are running outside a properly configured Cloud environment. Please run this in an AWS/Azure/GCP environment as
explained in the <a href="https://github.com/commjoen/wrongsecrets#cloud-challenges">README.md</a>.
</div>
</div>
Expand Down
Loading

0 comments on commit 0702491

Please sign in to comment.