about summary refs log tree commit diff
diff options
context:
space:
mode:
authormaride <maride@darknebu.la>2018-08-14 17:28:47 +0200
committermaride <maride@darknebu.la>2018-08-14 17:28:47 +0200
commit6a66ed60af5ba83d3e9c064d41dbd1b7a0f23468 (patch)
tree5c940674cfbcc93b52eb9ed87c78f1236976a859
parent728e93e07648adb84c83f77687077d18987e0319 (diff)
Add challenges
-rw-r--r--README.md22
-rw-r--r--src/challenge.go23
-rw-r--r--src/http.go21
-rw-r--r--src/main.go5
-rw-r--r--src/seed.go86
5 files changed, 157 insertions, 0 deletions
diff --git a/README.md b/README.md
index 433f658..89b2017 100644
--- a/README.md
+++ b/README.md
@@ -18,3 +18,25 @@ This executable needs some parameters to work properly:
 | `-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. |
+
+## 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"
+    },
+    {
+      "name": "Insecure Stuff",
+      [...]
+  ]
+}
+```
diff --git a/src/challenge.go b/src/challenge.go
new file mode 100644
index 0000000..a1f9893
--- /dev/null
+++ b/src/challenge.go
@@ -0,0 +1,23 @@
+package main
+
+type Challenge struct {
+	Name string
+	Description string
+	Flag string // this should never leave the server
+	Container string // this could, but is not required as well
+	Category string
+}
+
+type StrippedChallenge struct {
+	Name string `json:"name"`
+	Description string `json:"description"`
+	Category string `json:"category"`
+}
+
+func stripChallenge(c Challenge) (StrippedChallenge) {
+	return StrippedChallenge{
+		Name: c.Name,
+		Description: c.Description,
+		Category: c.Category,
+	}
+}
diff --git a/src/http.go b/src/http.go
index 9f97352..1ddebe5 100644
--- a/src/http.go
+++ b/src/http.go
@@ -8,6 +8,7 @@ import (
 	"strings"
 	"io/ioutil"
 	"time"
+	"log"
 )
 
 var (
@@ -26,6 +27,7 @@ func runHTTPServer() (error) {
 	r.HandleFunc("/login", loginGetHandler).Methods("GET")
 	r.HandleFunc("/login", loginPostHandler).Methods("POST")
 	r.HandleFunc("/logout", logoutHandler).Methods("GET")
+	r.HandleFunc("/api/getChallenges", getChallengesHandler).Methods("GET")
 
 	address := fmt.Sprintf(":%d", *port)
 	return http.ListenAndServe(address, r)
@@ -141,3 +143,22 @@ func logoutHandler(w http.ResponseWriter, r *http.Request) {
 
 	http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
 }
+
+func getChallengesHandler(w http.ResponseWriter, r *http.Request) {
+	session, cookieNotFoundError := r.Cookie("session")
+
+	if cookieNotFoundError != nil || !isValidSession(session.Value) {
+		// either no session cookie found, or it contains an invalid session token. Redirect.
+		http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
+	} else {
+		// valid session token found, send out JSON array containing all challenges
+		json, jsonErr := generateJSONFromChallenges()
+
+		if jsonErr == nil {
+			w.Write([]byte(json))
+		} else {
+			log.Println(jsonErr)
+			w.WriteHeader(500)
+		}
+	}
+}
diff --git a/src/main.go b/src/main.go
index 519943a..8ca13a7 100644
--- a/src/main.go
+++ b/src/main.go
@@ -10,8 +10,13 @@ func main() {
 	registerHTTPFlags()
 	registerSessionFlags()
 	registerCredentialsFlags()
+	registerSeedFlags()
 	flag.Parse()
 
+	// Read challenges from file
+	getChallengesFromSeedFile()
+	generateJSONFromChallenges()
+
 	// Run HTTP server
 	log.Fatalln(runHTTPServer())
 }
\ No newline at end of file
diff --git a/src/seed.go b/src/seed.go
new file mode 100644
index 0000000..8256929
--- /dev/null
+++ b/src/seed.go
@@ -0,0 +1,86 @@
+package main
+
+import (
+	"flag"
+	"io/ioutil"
+	"log"
+	"encoding/json"
+)
+
+var (
+	challenges = []Challenge{}
+	seedFilePath* string
+)
+
+func registerSeedFlags() {
+	seedFilePath = flag.String("seedFile", "/etc/companion.json", "Path to seedfile")
+}
+
+// Read a given seed file and return their containing challenges
+func readSeedFile(path string) ([]Challenge) {
+	var jsonContents map[string]interface{}
+
+	// Read file
+	rawContents, readError := ioutil.ReadFile(path)
+	if readError != nil {
+		log.Printf("Failed to read seed file at %s: %s", *seedFilePath, readError.Error())
+		return nil
+	}
+
+	// Convert JSON String to map
+	unmarshalError := json.Unmarshal(rawContents, &jsonContents)
+	if unmarshalError != nil {
+		log.Printf("Failed to parse JSON in seed file at %s: %s", *seedFilePath, unmarshalError.Error())
+		return nil
+	}
+
+	tmpChallenges := []Challenge{}
+
+	// Iterate over challenges
+	for index, challengeObject := range jsonContents["challenges"].([]interface{}) {
+		challenge := challengeObject.(map[string]interface{})
+		// add our parsed challenge to array
+
+		name, nameOK := challenge["name"].(string)
+		desc, descOK := challenge["description"].(string)
+		flag, flagOK := challenge["flag"].(string)
+		cont, contOK := challenge["container"].(string)
+		category, categoryOK := challenge["category"].(string)
+
+		if nameOK && descOK && flagOK && contOK && categoryOK {
+			tmpChallenges = append(tmpChallenges, Challenge{
+				Name: name,
+				Description: desc,
+				Flag: flag,
+				Container: cont,
+				Category: category,
+			})
+		} else {
+			log.Printf("Ignoring challenge at position %d: Not all values are parseable (name: %b, desc: %b, flag: %b, container: %b, category: %b", index, nameOK, descOK, flagOK, contOK, categoryOK)
+		}
+	}
+
+	return tmpChallenges
+}
+
+// Read the file we set up using flags and store the returned challenge array
+func getChallengesFromSeedFile() {
+	tmpChallenges := readSeedFile(*seedFilePath)
+
+	if tmpChallenges != nil {
+		challenges = tmpChallenges
+	}
+}
+
+// Generate a JSON string from the stored challenges, stripped from content which might spoil the user
+func generateJSONFromChallenges() (string, error) {
+	// To avoid leakage of container name or the flag towards the user, we need to strip the challenges
+	var strippedChallenges []StrippedChallenge
+
+	for _, challenge := range challenges {
+		strippedChallenges = append(strippedChallenges, stripChallenge(challenge))
+	}
+
+	marshalled, marshalError := json.Marshal(strippedChallenges)
+	return string(marshalled), marshalError
+}