Skip to content

Commit a439364

Browse files
authored
Merge pull request #2462 from OWASP/copilot/upgrade-ctf-party-to-version12
Upgrade juice-shop-ctf-cli instructions to v12 and add /api/Hints endpoint
2 parents c21324b + 3f14f45 commit a439364

File tree

5 files changed

+85
-4
lines changed

5 files changed

+85
-4
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -521,11 +521,13 @@ Initial creation of the zip file for CTFD requires you to visit [https://wrongse
521521
Follow the following steps:
522522

523523
```shell
524-
npm install -g juice-shop-ctf-cli@10.0.1
525-
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.
524+
npm install -g juice-shop-ctf-cli@12.0.0
525+
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.
526526
docker run -p 8001:8000 -it ctfd/ctfd:3.7.4
527527
```
528528

529+
> **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`.
530+
529531
Now visit the CTFD instance at [http://localhost:8001](http://localhost:8001) and setup your CTF.
530532
Then use the administrative backup function to import the zipfile you created with the juice-shop-ctf command.
531533
Game on using [https://wrongsecrets-ctf.herokuapp.com](https://wrongsecrets-ctf.herokuapp.com)!

ctf-instructions.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# CTF Instructions
22

33
So you want to play a CTF with WrongSecrets? This is the place to read up all about it.
4-
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).
4+
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).
55

66
The difference between Juiceshop and WrongSecrets, is that WrongSecrets is more of a secrets-hunter game.
77
This means that your contestants will try to find the CTF key soon after a few challenges.
@@ -35,6 +35,8 @@ There are 3 flavors of CTF to be setup: Docker/Heroku, K8S, Cloud based.
3535
When doing a Docker or Heroku based CTF, you can follow the [instructions in the readme](https://github.com/OWASP/wrongsecrets#ctfd-support).
3636
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
3737
to `juice-shop-ctf` when you run it.
38+
39+
> **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.
3840
Host the Docker container somewhere, where your users can not access the container variables directly, so they cannot extract the CTF key that easily.
3941
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!
4042
There are a few env-vars that you need to pay attention to when setting this up:

src/main/java/org/owasp/wrongsecrets/challenges/ChallengesCtfController.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.owasp.wrongsecrets.definitions.ChallengeDefinition;
1212
import org.owasp.wrongsecrets.definitions.ChallengeDefinitionsConfiguration;
1313
import org.owasp.wrongsecrets.definitions.Difficulty;
14+
import org.springframework.beans.factory.annotation.Value;
1415
import org.springframework.http.MediaType;
1516
import org.springframework.web.bind.annotation.GetMapping;
1617
import org.springframework.web.bind.annotation.RestController;
@@ -26,16 +27,53 @@ public class ChallengesCtfController {
2627
private final Challenges challenges;
2728
private final ChallengeDefinitionsConfiguration wrongSecretsConfiguration;
2829
private final RuntimeEnvironment runtimeEnvironment;
30+
private final boolean hintsEnabled;
2931

3032
public ChallengesCtfController(
3133
ScoreCard scoreCard,
3234
Challenges challenges,
3335
RuntimeEnvironment runtimeEnvironment,
34-
ChallengeDefinitionsConfiguration wrongSecretsConfiguration) {
36+
ChallengeDefinitionsConfiguration wrongSecretsConfiguration,
37+
@Value("${hints_enabled}") boolean hintsEnabled) {
3538
this.scoreCard = scoreCard;
3639
this.challenges = challenges;
3740
this.wrongSecretsConfiguration = wrongSecretsConfiguration;
3841
this.runtimeEnvironment = runtimeEnvironment;
42+
this.hintsEnabled = hintsEnabled;
43+
}
44+
45+
@GetMapping(
46+
value = {"/api/Hints", "/api/hints"},
47+
produces = MediaType.APPLICATION_JSON_VALUE)
48+
@Operation(
49+
summary =
50+
"Gives all hints back in a jsonArray, to be used with the Juiceshop CTF cli v12+."
51+
+ " Each challenge contributes one hint entry.")
52+
public String getHints() {
53+
var json = JsonNodeFactory.instance.objectNode();
54+
ArrayNode jsonArray = JsonNodeFactory.instance.arrayNode();
55+
if (hintsEnabled) {
56+
List<ChallengeDefinition> definitions = challenges.getDefinitions().challenges();
57+
for (int i = 0; i < definitions.size(); i++) {
58+
ChallengeDefinition definition = definitions.get(i);
59+
String hintText =
60+
definition.source(runtimeEnvironment).map(s -> s.hint().contents().get()).orElse(null);
61+
if (hintText != null) {
62+
int hintId = i + 1;
63+
var jsonHint = JsonNodeFactory.instance.objectNode();
64+
jsonHint.put("ChallengeId", hintId);
65+
jsonHint.put("id", hintId);
66+
jsonHint.put("text", hintText);
67+
jsonHint.put("order", 1);
68+
jsonHint.put("unlocked", true);
69+
jsonArray.add(jsonHint);
70+
}
71+
}
72+
}
73+
json.set("data", jsonArray);
74+
String result = json.toString();
75+
log.trace("returning hints {}", result);
76+
return result;
3977
}
4078

4179
@GetMapping(
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.owasp.wrongsecrets;
2+
3+
import static org.hamcrest.Matchers.containsString;
4+
import static org.hamcrest.Matchers.not;
5+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
6+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
7+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
8+
9+
import org.junit.jupiter.api.Test;
10+
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.boot.test.context.SpringBootTest;
12+
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
13+
import org.springframework.test.web.servlet.MockMvc;
14+
15+
@SpringBootTest(
16+
properties = {"hints_enabled=false"},
17+
classes = WrongSecretsApplication.class)
18+
@AutoConfigureMockMvc
19+
class ChallengeAPIControllerHintsDisabledTest {
20+
21+
@Autowired private MockMvc mvc;
22+
23+
@Test
24+
void shouldReturnEmptyHintsListWhenHintsDisabled() throws Exception {
25+
mvc.perform(get("/api/Hints"))
26+
.andExpect(status().isOk())
27+
.andExpect(content().string(not(containsString("ChallengeId"))))
28+
.andExpect(content().string(containsString("\"data\":[]")));
29+
}
30+
}

src/test/java/org/owasp/wrongsecrets/ChallengeAPiControllerTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ void shouldGetListOfChallenges() throws Exception {
2525
.andExpect(status().isOk())
2626
.andExpect(content().string(containsString("hint")));
2727
}
28+
29+
@Test
30+
void shouldGetListOfHints() throws Exception {
31+
mvc.perform(get("/api/Hints"))
32+
.andExpect(status().isOk())
33+
.andExpect(content().string(containsString("ChallengeId")))
34+
.andExpect(content().string(containsString("text")))
35+
.andExpect(content().string(containsString("unlocked")));
36+
}
2837
}
2938

3039
/*

0 commit comments

Comments
 (0)