1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
# circus-companion
The companion ~cube~ container spawned specifically for one user.
## Purpose
- Validator for flags
- Temporary secret vault for the user
- control endpoint of challenge containers for the user
## Usage
This executable needs some parameters to work properly:
| Key | Required? | Description |
|---|---|---|
| `-port` | No | The port for HTTP. *Default: 8080* |
| `-username` | No | Name of our user, as used e.g. in salutation. No length or charset limitation. *Default: Player 1* |
| `-accessCode` | Yes | Access code for the user. *Default: AllYourCodesAreBelongToUs* |
| `-sessionSalt` | Yes | Variable to salt the session token generator with. |
| `-seedFile` | Yes | JSON file to read challenge information from. |
| `-vpnRemoteAddress` | Yes | Address the VPN will run on, as rendered into the client VPN configuration file. |
| `-vpnRemotePort` | No | Port the VPN will run on |
| `-endTimestamp` | No | Date/Time after which flags are not accepted anymore |
| `-endAfter` | No | Seconds (!) after the first login, after which flags are not accepted anymore |
If `-endTimestamp` **and** `-endAfter` is given, flags are not accepted if **one** of the given flags kicks in.
## Seed file
The seed file should be of the following format:
```
{
"challenges": [
{
"name": "Evil Service",
"description": "A meaningful and funny description",
"flag": "CIRCUS[IS_REALLY_COOL]",
"container": "challenge-evilservice",
"category": "Miscellaneous",
"points": 100
},
{
"name": "Insecure Stuff",
[...]
]
}
```
## API endpoints
| Path | Method | Parameter | Description |
|---|---|---|---|
| `/` | GET | - | static hosting `/files/index.html` |
| `/files/{file}` | GET | - | static hosting `{file}`, from `hosted/` directory. *Path traversal*-safe |
| `/login` | GET | - | static hosting `/files/login.html` |
| `/login` | POST | `username`, `accesscode` | processing `username` and `accesscode`, giving the user a session if they are valid |
| `/logout` | GET | - | Invalidates the session |
| `/challenges` | GET | - | static hosting `/files/challenges.html` |
| `/access` | GET | - | static hosting `/files/access.html` |
| `/api/getChallenges` | GET | - | Returns all challenges, as JSON. For an example, see below. |
| `/api/submitFlag` | POST | `challengeName`, `flag` | Validates `flag` for `challengeName` |
| `/api/startContainer` | POST | `challengeName` | Starts the challenge container for `challengeName`, if it's not already started |
| `/api/stopContainer` | POST | `challengeName` | Stops the challenge container for `challengeName`, if it's running |
| `/api/getAccess` | GET | - | Returns the VPN configuration required for accessing the containers |
### Response: `/api/getChallenges`
```
{
"categories": {
"Miscellaneous":1 // contains the amount of challenges in that category
},
"challenges": [
{
"name": "ChallengeName",
"description": "Some meaningful, interesting and funny description",
"category": "Miscellaneous",
"points": 100,
"foundFlag": false, // whether the user found the flag
"ContainsLaunchable": true, // if set to true: there's a container which can be started/stopped
"IPAddress": "1.2.3.4" // contains the IP address if the container is running
}
]
}
```
|