about summary refs log tree commit diff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go188
1 files changed, 188 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..35f32dc
--- /dev/null
+++ b/main.go
@@ -0,0 +1,188 @@
+package main
+
+import (
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"strings"
+
+	"gopkg.in/h2non/gentleman.v2"
+)
+
+var session = flag.String("session", "9e8831af-ce30-48c3-8663-4b27262f43f1.pjKPVCYufDhuA9GPJAlc_xh45M8", "The session (the value of the cookie named 'session')")
+var rootURL = flag.String("url", "https://ctf.example.com", "The root URL of the CTFd instance")
+var outputFolder = flag.String("out", "challdump", "The name of the folder to dump the files to")
+
+func main() {
+	flag.Parse()
+
+	// fetch the list of all challenges
+	challs, err := fetchAllChallenges()
+	if err != nil {
+		panic(err)
+	}
+
+	// iterate over all challenges downloading all files
+	fmt.Println("Downloading the files included in the challenges")
+	for _, chall := range challs.Data {
+
+		// define where to store the challenge
+		filepath := fmt.Sprintf("%s/%s/%s", *outputFolder, chall.Category, chall.Name)
+		fmt.Printf("→ %s\n", filepath)
+		err := os.MkdirAll(filepath, os.ModePerm) // create the directory
+		if err != nil {
+			fmt.Println(err)
+		}
+
+		// fetch the challenge information
+		challenge, err := fetchChallenge(chall.ID)
+		if err != nil {
+			fmt.Println(err)
+		}
+
+		// download all files
+		for _, file := range challenge.Data.Files {
+			err := Download(file, filepath)
+			if err != nil {
+				fmt.Println(err)
+			}
+		}
+
+		// store the description of the challenge in a README.md file
+		err = saveDescription(challenge, filepath)
+		if err != nil {
+			fmt.Println(err)
+		}
+	}
+}
+
+// fetchAllChallenges fetches the list of all challs using the ctfs api.
+func fetchAllChallenges() (Challenges, error) {
+	fmt.Println("Fetching all challenges using the ctf api...")
+	cli := gentleman.New()
+	cli.URL(*rootURL)
+
+	req := cli.Request()
+	req.Path("/api/v1/challenges")
+
+	req.SetHeader("Cookie", fmt.Sprintf("session=%s", *session))
+
+	// Perform the request
+	res, err := req.Send()
+	if err != nil {
+		fmt.Printf("Request error: %s\n", err)
+		return Challenges{}, err
+	}
+	if !res.Ok {
+		fmt.Printf("Invalid server response: %d\n", res.StatusCode)
+		return Challenges{}, err
+	}
+
+	// unmarshal the resulting json into a Challenges struct
+	var challenges Challenges
+	if err := json.Unmarshal(res.Bytes(), &challenges); err != nil {
+		return Challenges{}, err
+	}
+	fmt.Println("Done fetching all challenges")
+	return challenges, nil
+}
+
+// fetchChallenge fetches a single challenge
+func fetchChallenge(id int) (Challenge, error) {
+	cli := gentleman.New()
+	cli.URL(*rootURL)
+
+	req := cli.Request()
+	req.Path(fmt.Sprintf("/api/v1/challenges/%d", id))
+
+	req.SetHeader("Cookie", fmt.Sprintf("session=%s", *session))
+
+	// Perform the request
+	res, err := req.Send()
+	if err != nil {
+		fmt.Printf("Request error: %s\n", err)
+		return Challenge{}, err
+	}
+	if !res.Ok {
+		fmt.Printf("Invalid server response: %d\n", res.StatusCode)
+		return Challenge{}, err
+	}
+
+	var challenge Challenge
+	if err := json.Unmarshal(res.Bytes(), &challenge); err != nil {
+		return Challenge{}, err
+	}
+	return challenge, nil
+}
+
+// Download downloads a file from the given URL and stores it at the given
+// filepath
+func Download(url string, filepath string) error {
+
+	// So what the code below does, is it extracts the filename from the url by
+	// first splitting the url at slashed and then at questionmarks (could have
+	// used regex, but this is CTF code ¯\_(ツ)_/¯)
+	a := strings.Split(url, "/")
+	b := strings.Split(a[len(a)-1], "?")
+	filename := b[0]
+
+	prefix := *rootURL
+	fullurl := fmt.Sprintf("%s%s", prefix, url)
+
+	client := &http.Client{}
+	req, err := http.NewRequest("GET", fullurl, nil)
+	req.Header.Add("Cookie", fmt.Sprintf("session=%s", *session))
+
+	resp, err := client.Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+
+	// Create the file
+	out, err := os.Create(fmt.Sprintf("%s/%s", filepath, filename))
+	if err != nil {
+		return err
+	}
+	defer out.Close()
+
+	// Write the body to file
+	_, err = io.Copy(out, resp.Body)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func saveDescription(challenge Challenge, filepath string) error {
+	path := fmt.Sprintf("%s/README.md", filepath)
+	f, err := os.Create(path)
+	if err != nil {
+		return err
+	}
+
+	// fill the readme with some content
+	f.WriteString(fmt.Sprintf("# %s\n\n", challenge.Data.Name))
+	f.WriteString(fmt.Sprintf("Category: %s\n\n", challenge.Data.Category))
+
+	f.WriteString("Files:\n")
+	for _, file := range challenge.Data.Files {
+
+		// So what the code below does, is it extracts the filename from the url by
+		// first splitting the url at slashed and then at questionmarks (could have
+		// used regex, but this is CTF code ¯\_(ツ)_/¯)
+		a := strings.Split(file, "/")
+		b := strings.Split(a[len(a)-1], "?")
+		filename := b[0]
+		f.WriteString(fmt.Sprintf("- %s\n", filename))
+	}
+	f.WriteString("\n")
+	f.WriteString("## Description\n\n")
+	f.WriteString(fmt.Sprintf("%s\n\n", challenge.Data.Description))
+	f.WriteString("## Writeup")
+
+	return nil
+}