Skip to content

Commit 800fd72

Browse files
committed
init
0 parents  commit 800fd72

11 files changed

+852
-0
lines changed

challenges

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/home/argonaut0/data/projects/maple-ctf-2023

cmd/ctfd.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/ubcctf/ctfci/ctfd"
6+
)
7+
8+
func init() {
9+
rootCmd.AddCommand(cmdCTFd)
10+
cmdCTFd.AddCommand(cmdCTFdSync)
11+
cmdCTFd.AddCommand(cmdCTFdQuery)
12+
}
13+
14+
var cmdCTFd = &cobra.Command{
15+
Use: "ctfd",
16+
Short: "CTFd API client",
17+
Long: `CTFd API client`,
18+
}
19+
20+
var cmdCTFdSync = &cobra.Command{
21+
Use: "sync",
22+
Short: "synchronize challenges",
23+
Long: "synchronize challenges",
24+
Run: ctfd.Sync,
25+
}
26+
27+
var cmdCTFdQuery = &cobra.Command{
28+
Use: "query",
29+
Short: "query challenges",
30+
Long: "query challenges",
31+
Run: ctfd.Query,
32+
}

cmd/root.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
3+
*/
4+
package cmd
5+
6+
import (
7+
"fmt"
8+
"os"
9+
10+
"github.com/spf13/cobra"
11+
"github.com/spf13/viper"
12+
)
13+
14+
var cfgFile string
15+
16+
// rootCmd represents the base command when called without any subcommands
17+
var rootCmd = &cobra.Command{
18+
Use: "ctfci",
19+
Short: "API Client intended as an automation tool for CTF platforms",
20+
Long: `MB CTFCI
21+
------
22+
A tool for configuring CTF platforms.
23+
`,
24+
// Uncomment the following line if your bare application
25+
// has an action associated with it:
26+
// Run: func(cmd *cobra.Command, args []string) { },
27+
}
28+
29+
// Execute adds all child commands to the root command and sets flags appropriately.
30+
// This is called by main.main(). It only needs to happen once to the rootCmd.
31+
func Execute() {
32+
err := rootCmd.Execute()
33+
if err != nil {
34+
os.Exit(1)
35+
}
36+
}
37+
38+
func init() {
39+
cobra.OnInitialize(initConfig)
40+
41+
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ctfci.yaml)")
42+
}
43+
44+
// initConfig reads in config file and ENV variables if set.
45+
func initConfig() {
46+
if cfgFile != "" {
47+
// Use config file from the flag.
48+
viper.SetConfigFile(cfgFile)
49+
} else {
50+
// Find home directory.
51+
home, err := os.UserHomeDir()
52+
cobra.CheckErr(err)
53+
54+
// Search config in home directory with name ".ctfci" (without extension).
55+
viper.AddConfigPath(home)
56+
viper.SetConfigType("yaml")
57+
viper.SetConfigName(".ctfci")
58+
}
59+
60+
viper.AutomaticEnv() // read in environment variables that match
61+
62+
// If a config file is found, read it in.
63+
if err := viper.ReadInConfig(); err == nil {
64+
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
65+
}
66+
}

cmd/version.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
)
8+
9+
func init() {
10+
rootCmd.AddCommand(versionCmd)
11+
}
12+
13+
var versionCmd = &cobra.Command{
14+
Use: "version",
15+
Short: "Print the version",
16+
Long: `Print the version`,
17+
Run: func(cmd *cobra.Command, args []string) {
18+
fmt.Println("ctfci-v0.0.0-dev")
19+
},
20+
}

ctfci.code-workspace

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"folders": [
3+
{
4+
"path": "."
5+
},
6+
{
7+
"path": "../ctfcli"
8+
}
9+
],
10+
"settings": {}
11+
}

ctfd/challenge.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package ctfd
2+
3+
import "fmt"
4+
5+
type Challenge struct {
6+
Name string `json:"name"`
7+
Category string `json:"category"`
8+
Desc string `json:"description"`
9+
Type string `json:"type"`
10+
Value int `json:"value"`
11+
}
12+
13+
func (c *APIClient) getChallenge(id string) (interface{}, error) {
14+
obj, err := c.get(fmt.Sprintf("/api/v1/challenges/%v", id))
15+
if err != nil {
16+
return nil, err
17+
}
18+
return obj["data"], nil
19+
}
20+
21+
func (c *APIClient) getChallenges() (interface{}, error) {
22+
obj, err := c.get("/api/v1/challenges")
23+
if err != nil {
24+
return nil, err
25+
}
26+
return obj["data"], nil
27+
}
28+
29+
func (c *APIClient) getChallengeFiles(chalId string) (interface{}, error) {
30+
obj, err := c.get(fmt.Sprintf("/api/v1/challenges/%v/files", chalId))
31+
if err != nil {
32+
return nil, err
33+
}
34+
return obj["data"], nil
35+
}

ctfd/client.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package ctfd
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
)
9+
10+
type APIClient struct {
11+
Token string
12+
URL string
13+
http.Client
14+
}
15+
16+
// NewAPIClient creates an api client with auth token and CTFd url without trailing slash.
17+
func NewAPIClient(token string, url string) *APIClient {
18+
return &APIClient{
19+
Token: token,
20+
URL: url,
21+
}
22+
}
23+
24+
func (c *APIClient) request(method string, url string, reqBody io.Reader) (map[string]interface{}, error) {
25+
req, err := http.NewRequest(method, c.URL+url, reqBody)
26+
if err != nil {
27+
return nil, err
28+
}
29+
req.Header.Add("Authorization", fmt.Sprintf("Token %v", c.Token))
30+
req.Header.Add("Content-Type", "application/json")
31+
res, err := c.Do(req)
32+
if err != nil {
33+
return nil, err
34+
}
35+
defer res.Body.Close()
36+
37+
if res.StatusCode != 200 {
38+
return nil, fmt.Errorf("request failed %v", res.StatusCode)
39+
}
40+
body, err := io.ReadAll(res.Body)
41+
if err != nil {
42+
return nil, err
43+
}
44+
data := make(map[string]interface{})
45+
json.Unmarshal(body, &data)
46+
return data, nil
47+
}
48+
49+
func (c *APIClient) get(url string) (map[string]interface{}, error) {
50+
return c.request("GET", url, nil)
51+
}
52+
53+
func (c *APIClient) post(url string, body io.Reader) (map[string]interface{}, error) {
54+
return c.request("POST", url, body)
55+
}
56+
57+
func (c *APIClient) patch(url string, body io.Reader) (map[string]interface{}, error) {
58+
return c.request("PATCH", url, body)
59+
}
60+
61+
func (c *APIClient) delete(url string) (map[string]interface{}, error) {
62+
return c.request("DELETE", url, nil)
63+
}

ctfd/ctfd.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package ctfd
2+
3+
import (
4+
"os"
5+
"path"
6+
7+
"github.com/rs/zerolog"
8+
zlog "github.com/rs/zerolog/log"
9+
"github.com/spf13/cobra"
10+
"gopkg.in/yaml.v3"
11+
)
12+
13+
var categories = []string{"crypto", "web", "misc", "pwn", "rev"}
14+
15+
var log = zlog.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.InfoLevel)
16+
17+
type ChallengeInfo struct {
18+
Name string `yaml:"name"`
19+
Author string `yaml:"author"`
20+
Category string `yaml:"category"`
21+
Description string `yaml:"description"`
22+
Value int `yaml:"value"`
23+
Type string `yaml:"type"`
24+
ConnInfo string `yaml:"connection_info"`
25+
Flags []string `yaml:"flags"`
26+
Tags []string `yaml:"tags"`
27+
Files []string `yaml:"files"`
28+
Hints []string `yaml:"hints"`
29+
PreReqs []string `yaml:"requirements"`
30+
State string `yaml:"state"`
31+
}
32+
33+
func readChallengeYaml(chalPath string) (ChallengeInfo, error) {
34+
info := ChallengeInfo{}
35+
challengeYaml, err := os.ReadFile(path.Join(chalPath, "challenge.yaml"))
36+
if err != nil {
37+
return info, err
38+
}
39+
if err = yaml.Unmarshal(challengeYaml, &info); err != nil {
40+
return info, err
41+
}
42+
return info, nil
43+
}
44+
45+
func uploadDist(chalPath string) error {
46+
distPath := path.Join(chalPath, "dist")
47+
files, err := os.ReadDir(distPath)
48+
if err != nil {
49+
log.Info().Str("path", distPath).Msg("no dist found")
50+
return err
51+
}
52+
for _, file := range files {
53+
log.Info().Str("file", file.Name()).Msg("unimplemented: uploading file")
54+
}
55+
return nil
56+
}
57+
58+
// syncChallenge synchronizes a challenge to CTFd given a challenge folder
59+
func syncChallenge(chalPath string) error {
60+
info, err := readChallengeYaml(chalPath)
61+
if err != nil {
62+
return err
63+
}
64+
log.Info().Any("object", info).Msg("parsed challenge info")
65+
err = uploadDist(chalPath)
66+
if err != nil {
67+
return err
68+
}
69+
return nil
70+
}
71+
72+
func Sync(c *cobra.Command, args []string) {
73+
// Synchronize challenges
74+
for _, cat := range categories {
75+
dir := path.Join("challenges", cat)
76+
log.Info().Str("path", dir).Msg("reading directory")
77+
challenges, err := os.ReadDir(dir)
78+
if err != nil {
79+
log.Error().Err(err).Msg("could not read challenge directory")
80+
continue
81+
}
82+
for _, challenge := range challenges {
83+
chalFolder := path.Join(dir, challenge.Name())
84+
log.Debug().Str("path", chalFolder).Msg("reading directory")
85+
err := syncChallenge(chalFolder)
86+
if err != nil {
87+
log.Warn().Err(err).Str("path", chalFolder).Msg("could not sync challenge")
88+
}
89+
}
90+
}
91+
}
92+
93+
func Query(c *cobra.Command, args []string) {
94+
client := NewAPIClient("ctfd_b565c8f736af89490959917d5b932f85e379e09c5dd2b33fced654d774767f99", "https://ctf.maplebacon.org")
95+
data, err := client.getChallenges()
96+
if err != nil {
97+
log.Error().Err(err).Msg("borked")
98+
}
99+
log.Info().Any("challenges", data).Msg("Listing Challenges")
100+
}

go.mod

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module github.com/ubcctf/ctfci
2+
3+
go 1.20
4+
5+
require gopkg.in/yaml.v3 v3.0.1
6+
7+
require (
8+
github.com/fsnotify/fsnotify v1.6.0 // indirect
9+
github.com/hashicorp/hcl v1.0.0 // indirect
10+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
11+
github.com/magiconair/properties v1.8.7 // indirect
12+
github.com/mattn/go-colorable v0.1.12 // indirect
13+
github.com/mattn/go-isatty v0.0.14 // indirect
14+
github.com/mitchellh/mapstructure v1.5.0 // indirect
15+
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
16+
github.com/rs/zerolog v1.30.0 // indirect
17+
github.com/spf13/afero v1.9.5 // indirect
18+
github.com/spf13/cast v1.5.1 // indirect
19+
github.com/spf13/cobra v1.7.0 // indirect
20+
github.com/spf13/jwalterweatherman v1.1.0 // indirect
21+
github.com/spf13/pflag v1.0.5 // indirect
22+
github.com/spf13/viper v1.16.0 // indirect
23+
github.com/subosito/gotenv v1.4.2 // indirect
24+
golang.org/x/sys v0.8.0 // indirect
25+
golang.org/x/text v0.9.0 // indirect
26+
gopkg.in/ini.v1 v1.67.0 // indirect
27+
)

0 commit comments

Comments
 (0)