├── .drone.yml ├── .github └── workflows │ └── release.yml ├── .gitignore ├── Makefile ├── README.md ├── cmd ├── check.go ├── config.go ├── docker.go ├── new.go ├── root.go ├── self.go └── version.go ├── go.mod ├── go.sum ├── main.go ├── meta_example.yml ├── pkg ├── cmdutil │ ├── check.go │ ├── config.go │ ├── docker.go │ ├── file.go │ ├── self.go │ ├── wizard.go │ └── wizard_func.go ├── errutil │ └── errutil.go ├── global │ ├── const.go │ └── type.go ├── sdk │ └── github │ │ ├── download.go │ │ ├── error.go │ │ └── release.go ├── tpl │ ├── README.md │ ├── config.yml │ ├── db.json │ ├── db.sql │ ├── docker-compose.yml │ ├── flag.sh │ ├── meta.yml │ ├── start.sh │ └── template.go └── util │ ├── ask.go │ ├── func.go │ ├── logger.go │ └── version.go └── usage.md /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | name: Build 3 | 4 | 5 | steps: 6 | 7 | - name: Sync Github 8 | image: appleboy/drone-git-push 9 | settings: 10 | branch: main 11 | remote: "ssh://git@github.com/ctfhub-team/challenge_generate.git" 12 | force: false 13 | followtags: true 14 | ssh_key: 15 | from_secret: SYNC_GITHUB_KEY 16 | 17 | - name: Build All 18 | image: golang:1.19-bullseye 19 | environment: 20 | GOPROXY: "https://goproxy.cn,direct" 21 | GOPRIVATE: 22 | from_secret: GOPRIVATE 23 | GOINSECURE: 24 | from_secret: GOPRIVATE 25 | CGO_ENABLED: "0" 26 | GOOS: linux 27 | GOARCH: amd64 28 | GO111MODULE: "on" 29 | commands: 30 | - sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list 31 | - apt update && apt install -y zip 32 | - make all 33 | 34 | - name: Gitea Release 35 | image: plugins/gitea-release 36 | when: 37 | event: 38 | - tag 39 | settings: 40 | api_key: 41 | from_secret: GITEA_API_KEY 42 | base_url: 43 | from_secret: GITEA_URL 44 | files: build/* 45 | prerelease: true 46 | 47 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Build Release 5 | 6 | on: 7 | push: 8 | tags: 9 | - v* 10 | 11 | permissions: 12 | contents: write 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Set up Go 21 | uses: actions/setup-go@v4 22 | with: 23 | go-version-file: go.mod 24 | check-latest: true 25 | 26 | - name: Build 27 | env: 28 | DRONE_TAG: ${{ github.ref_name }} 29 | DRONE_COMMIT_SHA: ${{ github.sha }} 30 | run: make all 31 | 32 | - name: Create Release and Upload Release Asset 33 | uses: softprops/action-gh-release@v1 34 | if: startsWith(github.ref, 'refs/tags/') 35 | with: 36 | name: Release ${{ github.ref_name }} 37 | draft: false 38 | prerelease: false 39 | files: | 40 | build/* 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ---> Go 2 | # If you prefer the allow list template instead of the deny list, see community template: 3 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 4 | # 5 | # Binaries for programs and plugins 6 | *.exe 7 | *.exe~ 8 | *.dll 9 | *.so 10 | *.dylib 11 | 12 | # Test binary, built with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Dependency directories (remove the comment below to include it) 19 | # vendor/ 20 | 21 | # Go workspace file 22 | go.work 23 | 24 | .history 25 | build -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUILD_ENV := CGO_ENABLED=0 2 | export BUILD_TIME=$(shell date +%Y-%m-%d) 3 | export GIT_COMMIT_ID=$(wordlist 1,9,${DRONE_COMMIT_SHA}) 4 | export VERSION=${DRONE_TAG} 5 | export LDFLAGS="\ 6 | -X 'cg/pkg/util.GitCommitId=${GIT_COMMIT_ID}' \ 7 | -X 'cg/pkg/util.BuildTime=${BUILD_TIME}' \ 8 | -X 'cg/pkg/util.Version=${DRONE_TAG}' \ 9 | " 10 | 11 | TARGET_EXEC := cg 12 | 13 | .PHONY: all setup linux osx 14 | 15 | all: setup linux osx finish 16 | 17 | setup: 18 | mkdir -p build 19 | 20 | osx: 21 | ${BUILD_ENV} GOARCH=amd64 GOOS=darwin go build -v -a -ldflags ${LDFLAGS} -o build/${TARGET_EXEC}_darwin_amd64 22 | ${BUILD_ENV} GOARCH=arm64 GOOS=darwin go build -v -a -ldflags ${LDFLAGS} -o build/${TARGET_EXEC}_darwin_arm64 23 | 24 | linux: 25 | ${BUILD_ENV} GOARCH=amd64 GOOS=linux go build -v -a -ldflags ${LDFLAGS} -o build/${TARGET_EXEC}_linux_amd64 26 | 27 | clean: 28 | rm -rf build 29 | 30 | finish: 31 | cp usage.md build/ && cd build && zip -r cg_${VERSION}.zip ./ 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # challenge_generate 2 | 3 | 题目环境生成器 4 | 5 | 基于向导或是配置文件进行题目整体框架创建的工具 6 | 7 | ```bash 8 | Challenge Generate (cg) 是用于创建CTF题目环境模板的自助式生成工具. 9 | 10 | https://www.ctfhub.com/ Developed by: L1n3@CTFHub Team 11 | 12 | Usage: 13 | cg [command] 14 | 15 | Available Commands: 16 | check 检查当前题目目录及内容是否符合规范 17 | completion Generate the autocompletion script for the specified shell 18 | config 设置 cg 的配置文件 19 | docker docker相关操作 20 | help Help about any command 21 | new 创建新的题目模板 22 | version 输出 cg 的版本和更新时间 23 | 24 | Flags: 25 | -h, --help help for cg 26 | 27 | Use "cg [command] --help" for more information about a command. 28 | ``` 29 | 30 | ## 创建 31 | 32 | 使用`new`子命令进行创建,分为向导模式和文件模式 33 | ```bash 34 | n 创建新的题目模板 35 | 36 | Usage: 37 | cg new [flags] 38 | cg new [command] 39 | 40 | Aliases: 41 | new, n 42 | 43 | Available Commands: 44 | file 从预定义文件创建 45 | wizard 使用向导创建 46 | 47 | Flags: 48 | -h, --help help for new 49 | 50 | Use "cg new [command] --help" for more information about a command. 51 | ``` 52 | 53 | ## 测试 54 | 55 | 测试需要将当前目录切换至含有`docker-compose.yml`文件的目录,一般来说即为题目目录中的`environment` 目录,之后执行`docker`子命令按需处理即可 56 | 57 | ```bash 58 | d 测试已完成的镜像 59 | 60 | Usage: 61 | cg docker [flags] 62 | cg docker [command] 63 | 64 | Aliases: 65 | docker, d 66 | 67 | Available Commands: 68 | auto 依次执行 Stop -> Build -> Run -> Bash 69 | bash 执行bash进入容器 70 | build 构建镜像 71 | log 查看容器日志 72 | run 运行镜像 73 | save 导出容器tar包 74 | stop 停止镜像 75 | 76 | Flags: 77 | -h, --help help for docker 78 | 79 | Use "cg docker [command] --help" for more information about a command. 80 | ``` 81 | 82 | ## 环境目录结构 83 | 84 | ``` 85 | environment 86 | src/ 87 | files/ 88 | flag.sh 89 | db.sql 90 | start.sh 91 | Dockerfile 92 | docker-compose.yml 93 | writeup/ 94 | images/ 95 | readme.md 96 | writeup.pdf 97 | exp.py 98 | requirement.txt 99 | attachment.zip 100 | meta.yml 101 | ``` -------------------------------------------------------------------------------- /cmd/check.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "cg/pkg/cmdutil" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | func init() { 10 | RootCmd.AddCommand(CheckCmd) 11 | } 12 | 13 | var CheckCmd = &cobra.Command{ 14 | Use: "check", 15 | Short: "检查当前题目目录及内容是否符合规范", 16 | Long: "检查当前题目目录及内容是否符合规范", 17 | Run: func(cmd *cobra.Command, args []string) { 18 | cmdutil.Check() 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /cmd/config.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "cg/pkg/cmdutil" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func init() { 11 | RootCmd.AddCommand(ConfigCmd) 12 | ConfigCmd.AddCommand(ConfigSetCmd) 13 | ConfigCmd.AddCommand(ConfigGetCmd) 14 | ConfigCmd.AddCommand(ConfigCleanCmd) 15 | } 16 | 17 | var ConfigCmd = &cobra.Command{ 18 | Use: "config", 19 | Short: "设置 cg 的配置文件", 20 | Long: "设置 cg 的配置文件", 21 | Run: func(cmd *cobra.Command, args []string) { 22 | cmd.Help() 23 | os.Exit(0) 24 | }, 25 | } 26 | 27 | var ConfigSetCmd = &cobra.Command{ 28 | Use: "set", 29 | Short: "进入配置设置向导", 30 | Long: "进入配置设置向导", 31 | Run: func(cmd *cobra.Command, args []string) { 32 | cmdutil.ConfigSet() 33 | }, 34 | } 35 | 36 | var ConfigGetCmd = &cobra.Command{ 37 | Use: "get", 38 | Short: "获取当前配置", 39 | Long: "获取当前配置", 40 | Run: func(cmd *cobra.Command, args []string) { 41 | cmdutil.ConfigGet() 42 | }, 43 | } 44 | 45 | var ConfigCleanCmd = &cobra.Command{ 46 | Use: "clean", 47 | Short: "清除当前配置", 48 | Long: "清除当前配置", 49 | Run: func(cmd *cobra.Command, args []string) { 50 | cmdutil.ConfigClean() 51 | }, 52 | } 53 | -------------------------------------------------------------------------------- /cmd/docker.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "cg/pkg/cmdutil" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/gookit/color" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func init() { 13 | RootCmd.AddCommand(DockerCmd) 14 | DockerCmd.AddCommand(AutoCmd) 15 | DockerCmd.AddCommand(BuildCmd) 16 | DockerCmd.AddCommand(RunCmd) 17 | DockerCmd.AddCommand(StopCmd) 18 | DockerCmd.AddCommand(BashCmd) 19 | DockerCmd.AddCommand(LogCmd) 20 | DockerCmd.AddCommand(SaveCmd) 21 | } 22 | 23 | var Cyan = color.FgCyan.Render 24 | var Red = color.FgRed.Render 25 | 26 | func CheckDockerCompose() bool { 27 | _, err := os.Stat("docker-compose.yml") 28 | if err == nil { 29 | fmt.Println(Cyan("检测到当前目录存在 docker-compose.yml")) 30 | } else if os.IsNotExist(err) { 31 | fmt.Println(Red("当前目录不存在 docker-compose.yml")) 32 | } 33 | return err == nil 34 | } 35 | 36 | var DockerCmd = &cobra.Command{ 37 | Use: "docker", 38 | Short: "docker相关操作", 39 | Long: `docker相关操作`, 40 | Aliases: []string{"d"}, 41 | Run: func(cmd *cobra.Command, args []string) { 42 | cmd.Help() 43 | fmt.Println() 44 | CheckDockerCompose() 45 | os.Exit(0) 46 | }, 47 | } 48 | 49 | var AutoCmd = &cobra.Command{ 50 | Use: "auto", 51 | Short: "依次执行 Stop -> Build -> Run -> Bash", 52 | Long: `构建镜像`, 53 | Aliases: []string{"a"}, 54 | Run: func(cmd *cobra.Command, args []string) { 55 | fmt.Println("开始测试") 56 | if !CheckDockerCompose() { 57 | os.Exit(0) 58 | } 59 | cmdutil.Auto() 60 | }, 61 | } 62 | 63 | var BuildCmd = &cobra.Command{ 64 | Use: "build", 65 | Short: "构建镜像", 66 | Long: `构建镜像`, 67 | Aliases: []string{"b"}, 68 | Run: func(cmd *cobra.Command, args []string) { 69 | fmt.Println("开始构建镜像") 70 | if !CheckDockerCompose() { 71 | os.Exit(0) 72 | } 73 | cmdutil.Build() 74 | }, 75 | } 76 | 77 | var RunCmd = &cobra.Command{ 78 | Use: "run", 79 | Short: "运行镜像", 80 | Long: `运行镜像`, 81 | Aliases: []string{"r"}, 82 | Run: func(cmd *cobra.Command, args []string) { 83 | fmt.Println("开始运行镜像") 84 | if !CheckDockerCompose() { 85 | os.Exit(0) 86 | } 87 | cmdutil.Run() 88 | }, 89 | } 90 | 91 | var StopCmd = &cobra.Command{ 92 | Use: "stop", 93 | Short: "停止镜像", 94 | Long: `停止镜像`, 95 | Aliases: []string{"s"}, 96 | Run: func(cmd *cobra.Command, args []string) { 97 | fmt.Println("开始停止镜像") 98 | if !CheckDockerCompose() { 99 | os.Exit(0) 100 | } 101 | cmdutil.Stop() 102 | }, 103 | } 104 | 105 | var BashCmd = &cobra.Command{ 106 | Use: "bash", 107 | Short: "执行bash进入容器", 108 | Long: `执行bash进入容器`, 109 | Run: func(cmd *cobra.Command, args []string) { 110 | fmt.Println("开始进入容器") 111 | if !CheckDockerCompose() { 112 | os.Exit(0) 113 | } 114 | cmdutil.Bash() 115 | }, 116 | } 117 | 118 | var LogCmd = &cobra.Command{ 119 | Use: "log", 120 | Short: "查看容器日志", 121 | Long: `查看容器日志`, 122 | Run: func(cmd *cobra.Command, args []string) { 123 | fmt.Println("查看容器日志") 124 | if !CheckDockerCompose() { 125 | os.Exit(0) 126 | } 127 | cmdutil.Log() 128 | }, 129 | } 130 | 131 | var SaveCmd = &cobra.Command{ 132 | Use: "save", 133 | Short: "导出容器tar包", 134 | Long: `导出容器tar包`, 135 | Run: func(cmd *cobra.Command, args []string) { 136 | fmt.Println("导出容器tar包") 137 | if !CheckDockerCompose() { 138 | os.Exit(0) 139 | } 140 | cmdutil.Save() 141 | }, 142 | } 143 | -------------------------------------------------------------------------------- /cmd/new.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "cg/pkg/cmdutil" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func init() { 11 | RootCmd.AddCommand(newCmd) 12 | newCmd.AddCommand(WizardCmd) 13 | newCmd.AddCommand(FileCmd) 14 | } 15 | 16 | var newCmd = &cobra.Command{ 17 | Use: "new", 18 | Short: "创建新的题目模板", 19 | Long: `创建新的题目模板`, 20 | Aliases: []string{"n"}, 21 | Run: func(cmd *cobra.Command, args []string) { 22 | cmd.Help() 23 | os.Exit(0) 24 | }, 25 | } 26 | 27 | var WizardCmd = &cobra.Command{ 28 | Use: "wizard", 29 | Short: "使用向导创建", 30 | Long: `使用向导创建`, 31 | Aliases: []string{"w"}, 32 | Run: func(cmd *cobra.Command, args []string) { 33 | cmdutil.Wizard() 34 | }, 35 | } 36 | 37 | var FileCmd = &cobra.Command{ 38 | Use: "file", 39 | Short: "从预定义文件创建", 40 | Long: `从预定义文件创建`, 41 | Aliases: []string{"f"}, 42 | Run: func(cmd *cobra.Command, args []string) { 43 | cmdutil.File() 44 | }, 45 | } 46 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | cc "github.com/ivanpirog/coloredcobra" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var RootCmd = &cobra.Command{ 11 | Use: "cg", 12 | Short: "cg 是用于创建CTF题目环境模板的自助式生成工具", 13 | Long: ` 14 | CTF Generate (cg) 是用于创建CTF题目环境模板的自助式生成工具. 15 | 16 | Developed by: L1n3 17 | `, 18 | } 19 | 20 | // func init() { 21 | // RootCmd.CompletionOptions.DisableDefaultCmd = true 22 | // } 23 | 24 | func Execute() { 25 | cc.Init(&cc.Config{ 26 | RootCmd: RootCmd, 27 | Headings: cc.HiGreen + cc.Underline, 28 | Commands: cc.Cyan + cc.Bold, 29 | Example: cc.Italic, 30 | ExecName: cc.Bold, 31 | Flags: cc.Cyan + cc.Bold, 32 | NoExtraNewlines: true, 33 | NoBottomNewline: true, 34 | }) 35 | err := RootCmd.Execute() 36 | if err != nil { 37 | os.Exit(1) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cmd/self.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "cg/pkg/cmdutil" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var Proxy string 11 | 12 | func init() { 13 | RootCmd.AddCommand(SelfCmd) 14 | SelfCmd.AddCommand(UpgradeCmd) 15 | SelfCmd.AddCommand(SelfCheckCmd) 16 | SelfCmd.PersistentFlags().StringVarP(&Proxy, "proxy", "p", "", "Socks5 Proxy example 1.2.3.4:5678") 17 | } 18 | 19 | var SelfCmd = &cobra.Command{ 20 | Use: "self", 21 | Short: "升级", 22 | Long: "升级", 23 | Run: func(cmd *cobra.Command, args []string) { 24 | cmd.Help() 25 | os.Exit(0) 26 | }, 27 | } 28 | 29 | var UpgradeCmd = &cobra.Command{ 30 | Use: "upgrade", 31 | Short: "自动升级", 32 | Long: "自动升级", 33 | Run: func(cmd *cobra.Command, args []string) { 34 | cmdutil.Upgrade(Proxy) 35 | }, 36 | } 37 | 38 | var SelfCheckCmd = &cobra.Command{ 39 | Use: "check", 40 | Short: "检查新版本", 41 | Long: "检查新版本", 42 | Run: func(cmd *cobra.Command, args []string) { 43 | cmdutil.SelfCheck(Proxy) 44 | }, 45 | } 46 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "cg/pkg/util" 5 | "fmt" 6 | 7 | "github.com/gookit/color" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func init() { 12 | RootCmd.AddCommand(versionCmd) 13 | } 14 | 15 | var versionCmd = &cobra.Command{ 16 | Use: "version", 17 | Short: "输出 cg 的版本和更新时间", 18 | Long: "输出 cg 的版本和更新时间", 19 | Run: func(cmd *cobra.Command, args []string) { 20 | Cyan := color.FgCyan.Render 21 | fmt.Println("Version: ", Cyan(util.Version)) 22 | fmt.Println("BuildTime: ", Cyan(util.BuildTime)) 23 | fmt.Println("GitCommit: ", Cyan(util.GitCommitId)) 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module cg 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/AlecAivazis/survey/v2 v2.3.7 7 | github.com/Masterminds/semver/v3 v3.2.1 8 | github.com/gookit/color v1.5.3 9 | github.com/ivanpirog/coloredcobra v1.0.1 10 | github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 11 | github.com/schollz/progressbar/v3 v3.12.1 12 | github.com/sirupsen/logrus v1.9.3 13 | github.com/spf13/cobra v1.7.0 14 | github.com/voidint/go-update v1.0.0 15 | github.com/x-cray/logrus-prefixed-formatter v0.5.2 16 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 17 | golang.org/x/net v0.10.0 18 | gopkg.in/yaml.v2 v2.4.0 19 | ) 20 | 21 | require ( 22 | github.com/fatih/color v1.13.0 // indirect 23 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 24 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 25 | github.com/mattn/go-colorable v0.1.13 // indirect 26 | github.com/mattn/go-isatty v0.0.16 // indirect 27 | github.com/mattn/go-runewidth v0.0.14 // indirect 28 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect 29 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect 30 | github.com/onsi/ginkgo v1.16.5 // indirect 31 | github.com/onsi/gomega v1.27.8 // indirect 32 | github.com/rivo/uniseg v0.4.3 // indirect 33 | github.com/spf13/pflag v1.0.5 // indirect 34 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect 35 | golang.org/x/crypto v0.10.0 // indirect 36 | golang.org/x/sys v0.12.0 // indirect 37 | golang.org/x/term v0.9.0 // indirect 38 | golang.org/x/text v0.10.0 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= 2 | github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= 3 | github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= 4 | github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= 5 | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= 6 | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 8 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 9 | github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= 10 | github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 15 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 16 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 17 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 18 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 19 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 20 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 21 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 22 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 23 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 24 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 25 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 26 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 27 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 28 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 29 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 31 | github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE= 32 | github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE= 33 | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= 34 | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= 35 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 36 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 37 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 38 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 39 | github.com/ivanpirog/coloredcobra v1.0.1 h1:aURSdEmlR90/tSiWS0dMjdwOvCVUeYLfltLfbgNxrN4= 40 | github.com/ivanpirog/coloredcobra v1.0.1/go.mod h1:iho4nEKcnwZFiniGSdcgdvRgZNjxm+h20acv8vqmN6Q= 41 | github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg= 42 | github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= 43 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 44 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 45 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 46 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 47 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 48 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 49 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 50 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 51 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 52 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= 53 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 54 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= 55 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 56 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 57 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= 58 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 59 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= 60 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= 61 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 62 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 63 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 64 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 65 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 66 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 67 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 68 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 69 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 70 | github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= 71 | github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= 72 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 73 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 74 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 75 | github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 76 | github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= 77 | github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 78 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 79 | github.com/schollz/progressbar/v3 v3.12.1 h1:JAhtIrLWAn6/p7i82SrpSG3fgAwlAxi+Sy12r4AzBvQ= 80 | github.com/schollz/progressbar/v3 v3.12.1/go.mod h1:g7QSuwyGpqCjVQPFZXA31MSxtrhka9Y9LMdF+XT77/Y= 81 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 82 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 83 | github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= 84 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 85 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 86 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 87 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 88 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 89 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 90 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 91 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 92 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 93 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 94 | github.com/voidint/go-update v1.0.0 h1:mNvquoaMsX5+5ZLSAMn0/YW6j4wM9mJh06x4GEQoqkQ= 95 | github.com/voidint/go-update v1.0.0/go.mod h1:vIGRjczyOkqy0I2te0DerJgzvUwaj0nA9xUgBKCY/Tc= 96 | github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= 97 | github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= 98 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= 99 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= 100 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 101 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 102 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 103 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 104 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 105 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 106 | golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= 107 | golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= 108 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= 109 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 110 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 111 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 112 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 113 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 114 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 115 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 116 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 117 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 118 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 119 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 120 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 121 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 122 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 123 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 124 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 125 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 126 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 127 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 128 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 129 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 130 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 131 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 132 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 133 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 134 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 135 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 136 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 137 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 138 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 139 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 140 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 141 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 142 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 143 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 144 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 145 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 146 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 147 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 148 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 149 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 150 | golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= 151 | golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= 152 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 153 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 154 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 155 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 156 | golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= 157 | golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 158 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 159 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 160 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 161 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 162 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 163 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 164 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 165 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 166 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 167 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 168 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 169 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 170 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 171 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 172 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 173 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 174 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 175 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 176 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 177 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 178 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 179 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 180 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 181 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 182 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 183 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 184 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 185 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "cg/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /meta_example.yml: -------------------------------------------------------------------------------- 1 | author: 2 | name: l1n3 3 | contact: yw9381@163.com 4 | task: 5 | name: challenge_web_2022_hitcon_rce 6 | type: Web 7 | description: aasasdsadas 8 | level: 签到 9 | flag: 10 | hints: 11 | - asdasd 12 | - asdas 13 | - asdad 14 | challenge: 15 | name: rce 16 | refer: 2022-HitCon-Web-rce 17 | tags: 18 | - web 19 | - 2022 20 | - hitcon 21 | -------------------------------------------------------------------------------- /pkg/cmdutil/check.go: -------------------------------------------------------------------------------- 1 | package cmdutil 2 | 3 | import "fmt" 4 | 5 | // TODO 6 | func Check() { 7 | fmt.Print("todo!!!") 8 | } 9 | -------------------------------------------------------------------------------- /pkg/cmdutil/config.go: -------------------------------------------------------------------------------- 1 | package cmdutil 2 | 3 | import ( 4 | "cg/pkg/global" 5 | "cg/pkg/tpl" 6 | "cg/pkg/util" 7 | "fmt" 8 | "os" 9 | 10 | "github.com/gookit/color" 11 | "gopkg.in/yaml.v2" 12 | ) 13 | 14 | func ConfigSet() { 15 | config := global.Config{} 16 | _ = yaml.Unmarshal(tpl.Config, &config) 17 | // 修改内容 18 | config.Author = util.InputLine("请输入你的ID: ") 19 | config.Contact = util.InputLine("请输入你的邮箱: ") 20 | config.RegistryUrl = util.SelectOne("请选择您要使用的默认镜像源", global.Registry) 21 | // 写入文件 22 | writeData, _ := yaml.Marshal(&config) 23 | UserHomeDir, _ := os.UserHomeDir() 24 | os.MkdirAll(UserHomeDir+"/.config/cg/", os.ModePerm) 25 | util.WriteFile(UserHomeDir+"/.config/cg/config.yaml", string(writeData), 0644) 26 | } 27 | 28 | func ConfigGet() { 29 | config := global.Config{} 30 | UserHomeDir, _ := os.UserHomeDir() 31 | data, err := util.ReadFileByte(UserHomeDir + "/.config/cg/config.yaml") 32 | if err != nil { 33 | fmt.Println("读取配置文件失败", err) 34 | fmt.Println("请确保已经设置配置") 35 | return 36 | } 37 | _ = yaml.Unmarshal(data, &config) 38 | Cyan := color.FgCyan.Render 39 | fmt.Println("制作者 ID: " + Cyan(config.Author)) 40 | fmt.Println("制作者邮箱: ", Cyan(config.Contact)) 41 | fmt.Println("默认镜像源地址: ", Cyan(config.RegistryUrl)) 42 | } 43 | 44 | func ConfigClean() { 45 | UserHomeDir, _ := os.UserHomeDir() 46 | err := os.Remove(UserHomeDir + "/.config/cg/config.yaml") 47 | if err != nil { 48 | fmt.Println("清除错误: ", err) 49 | return 50 | } 51 | fmt.Println("配置已清除") 52 | } 53 | -------------------------------------------------------------------------------- /pkg/cmdutil/docker.go: -------------------------------------------------------------------------------- 1 | package cmdutil 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | "os/exec" 10 | ) 11 | 12 | func exec_cmd(command string) { 13 | cmd := exec.Command("/bin/bash", "-c", command) 14 | cmd.Env = append(cmd.Env, "COMPOSE_DOCKER_CLI_BUILD=1", "DOCKER_BUILDKIT=1") 15 | stderr, err := cmd.StderrPipe() 16 | if err != nil { 17 | log.Fatalln("stderr pipe ", err) 18 | } 19 | defer stderr.Close() 20 | if err := cmd.Start(); err != nil { 21 | log.Fatalln("start ", err) 22 | } 23 | go func() { 24 | serr := bufio.NewReader(stderr) 25 | for { 26 | line, _, err2 := serr.ReadLine() 27 | if err2 != nil || io.EOF == err2 { 28 | break 29 | } 30 | fmt.Println(string(line)) 31 | } 32 | }() 33 | if err := cmd.Wait(); err != nil { 34 | log.Fatalln("wait ", err) 35 | } 36 | } 37 | 38 | // Deprecated 有 Bug 不建议使用 39 | func exec_cmd_attach(command string) { 40 | cmd := exec.Command("/bin/bash", "-c", command) 41 | log.Println(cmd.Args) 42 | cmd.Env = append(cmd.Env, "COMPOSE_DOCKER_CLI_BUILD=1", "DOCKER_BUILDKIT=1", "COMPOSE_INTERACTIVE_NO_CLI=1") 43 | stdin, err := cmd.StdinPipe() 44 | if err != nil { 45 | log.Fatalln("stdin pipe ", err) 46 | } 47 | defer stdin.Close() 48 | stdout, err := cmd.StdoutPipe() 49 | if err != nil { 50 | log.Fatalln("stdout pipe ", err) 51 | } 52 | defer stdout.Close() 53 | if err := cmd.Start(); err != nil { 54 | log.Fatalln("start ", err) 55 | } 56 | go func() { 57 | out := bufio.NewReader(stdout) 58 | for { 59 | line, _, err2 := out.ReadLine() 60 | if err2 != nil || io.EOF == err2 { 61 | break 62 | } 63 | fmt.Println(string(line)) 64 | } 65 | }() 66 | 67 | io.Copy(stdin, bufio.NewReader(os.Stdin)) 68 | 69 | if err := cmd.Wait(); err != nil { 70 | log.Fatalln("wait ", err) 71 | } 72 | } 73 | 74 | func Auto() { 75 | Stop() 76 | Build() 77 | Run() 78 | Bash() 79 | } 80 | 81 | func Build() { 82 | exec_cmd("docker-compose build") 83 | } 84 | 85 | func Run() { 86 | exec_cmd("docker-compose up -d") 87 | } 88 | 89 | func Stop() { 90 | exec_cmd("docker-compose down") 91 | } 92 | 93 | func Bash() { 94 | exec_cmd_attach("docker-compose exec challenge bash") 95 | } 96 | 97 | func Log() { 98 | exec_cmd("docker-compose logs") 99 | } 100 | 101 | func Save() { 102 | fmt.Printf("TODO") 103 | // exec_cmd("docker-compose save") 104 | } 105 | -------------------------------------------------------------------------------- /pkg/cmdutil/file.go: -------------------------------------------------------------------------------- 1 | package cmdutil 2 | 3 | import "fmt" 4 | 5 | // TODO 从配置文件生成题目模板 6 | func File() { 7 | fmt.Println("todo") 8 | } 9 | -------------------------------------------------------------------------------- /pkg/cmdutil/self.go: -------------------------------------------------------------------------------- 1 | package cmdutil 2 | 3 | import ( 4 | "cg/pkg/util" 5 | "fmt" 6 | "os" 7 | "runtime" 8 | "strings" 9 | 10 | "cg/pkg/sdk/github" 11 | 12 | "github.com/Masterminds/semver/v3" 13 | ) 14 | 15 | const ( 16 | ORG = "ctfhub-team" 17 | REPO = "challenge_generate" 18 | ) 19 | 20 | func Upgrade(proxy string) { 21 | up := github.NewReleaseUpdater() 22 | latest, yes, err := up.CheckForUpdates(semver.MustParse(util.Version), ORG, REPO) 23 | if err != nil { 24 | os.Exit(1) 25 | } 26 | if !yes { 27 | fmt.Println("当前cg为最新版 " + util.Version) 28 | } else { 29 | fmt.Printf("发现新版本 cg %s ", latest.TagName) 30 | // 应用更新 31 | if err = up.Apply(latest, findAsset, proxy); err != nil { 32 | fmt.Println(err) 33 | os.Exit(1) 34 | } 35 | fmt.Println("升级完成") 36 | } 37 | } 38 | 39 | func SelfCheck(proxy string) { 40 | up := github.NewReleaseUpdater() 41 | latest, _, err := up.CheckForUpdates(semver.MustParse(util.Version), ORG, REPO) 42 | if err != nil { 43 | os.Exit(1) 44 | } 45 | fmt.Println("云端cg版本 " + latest.TagName) 46 | fmt.Println("本地cg版本 " + util.Version) 47 | } 48 | func findAsset(items []github.Asset) (idx int) { 49 | suffix := fmt.Sprintf("cg_%s_%s", runtime.GOOS, runtime.GOARCH) 50 | fmt.Printf("开始下载 %s\n", suffix) 51 | for i := range items { 52 | if strings.HasSuffix(items[i].BrowserDownloadURL, suffix) { 53 | return i 54 | } 55 | } 56 | return -1 57 | } 58 | -------------------------------------------------------------------------------- /pkg/cmdutil/wizard.go: -------------------------------------------------------------------------------- 1 | package cmdutil 2 | 3 | import ( 4 | "cg/pkg/global" 5 | "cg/pkg/util" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/gookit/color" 10 | "gopkg.in/yaml.v2" 11 | ) 12 | 13 | /** 14 | * @Description: 生成题目模板 15 | * @param baseImageName 题目基础镜像名称 16 | * @param challengeType 题目类型 17 | * @param challengeName 题目名称 18 | * @param language 题目语言 19 | * @param hasDB 是否需要数据库 20 | */ 21 | func Generate(challengeInfo map[string]string) { 22 | // flag位置确认 23 | challengeInfo["need_flag"] = util.SelectArray("是否需要单独处理flag位置(flag.sh)", []string{"否", "是"}) 24 | challengeInfo["need_start"] = util.SelectArray("是否需要单独处理部分服务启动(start.sh)", []string{"否", "是"}) 25 | // 题目等级选择 26 | challengeInfo["level"] = util.SelectArray("此题目难度为", global.Level) 27 | 28 | // // DEBUG 测试输出 29 | // s, _ := json.MarshalIndent(challengeInfo, "", " ") 30 | // fmt.Printf(string(s)) 31 | 32 | // 二次确认 33 | confirm := util.SelectArray("确认创建题目 "+challengeInfo["challenge_name"]+" ?", []string{"确认", "取消"}) 34 | if confirm == "取消" { 35 | os.Exit(0) 36 | } 37 | 38 | // 创建题目模板目录 39 | os.Mkdir(challengeInfo["challenge_name"], 0755) 40 | os.Chdir(challengeInfo["challenge_name"]) 41 | 42 | // 创建文件夹 43 | dirTree := []string{ 44 | "environment/src/", 45 | "environment/files/", 46 | "writeup/", 47 | } 48 | for _, path := range dirTree { 49 | os.MkdirAll(path, os.ModePerm) 50 | } 51 | // 写入dockerfile 52 | GenerateDockerFile(challengeInfo) 53 | 54 | // 写入docker-compose.yml 55 | GenerateDockerCompose(challengeInfo) 56 | 57 | // 写入meta.yml 58 | GenerateMeta(challengeInfo) 59 | 60 | // 写入数据库 61 | GenerateDB(challengeInfo) 62 | 63 | // 写入flag.sh处理 64 | GenerateFlag(challengeInfo) 65 | 66 | // 写入start.sh处理 67 | GenerateStart(challengeInfo) 68 | 69 | // 写入README.md 70 | GenerateReadme(challengeInfo) 71 | 72 | // 输出成功提示 73 | fmt.Println("") 74 | fmt.Println(challengeInfo["challenge_name"] + " 创建成功,请按如下步骤依次操作:") 75 | fmt.Println("1. 初始化Git仓库") 76 | fmt.Println("2. 编辑 " + challengeInfo["challenge_name"] + "/meta.yml 文件修改题目信息") 77 | fmt.Println("3. 进入 " + challengeInfo["challenge_name"] + "/environment 目录进行测试") 78 | fmt.Println("4. 进入 " + challengeInfo["challenge_name"] + "/writeup 目录补全WP及Exp") 79 | fmt.Println("5. 一切完成后推送至Git仓库") 80 | } 81 | 82 | func Wizard() { 83 | challengeInfo := map[string]string{ 84 | "type": "", // 题目类型 85 | "language": "", // 使用语言 86 | "language_version": "", // 语言版本,HTML题目留空 87 | "webserver": "", // Web服务器,非Web题目留空 88 | "db": "", // 数据库,无数据库留空 89 | "pwn_arch": "", // Pwn题目架构,非Pwn题目留空 90 | "pwn_server": "", // Pwn题目服务器,非Pwn题目留空 91 | "need_flag": "", // 是否需要flag.sh 92 | "need_start": "", // 是否需要start.sh 93 | "level": "", // 题目等级 94 | "base_image_name": "", // 基础镜像名称 95 | "base_registry": "", // 基础镜像源地址 96 | "challenge_name": "", // 题目镜像名称 97 | } 98 | // 判断配置中是否已设置默认镜像源 99 | config := global.Config{} 100 | UserHomeDir, _ := os.UserHomeDir() 101 | data, err := util.ReadFileByte(UserHomeDir + "/.config/cg/config.yaml") 102 | if err != nil { 103 | fmt.Println("未检测到配置文件,建议先设置默认镜像源") 104 | registry := util.SelectOne("请选择您要使用的镜像源", global.Registry) 105 | challengeInfo["base_registry"] = registry + "/" 106 | } else { 107 | _ = yaml.Unmarshal(data, &config) 108 | fmt.Println("检测到配置文件,将使用配置文件中的镜像源") 109 | fmt.Println("镜像源地址:" + color.FgCyan.Render(config.RegistryUrl)) 110 | fmt.Println() 111 | challengeInfo["base_registry"] = config.RegistryUrl + "/" 112 | } 113 | color.Green.Println("如选择错误,请按 Ctrl+C 终止程序,然后重新执行向导") 114 | challengeInfo["type"] = util.SelectOne("请选择您要创建的题目类型", global.ChallengeType) 115 | challengeInfo["base_image_name"] = challengeInfo["type"] 116 | switch challengeInfo["type"] { 117 | case "web": 118 | challengeInfo = WizardWeb(challengeInfo) 119 | case "pwn": 120 | challengeInfo = WizardPwn(challengeInfo) 121 | case "misc": 122 | challengeInfo = WizardSocket(challengeInfo) 123 | } 124 | color.Cyan.Println("题目名称应当全为小写,如为中文名称则使用拼音,格式如下") 125 | color.Cyan.Println("challenge_年份_所属比赛简写_分类_题目名称") 126 | fmt.Println("") 127 | color.Cyan.Println("例1 2021年 N1CTF Web类 babysqli,则为 challenge_2021_n1ctf_web_baysqli") 128 | color.Cyan.Println("例2 2019年 SCTF Pwn类 babyheap,则为 challenge_2019_sctf_pwn_bayheap") 129 | // 不断获取输入直到有内容 130 | for { 131 | challengeInfo["challenge_name"] = util.InputLine("请输入您要创建的题目镜像名称") 132 | if len(challengeInfo["challenge_name"]) != 0 { 133 | break 134 | } 135 | color.Red.Println("你未输入题目名称,请重新输入") 136 | } 137 | // 创建 138 | Generate(challengeInfo) 139 | } 140 | 141 | func WizardWeb(challengeInfo map[string]string) map[string]string { 142 | // 判断语言 143 | challengeInfo["language"] = util.SelectOne("请选择您要使用的语言", global.Language) 144 | // 判断语言版本 145 | if challengeInfo["language"] != "html" { 146 | languageVersion := []string{} 147 | switch challengeInfo["language"] { 148 | case "php": 149 | languageVersion = global.PHPVersion 150 | case "python": 151 | languageVersion = global.PythonVersion 152 | case "nodejs": 153 | languageVersion = global.NodeJSVersion 154 | case "java": 155 | languageVersion = global.JavaVersion 156 | case "ruby": 157 | languageVersion = global.RubyVersion 158 | } 159 | challengeInfo["language_version"] = util.SelectArray("请选择您要使用的版本", languageVersion) 160 | } 161 | // 判断Web服务器 162 | switch challengeInfo["language"] { 163 | case "php", "html": 164 | challengeInfo["webserver"] = util.SelectOne("请选择您要使用的Web服务器", global.PHPWebServer) 165 | case "java": 166 | challengeInfo["webserver"] = util.SelectOne("请选择您要使用的Web服务器", global.JavaServer) 167 | case "python": 168 | challengeInfo["webserver"] = util.SelectOne("请选择您要使用的托管方式", global.PythonWebServer) 169 | } 170 | // 判断数据库 171 | if challengeInfo["language"] != "html" { 172 | challengeInfo["db"] = util.SelectOne("是否需要数据库", global.DBType) 173 | } 174 | // 拼接镜像名称 175 | baseImageName := "" 176 | switch challengeInfo["language"] { 177 | case "php", "python", "java", "html": 178 | baseImageName += "_" + challengeInfo["webserver"] 179 | case "nodejs", "ruby": 180 | // 不需要webserver 181 | } 182 | if challengeInfo["db"] != "" { 183 | baseImageName += "_" + challengeInfo["db"] 184 | } 185 | if challengeInfo["language"] != "html" { 186 | baseImageName += "_" + challengeInfo["language"] 187 | baseImageName += "_" + challengeInfo["language_version"] 188 | } 189 | challengeInfo["base_image_name"] += baseImageName 190 | return challengeInfo 191 | } 192 | 193 | func WizardPwn(challengeInfo map[string]string) map[string]string { 194 | challengeInfo["pwn_server"] = util.SelectOne("请选择您期望的启动方式", global.PwnServer) 195 | challengeInfo["pwn_arch"] = util.SelectOne("请选择您要使用的架构", global.PwnArch) 196 | // 拼接镜像名称 197 | baseImageName := "" 198 | switch challengeInfo["pwn_arch"] { 199 | case "": 200 | baseImageName += "" 201 | case "kernel", "qemu": 202 | baseImageName += "_" + challengeInfo["pwn_arch"] 203 | } 204 | baseImageName += "_" + challengeInfo["pwn_server"] 205 | challengeInfo["base_image_name"] += baseImageName 206 | return challengeInfo 207 | } 208 | 209 | func WizardSocket(challengeInfo map[string]string) map[string]string { 210 | selectLanguage := global.Language 211 | delete(selectLanguage, "HTML") 212 | delete(selectLanguage, "PHP") 213 | delete(selectLanguage, "Ruby") 214 | challengeInfo["language"] = util.SelectOne("请选择您要使用的语言", selectLanguage) 215 | challengeInfo["db"] = util.SelectOne("是否需要数据库", global.DBType) 216 | // 判断语言版本 217 | languageVersion := []string{} 218 | switch challengeInfo["language"] { 219 | case "python": 220 | languageVersion = global.PythonVersion 221 | case "nodejs": 222 | languageVersion = global.NodeJSVersion 223 | case "java": 224 | languageVersion = global.JavaVersion 225 | } 226 | challengeInfo["language_version"] = util.SelectArray("请选择您要使用的版本", languageVersion) 227 | // 拼接镜像名称 228 | baseImageName := "" 229 | if challengeInfo["db"] != "" { 230 | baseImageName += "_" + challengeInfo["db"] 231 | } 232 | baseImageName += "_" + challengeInfo["language"] 233 | baseImageName += "_" + challengeInfo["language_version"] 234 | // version := util.SelectOne("请选择您要使用的版本", global.SelectVersion[language]) 235 | // hasDB = util.SelectOne("是否需要数据库", []string{"无", "MySQL", "MongoDB"}) 236 | // baseImageName += "_misc_socat" 237 | // if hasDB != "无" { 238 | // baseImageName += "_" + strings.ToLower(hasDB) 239 | // hasDB = strings.ToLower(hasDB) 240 | // } 241 | // baseImageName += strings.ToLower(version) 242 | challengeInfo["base_image_name"] += baseImageName 243 | return challengeInfo 244 | } 245 | -------------------------------------------------------------------------------- /pkg/cmdutil/wizard_func.go: -------------------------------------------------------------------------------- 1 | package cmdutil 2 | 3 | import ( 4 | "cg/pkg/global" 5 | "cg/pkg/tpl" 6 | "cg/pkg/util" 7 | "os" 8 | "strings" 9 | 10 | "gopkg.in/yaml.v2" 11 | ) 12 | 13 | func GetServicePort(challengeType string) string { 14 | switch challengeType { 15 | case "web": 16 | return "80" 17 | case "pwn": 18 | return "10000" 19 | case "misc": 20 | return "10000" 21 | case "web_access": 22 | return "10800" 23 | default: 24 | return "9999" 25 | } 26 | } 27 | 28 | /** 29 | * @Description: 生成 Dockerfile 30 | * @param challengeInfo 题目信息 31 | */ 32 | func GenerateDockerFile(challengeInfo map[string]string) { 33 | baseImageName := challengeInfo["base_registry"] + challengeInfo["base_image_name"] 34 | // Dockerfile 35 | dockerfile := "FROM " + baseImageName + "\n" 36 | dockerfile += "MAINTAINER CTFHub Team\n" 37 | dockerfile += "\n" 38 | // 根据类型判断题目内容放置位置 39 | switch challengeInfo["type"] { 40 | case "web": 41 | switch challengeInfo["language"] { 42 | case "php", "html": 43 | dockerfile += "COPY ./src/ /var/www/html/\n" 44 | default: 45 | dockerfile += "COPY ./src/ /app/\n" 46 | } 47 | case "pwn": 48 | dockerfile += "COPY ./src/pwn /pwn\n" 49 | case "misc": 50 | dockerfile += "COPY ./src/ /app/\n" 51 | } 52 | dockerfile += "\n" 53 | dockerfile += "EXPOSE " + GetServicePort(challengeInfo["type"]) + "\n" 54 | util.WriteFile("environment/Dockerfile", dockerfile, 0644) 55 | } 56 | 57 | /** 58 | * @Description: 生成 docker-compose.yml 59 | * @param challengeInfo 题目信息 60 | */ 61 | func GenerateDockerCompose(challengeInfo map[string]string) { 62 | servicePort := "" 63 | accessPort := "" 64 | servicePort = GetServicePort(challengeInfo["type"]) 65 | if challengeInfo["type"] == "web" { 66 | accessPort = GetServicePort("web_access") 67 | } else { 68 | accessPort = servicePort 69 | } 70 | dockerCompose := global.DockerCompsoe{} 71 | _ = yaml.Unmarshal(tpl.DockerCompose, &dockerCompose) 72 | // 修改内容 73 | dockerCompose.Version = "3" 74 | dockerCompose.Services.Challenge.Image = challengeInfo["challenge_name"] 75 | dockerCompose.Services.Challenge.Ports = []string{accessPort + ":" + servicePort} 76 | dockerCompose.Services.Challenge.Environment = []string{ 77 | "FLAG=ctfhub{test_flag}", 78 | "DOMAIN=test.sandbox.ctfhub.com", 79 | } 80 | // 写入文件 81 | writeData, _ := yaml.Marshal(&dockerCompose) 82 | util.WriteFile("environment/docker-compose.yml", string(writeData), 0644) 83 | } 84 | 85 | /** 86 | * @Description: 生成 meta.yml 87 | * @param challengeInfo 题目信息 88 | */ 89 | func GenerateMeta(challengeInfo map[string]string) { 90 | // 读取配置 91 | config := global.Config{} 92 | UserHomeDir, _ := os.UserHomeDir() 93 | data, _ := util.ReadFileByte(UserHomeDir + "/.config/cg/config.yaml") 94 | _ = yaml.Unmarshal(data, &config) 95 | // 载入模板 96 | meta := global.Meta{} 97 | _ = yaml.Unmarshal(tpl.Meta, &meta) 98 | 99 | // 修改内容 100 | meta.Author.Name = config.Author 101 | meta.Author.Contact = config.Contact 102 | meta.Task.Name = challengeInfo["challenge_name"] 103 | meta.Task.Type = challengeInfo["type"] 104 | meta.Task.Level = challengeInfo["level"] 105 | // 写入文件 106 | writeData, _ := yaml.Marshal(&meta) 107 | util.WriteFile("meta.yml", string(writeData), 0644) 108 | } 109 | 110 | /** 111 | * @Description: 生成 flag.sh 112 | * @param challengeInfo 题目信息 113 | */ 114 | func GenerateFlag(challengeInfo map[string]string) { 115 | if challengeInfo["need_flag"] == "是" { 116 | util.WriteFile("environment/files/flag.sh", string(tpl.Flag), 0755) 117 | } 118 | } 119 | 120 | /** 121 | * @Description: 生成 start.sh 122 | * @param challengeInfo 题目信息 123 | */ 124 | func GenerateStart(challengeInfo map[string]string) { 125 | if challengeInfo["need_start"] == "是" { 126 | util.WriteFile("environment/files/start.sh", string(tpl.Start), 0755) 127 | } 128 | } 129 | 130 | /** 131 | * @Description: 生成 db.sql/db.json 132 | * @param challengeInfo 题目信息 133 | */ 134 | func GenerateDB(challengeInfo map[string]string) { 135 | switch challengeInfo["db_type"] { 136 | case "mysql": 137 | util.WriteFile("environment/files/db.sql", string(tpl.DB_SQL), 0644) 138 | case "mongodb": 139 | util.WriteFile("environment/files/db.json", string(tpl.DB_JSON), 0644) 140 | } 141 | } 142 | 143 | /** 144 | * @Description: 生成 README.md 145 | * @param challengeInfo 题目信息 146 | */ 147 | func GenerateReadme(challengeInfo map[string]string) { 148 | // 读取meta 149 | meta := global.Meta{} 150 | _ = yaml.Unmarshal(tpl.Meta, &meta) 151 | readme := string(tpl.Readme) 152 | readme = strings.Replace(readme, "CHALLENGE_NAME", meta.Challenge.Name, -1) 153 | readme = strings.Replace(readme, "CHALLENGE_REFER", meta.Challenge.Refer, -1) 154 | readme = strings.Replace(readme, "AUTHOR", meta.Author.Name, -1) 155 | readme = strings.Replace(readme, "EMAIL", meta.Author.Contact, -1) 156 | readme = strings.Replace(readme, "TASK_NAME", meta.Task.Name, -1) 157 | readme = strings.Replace(readme, "TASK_TYPE", meta.Task.Type, -1) 158 | readme = strings.Replace(readme, "TASK_LEVEL", meta.Task.Level, -1) 159 | if meta.Task.Flag == "" { 160 | readme = strings.Replace(readme, "TASK_FLAG", "动态 flag", -1) 161 | } else { 162 | readme = strings.Replace(readme, "TASK_FLAG", meta.Task.Flag, -1) 163 | } 164 | 165 | // 写入文件 166 | util.WriteFile("README.md", string(readme), 0644) 167 | 168 | } 169 | -------------------------------------------------------------------------------- /pkg/errutil/errutil.go: -------------------------------------------------------------------------------- 1 | package errutil 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | log "github.com/sirupsen/logrus" 8 | ) 9 | 10 | type error interface { 11 | Error() string 12 | } 13 | 14 | var errorMessages = map[string]string{ 15 | "InvalidAccessKeyId.NotFound": "当前访问密钥无效 (Current access key are invalid)", 16 | "Message: The specified parameter \"SecurityToken.Expired\" is not valid.": "当前临时访问密钥已过期 (Current SecurityToken has expired)", 17 | "ErrorCode: InvalidSecurityToken.Expired": "当前临时访问密钥已过期 (Current SecurityToken has expired)", 18 | "Message: The Access Key is disabled.": "当前访问密钥已被禁用 (The Access Key is disabled)", 19 | "ErrorCode: Forbidden.RAM": "当前访问密钥没有执行命令的权限 (Current Access Key do not have permission to execute commands)", 20 | "ErrorCode: NoPermission": "当前访问密钥没有接管控制台的权限 (Current Access Key do not have permission to take over the console)", 21 | "ErrorCode=NoSuchKey": "存储桶中没有这个对象 (There is no such key in the bucket)", 22 | "Code=ResourceNotFound, Message=未查询到对应机器": "指定资源不存在 (Resource not found)", 23 | //"Code=UnauthorizedOperation": "当前 AK 权限不足 (Insufficient Access Key permissions)", 24 | "you are not authorized to perform operation (tat:CreateCommand)": "当前 AK 不具备执行命令的权限 (This Access Key does not have permission to execute commands)", 25 | "network is unreachable": "当前网络连接异常 (Network is unreachable)", 26 | "InvalidSecurityToken.Expired": "临时令牌已过期 (STS token has expired)", 27 | "InvalidAccessKeyId.Inactive": "当前 AK 已被禁用 (The current AccessKeyId is inactive)", 28 | "interrupt": "程序已退出 (Program exited.)", 29 | "ErrorCode=AccessDenied, ErrorMessage=\"The bucket you access does not belong to you.\"": "获取 Bucket 信息失败,访问被拒绝 (Failed to get Bucket information, access is denied.)", 30 | "ExpiredToken": "当前访问密钥已过期 (Current token has expired)", 31 | "read: connection reset by peer": "网络连接出现错误,请检查您的网络环境是否正常 (There is an error in your network connection, please check if your network environment is normal.)", 32 | "Code=ResourceUnavailable.AgentNotInstalled": "Agent 未安装 (Agent not installed)", 33 | "Incorrect IAM authentication information": "当前 AK 信息无效 (Current AccessKey information is invalid)", 34 | "The API does not exist or has not been published in the environment": "当前用户已存在,请指定其他用户名 (User already exists, please specify another user name)", 35 | "Status=403 Forbidden, Code=AccessDenied": "当前权限不足 (Insufficient permissions)", 36 | "Message: The specified InstanceId does not exist.": "指定的实例不存在 (The specified instance does not exist.)", 37 | "Message: Specified account name already exists in this instance.": "用户名已存在,请指定其他的用户名 (The username already exists. Please specify a different username.)", 38 | "Message: Other endpoint exist.": "数据库已经是公开访问的 (The database is already publicly accessible.)", 39 | "Message: Current DB instance state does not support this operation.": "当前数据库状态不支持此操作,请稍后重试 (The current database state does not support this operation. Please try again later.)", 40 | "Message: Specified SecurityIPList is not valid.": "指定的白名单无效,请检查后重试,注意是否有使用 CIDR 格式 (The specified whitelist is invalid. Please check and try again, ensuring that you are using the CIDR format if required.)", 41 | "Message: Invalid security ip list specified, duplicated.": "该白名单列表已存在 (The whitelist entry already exists.)", 42 | } 43 | 44 | var errorMessagesNoExit = map[string]string{ 45 | "ErrorCode: Forbidden.RAM": "当前访问密钥没有执行命令的权限 (Current Access Key do not have permission to execute commands)", 46 | //"ErrorCode: Forbidden": " 当前访问密钥没有 RDS 的读取权限 (Current Access Key do not have read access to RDS"), 47 | "You are forbidden to list buckets.": "当前凭证不具备 OSS 的读取权限,无法获取 OSS 数据。 (OSS data is not available because the current credential does not have read access to OSS.)", 48 | "ErrorCode: EntityAlreadyExists.User.Policy": "已接管过控制台,无需重复接管 (Console has been taken over)", 49 | "ErrorCode: EntityAlreadyExists.User": "已接管过控制台,无需重复接管 (Console has been taken over)", 50 | "ErrorCode: EntityNotExist.User": "已取消接管控制台,无需重复取消 (Console has been de-taken over)", 51 | "Code=ResourceNotFound, Message=指定资源": "指定资源不存在 (ResourceNotFound)", 52 | "InvalidParameter.SubUserNameInUse": "已接管过控制台,无需重复接管 (Console has been taken over)", 53 | "you are not authorized to perform operation (cwp:DescribeMachines)": "当前 AK 没有 CWP 权限", 54 | } 55 | 56 | var errorMessagesExit = map[string]string{ 57 | "ErrorCode: Forbidden.RAM": "当前访问密钥没有执行命令的权限 (Current Access Key do not have permission to execute commands)", 58 | "ErrorCode: NoPermission": "当前访问密钥没有接管控制台的权限 (Current Access Key do not have permission to take over the console)", 59 | "network is unreachable": "当前网络连接异常 (Network is unreachable)", 60 | "InvalidSecurityToken.Expired": "临时令牌已过期 (STS token has expired)", 61 | "InvalidAccessKeyId.Inactive": "当前 AK 已被禁用 (The current AccessKeyId is inactive)", 62 | //"Message=操作未授权,请检查CAM策略。": "当前 AK 权限不足 (Insufficient Access Key permissions)", 63 | "Code=AuthFailure.SecretIdNotFound": "SecretId 不存在,请输入正确的密钥 (SecretId does not exist, please enter the correct key.)", 64 | "Code=AuthFailure.SignatureFailure": "请求签名验证失败,请检查您的访问密钥是否正确 (Request signature verification failed, please check if your access key is correct.)", 65 | "read: connection reset by peer": "网络连接出现错误,请检查您的网络环境是否正常 (There is an error in your network connection, please check if your network environment is normal.)", 66 | "InvalidAccessKeyId.NotFound": "当前访问密钥无效 (Current access key are invalid)", 67 | "InvalidAccessKeySecret": "无效的 AccessKey (Invalid AccessKey)", 68 | } 69 | 70 | func HandleErr(e error) { 71 | if e != nil { 72 | log.Traceln(e.Error()) 73 | for k, v := range errorMessages { 74 | if strings.Contains(e.Error(), k) { 75 | log.Errorln(v) 76 | os.Exit(0) 77 | } 78 | } 79 | log.Errorln(e) 80 | } 81 | } 82 | 83 | func HandleErrNoExit(e error) { 84 | if e != nil { 85 | log.Traceln(e.Error()) 86 | for k, v := range errorMessagesNoExit { 87 | if strings.Contains(e.Error(), k) { 88 | log.Debugln(v) 89 | } 90 | } 91 | for k, v := range errorMessagesExit { 92 | if strings.Contains(e.Error(), k) { 93 | log.Errorln(v) 94 | os.Exit(0) 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /pkg/global/const.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | // 镜像源配置 4 | var Registry = map[string]string{ 5 | "AliYun": "registry.cn-hangzhou.aliyuncs.com/ctfhub_base", 6 | // "CTFHub": "registry.cn-hangzhou.aliyuncs.com/", 7 | "DockerHub": "ctfhub", 8 | } 9 | 10 | // 题目类型 11 | var ChallengeType = map[string]string{ 12 | "Web": "web", 13 | "Pwn": "pwn", 14 | "Socket": "misc", 15 | } 16 | 17 | // 语言类型 18 | var Language = map[string]string{ 19 | "PHP": "php", 20 | "HTML": "html", 21 | "Python": "python", 22 | // "NodeJS": "nodejs", 23 | // "Java": "java", 24 | // "Ruby": "ruby", 25 | } 26 | 27 | var PHPVersion = []string{ 28 | "5.6", "7.4", "8.0", 29 | } 30 | var PythonVersion = []string{ 31 | "2.7", "3.11", 32 | } 33 | var NodeJSVersion = []string{ 34 | "12", "14", "16", "18", 35 | } 36 | var JavaVersion = []string{ 37 | "8", "11", "15", 38 | } 39 | var RubyVersion = []string{ 40 | "2.5", "2.6", "2.7", 41 | } 42 | 43 | var DBType = map[string]string{ 44 | "无/SQLite": "", 45 | "MySQL": "mysql", 46 | // "MongoDB": "mongodb", 47 | } 48 | 49 | // PHP Web服务器 50 | var PHPWebServer = map[string]string{ 51 | "Apache HTTPd": "httpd", 52 | "Nginx": "nginx", 53 | } 54 | 55 | // Python Web服务器 56 | var PythonWebServer = map[string]string{ 57 | "gunicron": "gunicron", 58 | "supervisor": "supervisor", 59 | } 60 | 61 | // Java Web服务器 62 | var JavaServer = map[string]string{ 63 | "Tomcat": "tomcat", 64 | } 65 | 66 | // Pwn架构 67 | var PwnArch = map[string]string{ 68 | "x86/x64 Binary": "binary", 69 | // "x86/x64 Kernel": "kernel", 70 | // "arm/arm64/mips/mips64": "qemu", 71 | } 72 | 73 | // Pwn启动方式 74 | var PwnServer = map[string]string{ 75 | "socat": "socat", 76 | "xinet.d": "xinetd", 77 | } 78 | 79 | var FileTemplate = map[string]string{ 80 | "flag.sh": "#!/bin/bash\nflag", 81 | "start.sh": "#!/bin/bash\nstart", 82 | "db.sql": "db", 83 | } 84 | 85 | // 难度 86 | var Level = []string{"签到", "简单", "中等", "困难"} 87 | -------------------------------------------------------------------------------- /pkg/global/type.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | type Config struct { 4 | Author string `yaml:"author"` 5 | Contact string `yaml:"contact"` 6 | RegistryUrl string `yaml:"registry_url"` 7 | } 8 | 9 | type DockerCompsoe struct { 10 | Version string `yaml:"version"` 11 | Services struct { 12 | Challenge struct { 13 | Build string `yaml:"build"` 14 | Image string `yaml:"image"` 15 | Ports []string `yaml:"ports"` 16 | Environment []string `yaml:"environment"` 17 | } `yaml:"challenge"` 18 | } `yaml:"services"` 19 | } 20 | 21 | type Meta struct { 22 | Author struct { 23 | Name string `yaml:"name"` 24 | Contact string `yaml:"contact"` 25 | } `yaml:"author"` 26 | Task struct { 27 | Name string `yaml:"name"` 28 | Type string `yaml:"type"` 29 | Description string `yaml:"description"` 30 | Level string `yaml:"level"` 31 | Flag string `yaml:"flag"` 32 | Hints []string `yaml:"hints"` 33 | } `yaml:"task"` 34 | Challenge struct { 35 | Name string `yaml:"name"` 36 | Refer string `yaml:"refer"` 37 | Tags []string `yaml:"tags"` 38 | } `yaml:"challenge"` 39 | } 40 | -------------------------------------------------------------------------------- /pkg/sdk/github/download.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/fs" 7 | "net/http" 8 | "os" 9 | "time" 10 | 11 | "github.com/k0kubun/go-ansi" 12 | "github.com/schollz/progressbar/v3" 13 | "golang.org/x/net/proxy" 14 | ) 15 | 16 | // Download 下载资源并另存为 17 | func Download(srcURL string, proxy_str string, filename string, flag int, perm fs.FileMode, withProgress bool) (size int64, err error) { 18 | httpClient := &http.Client{} 19 | if proxy_str != "" { 20 | fmt.Println("使用代理 socks5:// " + proxy_str) 21 | dialer, err := proxy.SOCKS5("tcp", proxy_str, nil, proxy.Direct) 22 | if err != nil { 23 | fmt.Fprintln(os.Stderr, "can't connect to the proxy:", err) 24 | os.Exit(1) 25 | } 26 | // setup a http client 27 | httpTransport := &http.Transport{} 28 | 29 | httpTransport.Dial = dialer.Dial 30 | httpClient = &http.Client{Transport: httpTransport} 31 | } 32 | 33 | //发起网络请求 34 | resp, err := httpClient.Get(srcURL) 35 | if err != nil { 36 | return 0, NewDownloadError(srcURL, err) 37 | } 38 | defer resp.Body.Close() 39 | 40 | if !IsSuccess(resp.StatusCode) { 41 | return 0, NewURLUnreachableError(srcURL, fmt.Errorf("%d", resp.StatusCode)) 42 | } 43 | 44 | f, err := os.OpenFile(filename, flag, perm) 45 | if err != nil { 46 | return 0, NewDownloadError(srcURL, err) 47 | } 48 | defer f.Close() 49 | 50 | var dst io.Writer 51 | if withProgress { 52 | bar := progressbar.NewOptions64( 53 | resp.ContentLength, 54 | progressbar.OptionEnableColorCodes(true), 55 | progressbar.OptionSetTheme(progressbar.Theme{ 56 | Saucer: "=", 57 | SaucerHead: ">", 58 | SaucerPadding: " ", 59 | BarStart: "[", 60 | BarEnd: "]", 61 | }), 62 | progressbar.OptionSetWidth(15), 63 | progressbar.OptionSetDescription("Downloading"), 64 | progressbar.OptionSetWriter(ansi.NewAnsiStdout()), 65 | progressbar.OptionShowBytes(true), 66 | progressbar.OptionThrottle(65*time.Millisecond), 67 | progressbar.OptionShowCount(), 68 | progressbar.OptionOnCompletion(func() { 69 | _, _ = fmt.Fprint(ansi.NewAnsiStdout(), "\n") 70 | }), 71 | // progressbar.OptionSpinnerType(35), 72 | // progressbar.OptionFullWidth(), 73 | ) 74 | _ = bar.RenderBlank() 75 | dst = io.MultiWriter(f, bar) 76 | 77 | } else { 78 | dst = f 79 | } 80 | return io.Copy(dst, resp.Body) 81 | } 82 | 83 | // IsSuccess 返回 http 请求是否成功 84 | func IsSuccess(statusCode int) bool { 85 | return statusCode >= http.StatusOK && statusCode < http.StatusMultipleChoices 86 | } 87 | -------------------------------------------------------------------------------- /pkg/sdk/github/error.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | // ErrVersionNotFound 版本不存在 11 | ErrVersionNotFound = errors.New("version not found") 12 | // ErrPackageNotFound 版本包不存在 13 | ErrPackageNotFound = errors.New("installation package not found") 14 | ) 15 | 16 | var ( 17 | // ErrUnsupportedChecksumAlgorithm 不支持的校验和算法 18 | ErrUnsupportedChecksumAlgorithm = errors.New("unsupported checksum algorithm") 19 | // ErrChecksumNotMatched 校验和不匹配 20 | ErrChecksumNotMatched = errors.New("file checksum does not match the computed checksum") 21 | // ErrChecksumFileNotFound 校验和文件不存在 22 | ErrChecksumFileNotFound = errors.New("checksum file not found") 23 | ) 24 | 25 | // URLUnreachableError URL不可达错误 26 | type URLUnreachableError struct { 27 | err error 28 | url string 29 | } 30 | 31 | // NewURLUnreachableError 返回URL不可达错误实例 32 | func NewURLUnreachableError(url string, err error) error { 33 | return &URLUnreachableError{ 34 | err: err, 35 | url: url, 36 | } 37 | } 38 | 39 | func (e URLUnreachableError) Error() string { 40 | var buf strings.Builder 41 | buf.WriteString(fmt.Sprintf("URL %q is unreachable", e.url)) 42 | if e.err != nil { 43 | buf.WriteString(" ==> " + e.err.Error()) 44 | } 45 | return buf.String() 46 | } 47 | 48 | func (e URLUnreachableError) Err() error { 49 | return e.err 50 | } 51 | 52 | func (e URLUnreachableError) URL() string { 53 | return e.url 54 | } 55 | 56 | // DownloadError 下载失败错误 57 | type DownloadError struct { 58 | url string 59 | err error 60 | } 61 | 62 | // NewDownloadError 返回下载失败错误实例 63 | func NewDownloadError(url string, err error) error { 64 | return &DownloadError{ 65 | url: url, 66 | err: err, 67 | } 68 | } 69 | 70 | // Error 返回错误字符串 71 | func (e DownloadError) Error() string { 72 | var buf strings.Builder 73 | buf.WriteString(fmt.Sprintf("Resource(%s) download failed", e.url)) 74 | if e.err != nil { 75 | buf.WriteString(" ==> " + e.err.Error()) 76 | } 77 | return buf.String() 78 | } 79 | 80 | // Err 返回错误对象 81 | func (e DownloadError) Err() error { 82 | return e.err 83 | } 84 | 85 | // URL 返回资源URL 86 | func (e DownloadError) URL() string { 87 | return e.url 88 | } 89 | -------------------------------------------------------------------------------- /pkg/sdk/github/release.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | "os" 9 | "path/filepath" 10 | "strconv" 11 | "time" 12 | 13 | "github.com/Masterminds/semver/v3" 14 | 15 | "github.com/voidint/go-update" 16 | ) 17 | 18 | // Release 版本 19 | type Release struct { 20 | TagName string `json:"tag_name"` 21 | Assets []Asset `json:"assets"` 22 | } 23 | 24 | // Asset 静态资源 25 | type Asset struct { 26 | Name string `json:"name"` 27 | ContentType string `json:"content_type"` 28 | BrowserDownloadURL string `json:"browser_download_url"` 29 | } 30 | 31 | // IsCompressedFile 返回是否是压缩文件的布尔值 32 | func (a Asset) IsCompressedFile() bool { 33 | return a.ContentType == "application/zip" || a.ContentType == "application/x-gzip" 34 | } 35 | 36 | // ReleaseUpdater 版本更新器 37 | type ReleaseUpdater struct { 38 | } 39 | 40 | // NewReleaseUpdater 返回版本更新器实例 41 | func NewReleaseUpdater() *ReleaseUpdater { 42 | return new(ReleaseUpdater) 43 | } 44 | 45 | // CheckForUpdates 检查是否有更新 46 | func (up ReleaseUpdater) CheckForUpdates(current *semver.Version, owner, repo string) (rel *Release, yes bool, err error) { 47 | url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo) 48 | 49 | req, err := http.NewRequest(http.MethodGet, url, nil) 50 | if err != nil { 51 | return nil, false, err 52 | } 53 | req.Header.Set("Accept", "application/vnd.github.v3+json") 54 | 55 | resp, err := http.DefaultClient.Do(req) 56 | if err != nil { 57 | return nil, false, err 58 | } 59 | defer resp.Body.Close() 60 | 61 | if !IsSuccess(resp.StatusCode) { 62 | return nil, false, NewURLUnreachableError(url, fmt.Errorf("%d", resp.StatusCode)) 63 | } 64 | 65 | var latest Release 66 | if err = json.NewDecoder(resp.Body).Decode(&latest); err != nil { 67 | return nil, false, err 68 | } 69 | 70 | latestVersion, err := semver.NewVersion(latest.TagName) 71 | if err != nil { 72 | return nil, false, err 73 | } 74 | if latestVersion.GreaterThan(current) { 75 | return &latest, true, nil 76 | } 77 | return nil, false, nil 78 | } 79 | 80 | // ErrAssetNotFound 资源不存在 81 | var ErrAssetNotFound = errors.New("asset not found") 82 | 83 | // Apply 更新指定版本 84 | func (up ReleaseUpdater) Apply(rel *Release, 85 | findAsset func([]Asset) (idx int), 86 | proxy string, 87 | ) error { 88 | // 查找下载链接 89 | idx := findAsset(rel.Assets) 90 | if idx < 0 { 91 | return ErrAssetNotFound 92 | } 93 | 94 | // 下载文件 95 | tmpDir, err := os.MkdirTemp("", strconv.FormatInt(time.Now().UnixNano(), 10)) 96 | if err != nil { 97 | return err 98 | } 99 | defer os.RemoveAll(tmpDir) 100 | 101 | url := rel.Assets[idx].BrowserDownloadURL 102 | srcFilename := filepath.Join(tmpDir, filepath.Base(url)) 103 | dstFilename := srcFilename 104 | if _, err = Download(url, proxy, srcFilename, os.O_WRONLY|os.O_CREATE, 0644, true); err != nil { 105 | return err 106 | } 107 | 108 | // 更新文件 109 | dstFile, err := os.Open(dstFilename) 110 | if err != nil { 111 | return nil 112 | } 113 | defer dstFile.Close() 114 | return update.Apply(dstFile, update.Options{}) 115 | } 116 | -------------------------------------------------------------------------------- /pkg/tpl/README.md: -------------------------------------------------------------------------------- 1 | # CHALLENGE_NAME 2 | 3 | 题目来源: `CHALLENGE_REFER` 4 | 5 | ## 制作者信息 6 | 7 | - 制作者: `AUTHOR` 8 | - 联系方式: `EMAIL` 9 | 10 | ## 题目信息 11 | 12 | - 镜像名称: `TASK_NAME` 13 | - 题目类型: `TASK_TYPE` 14 | - 题目难度: `TASK_LEVEL` 15 | - flag: `TASK_FLAG` -------------------------------------------------------------------------------- /pkg/tpl/config.yml: -------------------------------------------------------------------------------- 1 | author: l1n3 2 | contact: yw9381@163.com 3 | registry_url: https://registry.cn-hangzhou.aliyuncs.com/ctfhub/ -------------------------------------------------------------------------------- /pkg/tpl/db.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfhub-team/challenge_generate/f7d322b002e04fe8a542d25f422f9a9ab18b6143/pkg/tpl/db.json -------------------------------------------------------------------------------- /pkg/tpl/db.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctfhub-team/challenge_generate/f7d322b002e04fe8a542d25f422f9a9ab18b6143/pkg/tpl/db.sql -------------------------------------------------------------------------------- /pkg/tpl/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | challenge: 4 | build: . 5 | image: test 6 | ports: 7 | - 80:80 8 | environment: 9 | - FLAG=ctfhub{test_flag} 10 | - DOMAIN=test.sandbox.ctfhub.com -------------------------------------------------------------------------------- /pkg/tpl/flag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | < ${flag_path} 36 | } 37 | 38 | write_flag_in_db() { 39 | local db_name="${1:-web}" 40 | local db_table="${2:-flag}" 41 | local db_column="${3:-flag}" 42 | echo mysql -uroot -proot -e "update ${db_name}.${db_table} set ${db_column}='${FLAG}';" 43 | } 44 | 45 | export FLAG=not_flag 46 | FLAG=not_flag -------------------------------------------------------------------------------- /pkg/tpl/meta.yml: -------------------------------------------------------------------------------- 1 | author: 2 | name: l1n3 3 | contact: yw9381@163.com 4 | task: 5 | name: challenge_web_2022_hitcon_rce 6 | type: Web 7 | description: 8 | level: 签到 9 | flag: 10 | hints: 11 | challenge: 12 | name: 13 | refer: 14 | tags: -------------------------------------------------------------------------------- /pkg/tpl/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | <