Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -521,11 +521,13 @@ Initial creation of the zip file for CTFD requires you to visit [https://wrongse
Follow the following steps:

```shell
npm install -g juice-shop-ctf-cli@10.0.1
juice-shop-ctf #choose ctfd and https://wrongsecrets-ctf.herokuapp.com as domain. No trailing slash! The key is 'TRwzkRJnHOTckssAeyJbysWgP!Qc2T', feel free to enable hints. We do not support snippets or links/urls to code or hints.
npm install -g juice-shop-ctf-cli@12.0.0
juice-shop-ctf #choose ctfd and https://wrongsecrets-ctf.herokuapp.com as domain. No trailing slash! The key is 'TRwzkRJnHOTckssAeyJbysWgP!Qc2T', feel free to enable hints.
docker run -p 8001:8000 -it ctfd/ctfd:3.7.4
```

> **Note:** Hints can only be generated if the WrongSecrets instance has hints enabled (`HINTS_ENABLED=true`). The Heroku CTF instance ([https://wrongsecrets-ctf.herokuapp.com](https://wrongsecrets-ctf.herokuapp.com)) runs with `HINTS_ENABLED=false`, so selecting "Free hints" or "Paid hints" will result in an empty hints list. To generate hints, run your own instance with `HINTS_ENABLED=true`.

Now visit the CTFD instance at [http://localhost:8001](http://localhost:8001) and setup your CTF.
Then use the administrative backup function to import the zipfile you created with the juice-shop-ctf command.
Game on using [https://wrongsecrets-ctf.herokuapp.com](https://wrongsecrets-ctf.herokuapp.com)!
Expand Down
4 changes: 3 additions & 1 deletion ctf-instructions.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 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/companion-guide/snapshot/part1/ctf.html).
Our CTF setup makes use of the [Juice Shop CTF CLI extension](https://github.com/juice-shop/juice-shop-ctf), which is documented [here](https://pwning.owasp-juice.shop/companion-guide/latest/part4/ctf.html).

The difference between Juiceshop and WrongSecrets, is that WrongSecrets is more of a secrets-hunter game.
This means that your contestants will try to find the CTF key soon after a few challenges.
Expand Down Expand Up @@ -35,6 +35,8 @@ There are 3 flavors of CTF to be setup: Docker/Heroku, K8S, Cloud based.
When doing a Docker or Heroku based CTF, you can follow the [instructions in the readme](https://github.com/OWASP/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.

> **Note:** Hints for `juice-shop-ctf` can only be generated if the WrongSecrets instance has hints enabled (`HINTS_ENABLED=true`). When `HINTS_ENABLED=false` (the default for CTF containers), the `/api/Hints` endpoint returns an empty list, so selecting "Free hints" or "Paid hints" in `juice-shop-ctf` will produce challenges without hints. Set `HINTS_ENABLED=true` when building your container if you want hints to be included in the CTFd export.
Host the Docker container somewhere, where your users can not access the container variables directly, so they cannot extract the CTF key that easily.
Want to make it a little more exciting? Create your own custom Docker image for both the 'play-environment' and the 'CTF-scoring-environment', where you override certain values (e.g. the ARG, the docker ENV, etc.) with your preferred values, so that copying from any existing online solution no longer works!
There are a few env-vars that you need to pay attention to when setting this up:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.owasp.wrongsecrets.definitions.ChallengeDefinition;
import org.owasp.wrongsecrets.definitions.ChallengeDefinitionsConfiguration;
import org.owasp.wrongsecrets.definitions.Difficulty;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -26,16 +27,53 @@ public class ChallengesCtfController {
private final Challenges challenges;
private final ChallengeDefinitionsConfiguration wrongSecretsConfiguration;
private final RuntimeEnvironment runtimeEnvironment;
private final boolean hintsEnabled;

public ChallengesCtfController(
ScoreCard scoreCard,
Challenges challenges,
RuntimeEnvironment runtimeEnvironment,
ChallengeDefinitionsConfiguration wrongSecretsConfiguration) {
ChallengeDefinitionsConfiguration wrongSecretsConfiguration,
@Value("${hints_enabled}") boolean hintsEnabled) {
this.scoreCard = scoreCard;
this.challenges = challenges;
this.wrongSecretsConfiguration = wrongSecretsConfiguration;
this.runtimeEnvironment = runtimeEnvironment;
this.hintsEnabled = hintsEnabled;
}

@GetMapping(
value = {"/api/Hints", "/api/hints"},
produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(
summary =
"Gives all hints back in a jsonArray, to be used with the Juiceshop CTF cli v12+."
+ " Each challenge contributes one hint entry.")
public String getHints() {
var json = JsonNodeFactory.instance.objectNode();
ArrayNode jsonArray = JsonNodeFactory.instance.arrayNode();
if (hintsEnabled) {
List<ChallengeDefinition> definitions = challenges.getDefinitions().challenges();
for (int i = 0; i < definitions.size(); i++) {
ChallengeDefinition definition = definitions.get(i);
String hintText =
definition.source(runtimeEnvironment).map(s -> s.hint().contents().get()).orElse(null);
if (hintText != null) {
int hintId = i + 1;
var jsonHint = JsonNodeFactory.instance.objectNode();
jsonHint.put("ChallengeId", hintId);
jsonHint.put("id", hintId);
jsonHint.put("text", hintText);
jsonHint.put("order", 1);
jsonHint.put("unlocked", true);
jsonArray.add(jsonHint);
}
}
}
json.set("data", jsonArray);
String result = json.toString();
log.trace("returning hints {}", result);
return result;
}

@GetMapping(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.owasp.wrongsecrets;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest(
properties = {"hints_enabled=false"},
classes = WrongSecretsApplication.class)
@AutoConfigureMockMvc
class ChallengeAPIControllerHintsDisabledTest {

@Autowired private MockMvc mvc;

@Test
void shouldReturnEmptyHintsListWhenHintsDisabled() throws Exception {
mvc.perform(get("/api/Hints"))
.andExpect(status().isOk())
.andExpect(content().string(not(containsString("ChallengeId"))))
.andExpect(content().string(containsString("\"data\":[]")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ void shouldGetListOfChallenges() throws Exception {
.andExpect(status().isOk())
.andExpect(content().string(containsString("hint")));
}

@Test
void shouldGetListOfHints() throws Exception {
mvc.perform(get("/api/Hints"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("ChallengeId")))
.andExpect(content().string(containsString("text")))
.andExpect(content().string(containsString("unlocked")));
}
}

/*
Expand Down
Loading