about summary refs log tree commit diff
path: root/main.go
blob: 466ee219f0b8b12acf57e5efc6d421a0621c14da (plain)
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"

	"gopkg.in/h2non/gentleman.v2"
    "gopkg.in/h2non/gentleman.v2/plugins/timeout"
)

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)

	// define the timeouts outrageously long, as some CTFs hosted using CTFd are incredibly inresponsive.
	cli.Use(timeout.Request(1000 * time.Second))
	cli.Use(timeout.Dial(1000 * time.Second, 2000 * time.Second))

	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 {
		fmt.Printf("Challenge unmarshal error: %d\n", res.StatusCode)
		fmt.Println(res.String())
		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
}