From a4cab218bb5f9851028091eb74469d4e21c469d0 Mon Sep 17 00:00:00 2001 From: github Date: Mon, 8 Jan 2024 08:23:01 +0100 Subject: [PATCH 01/42] Add no-check flag --- cmd/root.go | 20 ++++++++++++-------- config/config.go | 1 + 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index d919ac5..61f9144 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -27,14 +27,17 @@ var rootCmd = &cobra.Command{ config.GlobalConfig.Logger.Error("", zap.Error(err)) os.Exit(1) } - message, err := update.Check(config.Version) - if err != nil { - config.GlobalConfig.Logger.Error("", zap.Error(err)) - os.Exit(1) - } - config.GlobalConfig.Logger.Debug(fmt.Sprintf("Message : %s", message)) - if strings.Contains(message, "A new update") { - fmt.Printf("%s\n\n", message) + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Check for updates : %v", config.GlobalConfig.NoCheck)) + if !config.GlobalConfig.NoCheck { + message, err := update.Check(config.Version) + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Message : %s", message)) + if strings.Contains(message, "A new update") { + fmt.Printf("%s\n\n", message) + } } }, } @@ -52,4 +55,5 @@ func init() { rootCmd.PersistentFlags().CountVarP(&config.GlobalConfig.Verbose, "verbose", "v", "Verbose level") rootCmd.PersistentFlags().StringVarP(&config.GlobalConfig.ProxyParam, "proxy", "p", "", "Configure a URL for an HTTP proxy") rootCmd.PersistentFlags().BoolVarP(&config.GlobalConfig.BatchParam, "batch", "b", false, "Don't ask questions") + rootCmd.PersistentFlags().BoolVarP(&config.GlobalConfig.NoCheck, "no-check", "n", false, "Don't check for new updates") } diff --git a/config/config.go b/config/config.go index 2b31a6a..728b4b4 100644 --- a/config/config.go +++ b/config/config.go @@ -16,6 +16,7 @@ type Settings struct { Logger *zap.Logger ProxyParam string BatchParam bool + NoCheck bool } var GlobalConfig Settings From 1ca7cd9911ef448dddb30cca224c7c609032ae9c Mon Sep 17 00:00:00 2001 From: Github Action Date: Mon, 8 Jan 2024 07:24:10 +0000 Subject: [PATCH 02/42] Update dev version to a4cab218bb5f9851028091eb74469d4e21c469d0 --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 728b4b4..a91aaa7 100644 --- a/config/config.go +++ b/config/config.go @@ -33,7 +33,7 @@ const BaseHackTheBoxAPIURL = "https://" + HostHackTheBox + "/api/v4" const StatusURL = "https://status.hackthebox.com/api/v2/status.json" -const Version = "395883f75ab40db134f940c627d5b19b66c792c5" +const Version = "a4cab218bb5f9851028091eb74469d4e21c469d0" func ConfigureLogger() error { var logLevel zapcore.Level From 32501bfab6e93e517a12b7017130f96e6b933311 Mon Sep 17 00:00:00 2001 From: QU35T-code <51704860+QU35T-code@users.noreply.github.com> Date: Mon, 8 Jan 2024 22:50:17 +0100 Subject: [PATCH 03/42] Create CODEOWNERS --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..19c8fc9 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +QU35T-code From 0625a7368ec7ee552466b8dc39fd3136bbcb8b10 Mon Sep 17 00:00:00 2001 From: Github Action Date: Mon, 8 Jan 2024 21:51:22 +0000 Subject: [PATCH 04/42] Update dev version to 32501bfab6e93e517a12b7017130f96e6b933311 --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index a91aaa7..bdd56f6 100644 --- a/config/config.go +++ b/config/config.go @@ -33,7 +33,7 @@ const BaseHackTheBoxAPIURL = "https://" + HostHackTheBox + "/api/v4" const StatusURL = "https://status.hackthebox.com/api/v2/status.json" -const Version = "a4cab218bb5f9851028091eb74469d4e21c469d0" +const Version = "32501bfab6e93e517a12b7017130f96e6b933311" func ConfigureLogger() error { var logLevel zapcore.Level From eccf372c442a0ecd7f81fd679db0f2bc44d1090e Mon Sep 17 00:00:00 2001 From: github Date: Thu, 11 Jan 2024 09:23:45 +0100 Subject: [PATCH 05/42] Add submit for fortresses --- cmd/submit.go | 49 ++++++++++++++++++++++++++++------ lib/submit/submit.go | 63 ++++++++++++++++++++------------------------ lib/utils/models.go | 13 +++++++++ lib/utils/tui.go | 2 ++ lib/utils/utils.go | 54 +++++++++++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 43 deletions(-) diff --git a/cmd/submit.go b/cmd/submit.go index 4b3ee64..ebbf070 100644 --- a/cmd/submit.go +++ b/cmd/submit.go @@ -35,11 +35,43 @@ var submitCmd = &cobra.Command{ os.Exit(1) } - config.GlobalConfig.Logger.Debug(fmt.Sprintf("Difficulty: %d", difficultyParam)) - config.GlobalConfig.Logger.Debug(fmt.Sprintf("Machine name: %s", machineNameParam)) - config.GlobalConfig.Logger.Debug(fmt.Sprintf("Challenge name: %s", challengeNameParam)) + fortressNameParam, err := cmd.Flags().GetString("fortress_name") + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + + // config.GlobalConfig.Logger.Debug(fmt.Sprintf("Difficulty: %d", difficultyParam)) + // config.GlobalConfig.Logger.Debug(fmt.Sprintf("Machine name: %s", machineNameParam)) + // config.GlobalConfig.Logger.Debug(fmt.Sprintf("Challenge name: %s", challengeNameParam)) + // config.GlobalConfig.Logger.Debug(fmt.Sprintf("Fortress name: %s", fortressNameParam)) + + if challengeNameParam != "" || machineNameParam != "" { + if difficultyParam == 0 { + fmt.Println("required flag(s) 'difficulty' not set") + os.Exit(1) + } + } + + var modeType string + var modeValue string + + // TODO: check si plusieurs arguments : -m sau -c dd -f aws -> Seulement un a la fois ! + + if fortressNameParam != "" { + modeType = "fortress" + modeValue = fortressNameParam + } else if machineNameParam != "" { + modeType = "machine" + modeValue = machineNameParam + } else if challengeNameParam != "" { + modeType = "challenge" + modeValue = challengeNameParam + } + + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Mode type: %s", modeType)) - output, err := submit.CoreSubmitCmd(difficultyParam, machineNameParam, challengeNameParam) + output, err := submit.CoreSubmitCmd(difficultyParam, modeType, modeValue) if err != nil { config.GlobalConfig.Logger.Error("", zap.Error(err)) os.Exit(1) @@ -60,9 +92,10 @@ func init() { rootCmd.AddCommand(submitCmd) submitCmd.Flags().StringP("machine_name", "m", "", "Machine Name") submitCmd.Flags().StringP("challenge_name", "c", "", "Challenge Name") + submitCmd.Flags().StringP("fortress_name", "f", "", "Fortress Name") submitCmd.Flags().IntP("difficulty", "d", 0, "Difficulty") - err := submitCmd.MarkFlagRequired("difficulty") - if err != nil { - fmt.Println(err) - } + // err := submitCmd.MarkFlagRequired("difficulty") + // if err != nil { + // fmt.Println(err) + // } } diff --git a/lib/submit/submit.go b/lib/submit/submit.go index 56514f8..ad6dfae 100644 --- a/lib/submit/submit.go +++ b/lib/submit/submit.go @@ -15,30 +15,32 @@ import ( ) // coreSubmitCmd handles the submission of flags for machines or challenges, returning a status message or error. -func CoreSubmitCmd(difficultyParam int, machineNameParam string, challengeNameParam string) (string, error) { - if difficultyParam < 1 || difficultyParam > 10 { - return "", errors.New("difficulty must be set between 1 and 10") - } - - // Common payload elements - difficultyString := strconv.Itoa(difficultyParam * 10) - payload := map[string]string{ - "difficulty": difficultyString, +func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (string, error) { + var payload map[string]string + var difficultyString string + if difficultyParam != 0 { + if difficultyParam < 1 || difficultyParam > 10 { + return "", errors.New("difficulty must be set between 1 and 10") + } + difficultyString = strconv.Itoa(difficultyParam * 10) } - url := "" + var url string - if challengeNameParam != "" { + if modeType == "challenge" { config.GlobalConfig.Logger.Info("Challenge submit requested") - challengeID, err := utils.SearchItemIDByName(challengeNameParam, "Challenge") + challengeID, err := utils.SearchItemIDByName(modeValue, "Challenge") if err != nil { return "", err } url = config.BaseHackTheBoxAPIURL + "/challenge/own" - payload["challenge_id"] = challengeID - } else if machineNameParam != "" { + payload = map[string]string{ + "difficulty": difficultyString, + "challenge_id": challengeID, + } + } else if modeType == "machine" { config.GlobalConfig.Logger.Info("Machine submit requested") - machineID, err := utils.SearchItemIDByName(machineNameParam, "Machine") + machineID, err := utils.SearchItemIDByName(modeValue, "Machine") if err != nil { return "", err } @@ -54,28 +56,19 @@ func CoreSubmitCmd(difficultyParam int, machineNameParam string, challengeNamePa url = config.BaseHackTheBoxAPIURL + "/machine/own" } - payload["id"] = machineID - } else if machineNameParam == "" && challengeNameParam == "" { - machineID, err := utils.GetActiveMachineID() - if err != nil { - return "", err - } - if machineID == "" { - return "No machine is running", nil + payload = map[string]string{ + "difficulty": difficultyString, + "id": machineID, } - machineType, err := utils.GetMachineType(machineID) + } else if modeType == "fortress" { + config.GlobalConfig.Logger.Info("Fortress submit requested") + fortressID, err := utils.SearchFortressID(modeValue) if err != nil { return "", err } - config.GlobalConfig.Logger.Debug(fmt.Sprintf("Machine Type: %s", machineType)) - - if machineType == "release" { - url = config.BaseHackTheBoxAPIURL + "/arena/own" - } else { - url = config.BaseHackTheBoxAPIURL + "/machine/own" - - } - payload["id"] = machineID + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Fortress ID : %d", fortressID)) + url = fmt.Sprintf("%s/fortress/%d/flag", config.BaseHackTheBoxAPIURL, fortressID) + payload = map[string]string{} } fmt.Print("Flag : ") @@ -86,10 +79,10 @@ func CoreSubmitCmd(difficultyParam int, machineNameParam string, challengeNamePa } flagOriginal := string(flagByte) flag := strings.ReplaceAll(flagOriginal, " ", "") - payload["flag"] = flag config.GlobalConfig.Logger.Debug(fmt.Sprintf("Flag: %s", flag)) - config.GlobalConfig.Logger.Debug(fmt.Sprintf("Difficulty: %s", difficultyString)) + + payload["flag"] = flag jsonData, err := json.Marshal(payload) if err != nil { diff --git a/lib/utils/models.go b/lib/utils/models.go index 3208afc..f75b492 100644 --- a/lib/utils/models.go +++ b/lib/utils/models.go @@ -20,3 +20,16 @@ type Root struct { Challenges interface{} `json:"challenges"` Usernames interface{} `json:"users"` } + +// Move this + +type Item struct { + ID int `json:"id"` + Name string `json:"name"` +} + +// Structure pour représenter le JSON entier +type JsonResponse struct { + Status bool `json:"status"` + Data map[string]Item `json:"data"` +} diff --git a/lib/utils/tui.go b/lib/utils/tui.go index 822acb3..35775a3 100644 --- a/lib/utils/tui.go +++ b/lib/utils/tui.go @@ -86,6 +86,8 @@ func displayInfo(dataMaps map[string]map[string]interface{}, dataMapKey string, formatterFunc = func(item map[string]interface{}) string { var object_type interface{} switch item["object_type"].(string) { + case "endgame": + object_type = item["flag_title"] case "fortress": object_type = item["flag_title"] case "challenge": diff --git a/lib/utils/utils.go b/lib/utils/utils.go index 5767b89..e2e6902 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -21,6 +21,7 @@ import ( "github.com/AlecAivazis/survey/v2" "github.com/GoToolSharing/htb-cli/config" "github.com/briandowns/spinner" + "github.com/sahilm/fuzzy" ) // SetTabWriterHeader will display the information in an array @@ -516,3 +517,56 @@ func SearchLastReleaseArenaMachine() (string, error) { machineIDstr := strconv.Itoa(machineID) return machineIDstr, nil } + +func extractNamesAndIDs(jsonData string) (map[string]int, error) { + var response JsonResponse + err := json.Unmarshal([]byte(jsonData), &response) + if err != nil { + return nil, err + } + + namesAndIDs := make(map[string]int) + for _, item := range response.Data { + namesAndIDs[item.Name] = item.ID + } + + return namesAndIDs, nil +} + +func SearchFortressID(partialName string) (int, error) { + url := fmt.Sprintf("%s/fortresses", config.BaseHackTheBoxAPIURL) + resp, err := HtbRequest(http.MethodGet, url, nil) + if err != nil { + return 0, err + } + jsonData, _ := io.ReadAll(resp.Body) + namesAndIDs, err := extractNamesAndIDs(string(jsonData)) + if err != nil { + fmt.Println("Error parsing JSON:", err) + return 0, nil + } + + var names []string + for name := range namesAndIDs { + names = append(names, name) + } + + matches := fuzzy.Find(partialName, names) + + for _, match := range matches { + matchedName := names[match.Index] + isConfirmed := AskConfirmation("The following fortress was found : " + matchedName) + if isConfirmed { + return namesAndIDs[matchedName], nil + } + } + + // return "", fmt.Errorf("error: Nothing was found") + // info := ParseJsonMessage(resp, "data") + // if info == nil { + // return 0, err + // } + // config.GlobalConfig.Logger.Debug(fmt.Sprintf("Data map: %v", info)) + + return 0, nil +} From 41bd1a337741fed2434bf7d3f71dd664bc695e89 Mon Sep 17 00:00:00 2001 From: Github Action Date: Thu, 11 Jan 2024 08:24:56 +0000 Subject: [PATCH 06/42] Update dev version to df9a0904e40ac48835fff2efc1ae57d69ab89097 --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index bdd56f6..6e452e3 100644 --- a/config/config.go +++ b/config/config.go @@ -33,7 +33,7 @@ const BaseHackTheBoxAPIURL = "https://" + HostHackTheBox + "/api/v4" const StatusURL = "https://status.hackthebox.com/api/v2/status.json" -const Version = "32501bfab6e93e517a12b7017130f96e6b933311" +const Version = "df9a0904e40ac48835fff2efc1ae57d69ab89097" func ConfigureLogger() error { var logLevel zapcore.Level From e660c0af0f8bb4a8b23e1d38afbe3ba9905257ea Mon Sep 17 00:00:00 2001 From: github Date: Thu, 11 Jan 2024 09:28:56 +0100 Subject: [PATCH 07/42] Remove automatic dev version --- .github/workflows/go.yml | 21 +------------------- cmd/version.go | 4 ++-- config/config.go | 2 +- lib/update/update.go | 42 ++-------------------------------------- 4 files changed, 6 insertions(+), 63 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 56238db..aec8ccf 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -35,23 +35,4 @@ jobs: env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} - run: go build -v ./... - - update-version: - needs: build - if: github.ref == 'refs/heads/dev' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - ref: 'dev' - - name: Update Version in config.go - run: | - sed -i "s/const Version = \".*\"/const Version = \"$GITHUB_SHA\"/" config/config.go - - name: Commit and Push - run: | - git config --global user.email "github-action@github.com" - git config --global user.name "Github Action" - git add config/config.go - git commit -m "Update dev version to $GITHUB_SHA" - git push origin HEAD:dev \ No newline at end of file + run: go build -v ./... \ No newline at end of file diff --git a/cmd/version.go b/cmd/version.go index d2a9cf7..a1d536a 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -18,8 +18,8 @@ var versionCmd = &cobra.Command{ config.GlobalConfig.Logger.Info("Version command executed") config.GlobalConfig.Logger.Debug(fmt.Sprintf("config.Version: %s", config.Version)) var message string - if len(config.Version) == 40 { - message = fmt.Sprintf("Development version (dev branch): %s", config.Version) + if config.Version == "dev" { + message = "Development version (dev branch)" } else { message = fmt.Sprintf("Stable version (main branch): %s", config.Version) } diff --git a/config/config.go b/config/config.go index bdd56f6..6db8ca9 100644 --- a/config/config.go +++ b/config/config.go @@ -33,7 +33,7 @@ const BaseHackTheBoxAPIURL = "https://" + HostHackTheBox + "/api/v4" const StatusURL = "https://status.hackthebox.com/api/v2/status.json" -const Version = "32501bfab6e93e517a12b7017130f96e6b933311" +const Version = "dev" func ConfigureLogger() error { var logLevel zapcore.Level diff --git a/lib/update/update.go b/lib/update/update.go index 0cb00f3..7cbde0b 100644 --- a/lib/update/update.go +++ b/lib/update/update.go @@ -3,9 +3,7 @@ package update import ( "encoding/json" "fmt" - "io" "net/http" - "strings" "github.com/GoToolSharing/htb-cli/config" "github.com/GoToolSharing/htb-cli/lib/utils" @@ -14,45 +12,9 @@ import ( func Check(newVersion string) (string, error) { // Dev version config.GlobalConfig.Logger.Debug(fmt.Sprintf("config.Version: %s", config.Version)) - if len(config.Version) == 40 { + if config.Version == "dev" { config.GlobalConfig.Logger.Info("Development version detected") - githubCommits := "https://api.github.com/repos/GoToolSharing/htb-cli/commits?sha=dev" - - resp, err := utils.HTTPRequest(http.MethodGet, githubCommits, nil) - if err != nil { - return "", err - } - body, err := io.ReadAll(resp.Body) - config.GlobalConfig.Logger.Debug(fmt.Sprintf("Body: %s", utils.TruncateString(string(body), 500))) - if strings.Contains(string(body), "API rate limit") { - return "htb-cli cannot check for new updates at this time. Please try again later", nil - } - if err != nil { - return "", fmt.Errorf("error when reading the response: %v", err) - } - var commits []Commit - err = json.Unmarshal(body, &commits) - if err != nil { - return "", fmt.Errorf("error when decoding JSON: %v", err) - } - config.GlobalConfig.Logger.Debug(fmt.Sprintf("Commits : %v", commits)) - - var commitHash string - for _, commit := range commits { - if commit.Commit.Author.Name != "Github Action" { - config.GlobalConfig.Logger.Debug(fmt.Sprintf("Last commit hash : %s", commit.SHA)) - commitHash = commit.SHA - break - } - } - var message string - if commitHash != config.Version { - message = fmt.Sprintf("A new update is now available (dev) ! (%s)\nUpdate with : git pull", commitHash) - } else { - message = fmt.Sprintf("You're up to date (dev) ! (%s)", commitHash) - } - - return message, nil + return "Development version (git pull to update)", nil } // Main version From 21357e29657a3e87aa508375e528846cb345a605 Mon Sep 17 00:00:00 2001 From: github Date: Thu, 11 Jan 2024 10:17:34 +0100 Subject: [PATCH 08/42] Add ra submit when no flag is submitted --- cmd/submit.go | 11 ++++------- lib/submit/submit.go | 16 ++++++++++++++++ lib/utils/utils.go | 14 ++++++++------ 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/cmd/submit.go b/cmd/submit.go index ebbf070..7bc6561 100644 --- a/cmd/submit.go +++ b/cmd/submit.go @@ -41,11 +41,6 @@ var submitCmd = &cobra.Command{ os.Exit(1) } - // config.GlobalConfig.Logger.Debug(fmt.Sprintf("Difficulty: %d", difficultyParam)) - // config.GlobalConfig.Logger.Debug(fmt.Sprintf("Machine name: %s", machineNameParam)) - // config.GlobalConfig.Logger.Debug(fmt.Sprintf("Challenge name: %s", challengeNameParam)) - // config.GlobalConfig.Logger.Debug(fmt.Sprintf("Fortress name: %s", fortressNameParam)) - if challengeNameParam != "" || machineNameParam != "" { if difficultyParam == 0 { fmt.Println("required flag(s) 'difficulty' not set") @@ -56,8 +51,6 @@ var submitCmd = &cobra.Command{ var modeType string var modeValue string - // TODO: check si plusieurs arguments : -m sau -c dd -f aws -> Seulement un a la fois ! - if fortressNameParam != "" { modeType = "fortress" modeValue = fortressNameParam @@ -67,9 +60,13 @@ var submitCmd = &cobra.Command{ } else if challengeNameParam != "" { modeType = "challenge" modeValue = challengeNameParam + } else { + modeType = "release-arena" + modeValue = "" } config.GlobalConfig.Logger.Debug(fmt.Sprintf("Mode type: %s", modeType)) + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Mode value: %s", modeValue)) output, err := submit.CoreSubmitCmd(difficultyParam, modeType, modeValue) if err != nil { diff --git a/lib/submit/submit.go b/lib/submit/submit.go index ad6dfae..1b25e30 100644 --- a/lib/submit/submit.go +++ b/lib/submit/submit.go @@ -69,6 +69,22 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri config.GlobalConfig.Logger.Debug(fmt.Sprintf("Fortress ID : %d", fortressID)) url = fmt.Sprintf("%s/fortress/%d/flag", config.BaseHackTheBoxAPIURL, fortressID) payload = map[string]string{} + } else if modeType == "release-arena" { + config.GlobalConfig.Logger.Info("Release Arena submit requested") + isConfirmed := utils.AskConfirmation("Would you like to submit a flag for the release arena ?") + if !isConfirmed { + return "", nil + } + releaseID, err := utils.SearchLastReleaseArenaMachine() + if err != nil { + return "", err + } + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Release Arena ID : %s", releaseID)) + url = fmt.Sprintf("%s/arena/own", config.BaseHackTheBoxAPIURL) + payload = map[string]string{ + "difficulty": difficultyString, + "id": releaseID, + } } fmt.Print("Flag : ") diff --git a/lib/utils/utils.go b/lib/utils/utils.go index e2e6902..4a2c3d3 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -90,7 +90,7 @@ func SearchItemIDByName(item string, element_type string) (string, error) { // Checking if machines array is empty if len(root.Machines.([]interface{})) == 0 { fmt.Println("No machine was found") - return "", fmt.Errorf("error: No machine was found") + os.Exit(0) } var machines []Machine machineData, _ := json.Marshal(root.Machines) @@ -109,7 +109,7 @@ func SearchItemIDByName(item string, element_type string) (string, error) { // Checking if machines array is empty if len(root.Machines.(map[string]interface{})) == 0 { fmt.Println("No machine was found") - return "", fmt.Errorf("error: No machine was found") + os.Exit(0) } var machines map[string]Machine machineData, _ := json.Marshal(root.Machines) @@ -125,7 +125,8 @@ func SearchItemIDByName(item string, element_type string) (string, error) { } os.Exit(0) default: - return "", fmt.Errorf("no machine was found") + fmt.Println("No machine was found") + os.Exit(0) } } else if element_type == "Challenge" { switch root.Challenges.(type) { @@ -133,7 +134,7 @@ func SearchItemIDByName(item string, element_type string) (string, error) { // Checking if challenges array is empty if len(root.Challenges.([]interface{})) == 0 { fmt.Println("No challenge was found") - return "", fmt.Errorf("error: No challenge was found") + os.Exit(0) } var challenges []Challenge challengeData, _ := json.Marshal(root.Challenges) @@ -152,7 +153,7 @@ func SearchItemIDByName(item string, element_type string) (string, error) { // Checking if challenges array is empty if len(root.Challenges.(map[string]interface{})) == 0 { fmt.Println("No challenge was found") - return "", fmt.Errorf("error: No challenge was found") + os.Exit(0) } var challenges map[string]Challenge challengeData, _ := json.Marshal(root.Challenges) @@ -168,7 +169,8 @@ func SearchItemIDByName(item string, element_type string) (string, error) { } os.Exit(0) default: - fmt.Println("No challenge found") + fmt.Println("No challenge was found") + os.Exit(0) } } else if element_type == "Username" { switch root.Usernames.(type) { From 1fc0949335f353739ab61f688a67e44fd4df68c2 Mon Sep 17 00:00:00 2001 From: github Date: Thu, 11 Jan 2024 11:07:35 +0100 Subject: [PATCH 09/42] Add submit for endgames --- cmd/submit.go | 10 +++++++++ lib/submit/submit.go | 9 ++++++++ lib/utils/models.go | 16 ++++++++++++++- lib/utils/utils.go | 49 ++++++++++++++++++++++++++++++++++++++------ 4 files changed, 77 insertions(+), 7 deletions(-) diff --git a/cmd/submit.go b/cmd/submit.go index 7bc6561..bb0bcf1 100644 --- a/cmd/submit.go +++ b/cmd/submit.go @@ -41,6 +41,12 @@ var submitCmd = &cobra.Command{ os.Exit(1) } + endgameNameParam, err := cmd.Flags().GetString("endgame_name") + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + if challengeNameParam != "" || machineNameParam != "" { if difficultyParam == 0 { fmt.Println("required flag(s) 'difficulty' not set") @@ -60,6 +66,9 @@ var submitCmd = &cobra.Command{ } else if challengeNameParam != "" { modeType = "challenge" modeValue = challengeNameParam + } else if endgameNameParam != "" { + modeType = "endgame" + modeValue = endgameNameParam } else { modeType = "release-arena" modeValue = "" @@ -90,6 +99,7 @@ func init() { submitCmd.Flags().StringP("machine_name", "m", "", "Machine Name") submitCmd.Flags().StringP("challenge_name", "c", "", "Challenge Name") submitCmd.Flags().StringP("fortress_name", "f", "", "Fortress Name") + submitCmd.Flags().StringP("endgame_name", "e", "", "Endgame Name") submitCmd.Flags().IntP("difficulty", "d", 0, "Difficulty") // err := submitCmd.MarkFlagRequired("difficulty") // if err != nil { diff --git a/lib/submit/submit.go b/lib/submit/submit.go index 1b25e30..9ac77dc 100644 --- a/lib/submit/submit.go +++ b/lib/submit/submit.go @@ -69,6 +69,15 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri config.GlobalConfig.Logger.Debug(fmt.Sprintf("Fortress ID : %d", fortressID)) url = fmt.Sprintf("%s/fortress/%d/flag", config.BaseHackTheBoxAPIURL, fortressID) payload = map[string]string{} + } else if modeType == "endgame" { + config.GlobalConfig.Logger.Info("Endgame submit requested") + endgameID, err := utils.SearchEndgameID(modeValue) + if err != nil { + return "", err + } + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Endgame ID : %d", endgameID)) + url = fmt.Sprintf("%s/endgame/%d/flag", config.BaseHackTheBoxAPIURL, endgameID) + payload = map[string]string{} } else if modeType == "release-arena" { config.GlobalConfig.Logger.Info("Release Arena submit requested") isConfirmed := utils.AskConfirmation("Would you like to submit a flag for the release arena ?") diff --git a/lib/utils/models.go b/lib/utils/models.go index f75b492..11891d8 100644 --- a/lib/utils/models.go +++ b/lib/utils/models.go @@ -21,7 +21,7 @@ type Root struct { Usernames interface{} `json:"users"` } -// Move this +// Fortreses type Item struct { ID int `json:"id"` @@ -33,3 +33,17 @@ type JsonResponse struct { Status bool `json:"status"` Data map[string]Item `json:"data"` } + +// Endgames + +type Endgame struct { + ID int `json:"id"` + Name string `json:"name"` +} + +// Structure pour représenter le JSON entier +type EndgameJsonResponse struct { + Status bool `json:"status"` + Data []Endgame `json:"data"` +} + diff --git a/lib/utils/utils.go b/lib/utils/utils.go index 4a2c3d3..33a81d3 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -562,13 +562,50 @@ func SearchFortressID(partialName string) (int, error) { return namesAndIDs[matchedName], nil } } + return 0, nil +} - // return "", fmt.Errorf("error: Nothing was found") - // info := ParseJsonMessage(resp, "data") - // if info == nil { - // return 0, err - // } - // config.GlobalConfig.Logger.Debug(fmt.Sprintf("Data map: %v", info)) +func extractEndgamesNamesAndIDs(jsonData string) (map[string]int, error) { + var response EndgameJsonResponse + err := json.Unmarshal([]byte(jsonData), &response) + if err != nil { + return nil, err + } + namesAndIDs := make(map[string]int) + for _, item := range response.Data { + namesAndIDs[item.Name] = item.ID + } + + return namesAndIDs, nil +} + +func SearchEndgameID(partialName string) (int, error) { + url := fmt.Sprintf("%s/endgames", config.BaseHackTheBoxAPIURL) + resp, err := HtbRequest(http.MethodGet, url, nil) + if err != nil { + return 0, err + } + jsonData, _ := io.ReadAll(resp.Body) + namesAndIDs, err := extractEndgamesNamesAndIDs(string(jsonData)) + if err != nil { + fmt.Println("Error parsing JSON:", err) + return 0, nil + } + + var names []string + for name := range namesAndIDs { + names = append(names, name) + } + + matches := fuzzy.Find(partialName, names) + + for _, match := range matches { + matchedName := names[match.Index] + isConfirmed := AskConfirmation("The following endgame was found : " + matchedName) + if isConfirmed { + return namesAndIDs[matchedName], nil + } + } return 0, nil } From 10b18b40c75de95ffa90851930f864d2929fb4d7 Mon Sep 17 00:00:00 2001 From: github Date: Thu, 11 Jan 2024 12:03:57 +0100 Subject: [PATCH 10/42] Add prolab submit --- cmd/root.go | 2 +- cmd/submit.go | 30 ++++++++++++++++----------- lib/submit/submit.go | 9 +++++++++ lib/utils/models.go | 19 ++++++++++++++++-- lib/utils/utils.go | 48 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 15 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 61f9144..69f05fc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -53,7 +53,7 @@ func Execute() { func init() { rootCmd.CompletionOptions.DisableDefaultCmd = true rootCmd.PersistentFlags().CountVarP(&config.GlobalConfig.Verbose, "verbose", "v", "Verbose level") - rootCmd.PersistentFlags().StringVarP(&config.GlobalConfig.ProxyParam, "proxy", "p", "", "Configure a URL for an HTTP proxy") + rootCmd.PersistentFlags().StringVarP(&config.GlobalConfig.ProxyParam, "proxy", "", "", "Configure a URL for an HTTP proxy") rootCmd.PersistentFlags().BoolVarP(&config.GlobalConfig.BatchParam, "batch", "b", false, "Don't ask questions") rootCmd.PersistentFlags().BoolVarP(&config.GlobalConfig.NoCheck, "no-check", "n", false, "Don't check for new updates") } diff --git a/cmd/submit.go b/cmd/submit.go index bb0bcf1..ff346db 100644 --- a/cmd/submit.go +++ b/cmd/submit.go @@ -23,25 +23,31 @@ var submitCmd = &cobra.Command{ os.Exit(1) } - machineNameParam, err := cmd.Flags().GetString("machine_name") + machineNameParam, err := cmd.Flags().GetString("machine") if err != nil { config.GlobalConfig.Logger.Error("", zap.Error(err)) os.Exit(1) } - challengeNameParam, err := cmd.Flags().GetString("challenge_name") + challengeNameParam, err := cmd.Flags().GetString("challenge") if err != nil { config.GlobalConfig.Logger.Error("", zap.Error(err)) os.Exit(1) } - fortressNameParam, err := cmd.Flags().GetString("fortress_name") + fortressNameParam, err := cmd.Flags().GetString("fortress") if err != nil { config.GlobalConfig.Logger.Error("", zap.Error(err)) os.Exit(1) } - endgameNameParam, err := cmd.Flags().GetString("endgame_name") + endgameNameParam, err := cmd.Flags().GetString("endgame") + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + + prolabNameParam, err := cmd.Flags().GetString("prolab") if err != nil { config.GlobalConfig.Logger.Error("", zap.Error(err)) os.Exit(1) @@ -69,6 +75,9 @@ var submitCmd = &cobra.Command{ } else if endgameNameParam != "" { modeType = "endgame" modeValue = endgameNameParam + } else if prolabNameParam != "" { + modeType = "prolab" + modeValue = prolabNameParam } else { modeType = "release-arena" modeValue = "" @@ -96,13 +105,10 @@ var submitCmd = &cobra.Command{ func init() { rootCmd.AddCommand(submitCmd) - submitCmd.Flags().StringP("machine_name", "m", "", "Machine Name") - submitCmd.Flags().StringP("challenge_name", "c", "", "Challenge Name") - submitCmd.Flags().StringP("fortress_name", "f", "", "Fortress Name") - submitCmd.Flags().StringP("endgame_name", "e", "", "Endgame Name") + submitCmd.Flags().StringP("machine", "m", "", "Machine Name") + submitCmd.Flags().StringP("challenge", "c", "", "Challenge Name") + submitCmd.Flags().StringP("fortress", "f", "", "Fortress Name") + submitCmd.Flags().StringP("endgame", "e", "", "Endgame Name") + submitCmd.Flags().StringP("prolab", "p", "", "Prolab Name") submitCmd.Flags().IntP("difficulty", "d", 0, "Difficulty") - // err := submitCmd.MarkFlagRequired("difficulty") - // if err != nil { - // fmt.Println(err) - // } } diff --git a/lib/submit/submit.go b/lib/submit/submit.go index 9ac77dc..9d8af8d 100644 --- a/lib/submit/submit.go +++ b/lib/submit/submit.go @@ -78,6 +78,15 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri config.GlobalConfig.Logger.Debug(fmt.Sprintf("Endgame ID : %d", endgameID)) url = fmt.Sprintf("%s/endgame/%d/flag", config.BaseHackTheBoxAPIURL, endgameID) payload = map[string]string{} + } else if modeType == "prolab" { + config.GlobalConfig.Logger.Info("Prolab submit requested") + prolabID, err := utils.SearchProlabID(modeValue) + if err != nil { + return "", err + } + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Prolab ID : %d", prolabID)) + url = fmt.Sprintf("%s/prolab/%d/flag", config.BaseHackTheBoxAPIURL, prolabID) + payload = map[string]string{} } else if modeType == "release-arena" { config.GlobalConfig.Logger.Info("Release Arena submit requested") isConfirmed := utils.AskConfirmation("Would you like to submit a flag for the release arena ?") diff --git a/lib/utils/models.go b/lib/utils/models.go index 11891d8..250947f 100644 --- a/lib/utils/models.go +++ b/lib/utils/models.go @@ -35,15 +35,30 @@ type JsonResponse struct { } // Endgames - type Endgame struct { ID int `json:"id"` Name string `json:"name"` } -// Structure pour représenter le JSON entier type EndgameJsonResponse struct { Status bool `json:"status"` Data []Endgame `json:"data"` } +// Prolabs + +type Lab struct { + ID int `json:"id"` + Name string `json:"name"` +} + +// Structure pour représenter la section 'data' du JSON +type Data struct { + Labs []Lab `json:"labs"` +} + +// Structure pour représenter le JSON entier +type ProlabJsonResponse struct { + Status bool `json:"status"` + Data Data `json:"data"` +} diff --git a/lib/utils/utils.go b/lib/utils/utils.go index 33a81d3..dedcb8e 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -561,6 +561,7 @@ func SearchFortressID(partialName string) (int, error) { if isConfirmed { return namesAndIDs[matchedName], nil } + os.Exit(0) } return 0, nil } @@ -606,6 +607,53 @@ func SearchEndgameID(partialName string) (int, error) { if isConfirmed { return namesAndIDs[matchedName], nil } + os.Exit(0) + } + return 0, nil +} + +func SearchProlabID(partialName string) (int, error) { + url := fmt.Sprintf("%s/prolabs", config.BaseHackTheBoxAPIURL) + resp, err := HtbRequest(http.MethodGet, url, nil) + if err != nil { + return 0, err + } + jsonData, _ := io.ReadAll(resp.Body) + namesAndIDs, err := extractProlabsNamesAndIDs(string(jsonData)) + if err != nil { + fmt.Println("Error parsing JSON:", err) + return 0, nil + } + + var names []string + for name := range namesAndIDs { + names = append(names, name) + } + + matches := fuzzy.Find(partialName, names) + + for _, match := range matches { + matchedName := names[match.Index] + isConfirmed := AskConfirmation("The following prolab was found : " + matchedName) + if isConfirmed { + return namesAndIDs[matchedName], nil + } + os.Exit(0) } return 0, nil } + +func extractProlabsNamesAndIDs(jsonData string) (map[string]int, error) { + var response ProlabJsonResponse + err := json.Unmarshal([]byte(jsonData), &response) + if err != nil { + return nil, err + } + + namesAndIDs := make(map[string]int) + for _, lab := range response.Data.Labs { + namesAndIDs[lab.Name] = lab.ID + } + + return namesAndIDs, nil +} From 92429764d3906f6b8eebc1727febe18408e2565d Mon Sep 17 00:00:00 2001 From: github Date: Thu, 11 Jan 2024 12:31:47 +0100 Subject: [PATCH 11/42] Add extension message when 2h left --- cmd/info.go | 66 +++++++++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/cmd/info.go b/cmd/info.go index 37e5492..5da7bd2 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -232,42 +232,48 @@ func displayActiveMachine(header string) error { now := time.Now() config.GlobalConfig.Logger.Debug(fmt.Sprintf("Actual date: %v", now)) - var remainingTime string - if date.After(now) { - duration := date.Sub(now) - hours := int(duration.Hours()) - minutes := int(duration.Minutes()) % 60 - seconds := int(duration.Seconds()) % 60 - - remainingTime = fmt.Sprintf("%dh %dm %ds", hours, minutes, seconds) - } - // Extend time - isConfirmed := utils.AskConfirmation(fmt.Sprintf("Would you like to extend the active machine time ? Remaining: %s", remainingTime)) - if isConfirmed { - jsonData := []byte("{\"machine_id\":" + machineID + "}") - resp, err := utils.HtbRequest(http.MethodPost, config.BaseHackTheBoxAPIURL+"/vm/extend", jsonData) - if err != nil { - return err - } - var response Response - if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { - return fmt.Errorf("Error decoding JSON response: %v", err) - } - inputLayout := time.RFC3339Nano + timeLeft := date.Sub(now) + limit := 2 * time.Hour + if timeLeft > 0 && timeLeft <= limit { + var remainingTime string + if date.After(now) { + duration := date.Sub(now) + hours := int(duration.Hours()) + minutes := int(duration.Minutes()) % 60 + seconds := int(duration.Seconds()) % 60 + + remainingTime = fmt.Sprintf("%dh %dm %ds", hours, minutes, seconds) - date, err := time.Parse(inputLayout, response.ExpiresAt) - if err != nil { - return fmt.Errorf("Error decoding JSON response: %v", err) } + // Extend time + isConfirmed := utils.AskConfirmation(fmt.Sprintf("Would you like to extend the active machine time ? Remaining: %s", remainingTime)) + if isConfirmed { + jsonData := []byte("{\"machine_id\":" + machineID + "}") + resp, err := utils.HtbRequest(http.MethodPost, config.BaseHackTheBoxAPIURL+"/vm/extend", jsonData) + if err != nil { + return err + } + var response Response + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return fmt.Errorf("error decoding JSON response: %v", err) + } + + inputLayout := time.RFC3339Nano - outputLayout := "2006-01-02 -> 15h 04m 05s" + date, err := time.Parse(inputLayout, response.ExpiresAt) + if err != nil { + return fmt.Errorf("error decoding JSON response: %v", err) + } - formattedDate := date.Format(outputLayout) + outputLayout := "2006-01-02 -> 15h 04m 05s" - fmt.Println(response.Message) - fmt.Printf("Expires Date: %s\n", formattedDate) + formattedDate := date.Format(outputLayout) + fmt.Println(response.Message) + fmt.Printf("Expires Date: %s\n", formattedDate) + + } } tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', tabwriter.Debug) @@ -321,7 +327,7 @@ func displayActiveMachine(header string) error { utils.SetTabWriterData(w, bodyData) w.Flush() } else { - fmt.Print("No machine is running") + fmt.Println("No machine is running") } return nil } From 93784f9bb6d9b2f8d34e728c7c0b2fb509908da7 Mon Sep 17 00:00:00 2001 From: github Date: Tue, 16 Jan 2024 22:02:20 +0100 Subject: [PATCH 12/42] Change from pgrep to grep --- lib/vpn/vpn.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/vpn/vpn.go b/lib/vpn/vpn.go index 70b6cb6..4f5ebbe 100644 --- a/lib/vpn/vpn.go +++ b/lib/vpn/vpn.go @@ -69,7 +69,7 @@ func downloadVPN(url string) error { } else if strings.Contains(url, "product=competitive") { parts := strings.Split(vpnName, "_") - if len(parts) > 1 { + if len(parts) > 1 && !strings.Contains(vpnName, "Release_Arena") { parts[1] = "Release_Arena" } @@ -137,11 +137,11 @@ func DownloadAll() error { // Start starts the VPN connection using an OpenVPN configuration file. func Start(configPath string) (string, error) { - config.GlobalConfig.Logger.Debug(fmt.Sprintf("VPN config file : %s", configPath)) files, err := filepath.Glob(configPath) if err != nil { return "", fmt.Errorf("search error : %v", err) } + config.GlobalConfig.Logger.Debug(fmt.Sprintf("VPN config file : %s", files)) if len(files) == 0 { isConfirmed := utils.AskConfirmation("VPN was not found. Would you like to download it ?") if isConfirmed { @@ -152,7 +152,7 @@ func Start(configPath string) (string, error) { } } config.GlobalConfig.Logger.Info("VPN is starting...") - cmd := "pgrep -fa openvpn" + cmd := "ps aux | grep '[o]penvpn'" hacktheboxFound := false processes, err := exec.Command("sh", "-c", cmd).Output() if err != nil { @@ -171,7 +171,9 @@ func Start(configPath string) (string, error) { } parts := strings.Fields(line) + config.GlobalConfig.Logger.Debug(fmt.Sprintf("parts: %v", parts)) processPath := parts[len(parts)-1] + config.GlobalConfig.Logger.Debug(fmt.Sprintf("processPath: %v", processPath)) if _, found := uniquePaths[processPath]; found { continue From f7845a5785bef8cd88a433fc31682e0a6397e0da Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Wed, 17 Jan 2024 21:12:07 +0100 Subject: [PATCH 13/42] Add fuzzy finder for challenges --- cmd/info.go | 23 +++++++++++++--- lib/utils/models.go | 11 ++++++++ lib/utils/utils.go | 66 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 4 deletions(-) diff --git a/cmd/info.go b/cmd/info.go index 5da7bd2..e37fbb1 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "os" + "strconv" "strings" "text/tabwriter" "time" @@ -44,11 +45,25 @@ func fetchAndDisplayInfo(url, header string, params []string, elementType string w := utils.SetTabWriterHeader(header) // Iteration on all machines / challenges / users argument + var itemID string for _, param := range params { - itemID, err := utils.SearchItemIDByName(param, elementType) - if err != nil { - fmt.Println(err) - return nil + if elementType == "Challenge" { + config.GlobalConfig.Logger.Info("Challenge search...") + challenges, err := utils.SearchChallengeByName(param) + if err != nil { + return err + } + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Challenge found: %v", challenges)) + + // TODO: get this int + itemID = strconv.Itoa(challenges.ID) + } else { + itemID, err := utils.SearchItemIDByName(param, elementType) + _ = itemID + if err != nil { + fmt.Println(err) + return nil + } } resp, err := utils.HtbRequest(http.MethodGet, (url + itemID), nil) diff --git a/lib/utils/models.go b/lib/utils/models.go index 250947f..a690cee 100644 --- a/lib/utils/models.go +++ b/lib/utils/models.go @@ -62,3 +62,14 @@ type ProlabJsonResponse struct { Status bool `json:"status"` Data Data `json:"data"` } + +// Challenges + +type ChallengeFinder struct { + ID int `json:"id"` + Name string `json:"name"` +} + +type ChallengeResponseFinder struct { + ChallengesFinder []ChallengeFinder `json:"challenges"` +} diff --git a/lib/utils/utils.go b/lib/utils/utils.go index dedcb8e..aa9914e 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -14,6 +14,7 @@ import ( "os/user" "strconv" "strings" + "sync" "syscall" "text/tabwriter" "time" @@ -657,3 +658,68 @@ func extractProlabsNamesAndIDs(jsonData string) (map[string]int, error) { return namesAndIDs, nil } + +// Fuzzy finder for challenges + +func SearchChallengeByName(partialName string) (ChallengeFinder, error) { + var wg sync.WaitGroup + respChan := make(chan *http.Response, 2) + var allChallenges []ChallengeFinder + + wg.Add(2) + go sendRequest(fmt.Sprintf("%s/challenge/list", config.BaseHackTheBoxAPIURL), respChan, &wg) + go sendRequest(fmt.Sprintf("%s/challenge/list/retired", config.BaseHackTheBoxAPIURL), respChan, &wg) + + go func() { + wg.Wait() + close(respChan) + }() + + for resp := range respChan { + challenges := processResponse(resp) + allChallenges = append(allChallenges, challenges...) + } + + challengeNames := make([]string, len(allChallenges)) + for i, ch := range allChallenges { + challengeNames[i] = ch.Name + } + + matches := fuzzy.Find(partialName, challengeNames) + + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Challenges matches: %v", matches)) + + for _, match := range matches { + matchedName := challengeNames[match.Index] + isConfirmed := AskConfirmation("The following challenge was found : " + matchedName) + if isConfirmed { + for _, ch := range allChallenges { + if ch.Name == matchedName { + return ch, nil + } + } + } + } + + return ChallengeFinder{}, fmt.Errorf("no matching challenge found") +} + +func processResponse(resp *http.Response) []ChallengeFinder { + defer resp.Body.Close() + var cr ChallengeResponseFinder + if err := json.NewDecoder(resp.Body).Decode(&cr); err != nil { + fmt.Errorf("JSON decoding error %v", err) + os.Exit(1) + } + return cr.ChallengesFinder +} + +func sendRequest(url string, respChan chan<- *http.Response, wg *sync.WaitGroup) { + defer wg.Done() + resp, err := HtbRequest(http.MethodGet, url, nil) + if err != nil { + fmt.Errorf("error sending request : %v", err) + os.Exit(1) + } + respChan <- resp +} From 4f0447edc75e25c61b3a453f52d716e3edd8b9d4 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Wed, 17 Jan 2024 21:33:26 +0100 Subject: [PATCH 14/42] Implement fuzzy search for submit challenges --- lib/submit/submit.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/submit/submit.go b/lib/submit/submit.go index 9d8af8d..45a30be 100644 --- a/lib/submit/submit.go +++ b/lib/submit/submit.go @@ -29,10 +29,15 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri if modeType == "challenge" { config.GlobalConfig.Logger.Info("Challenge submit requested") - challengeID, err := utils.SearchItemIDByName(modeValue, "Challenge") + challenges, err := utils.SearchChallengeByName(modeValue) if err != nil { return "", err } + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Challenge found: %v", challenges)) + + // TODO: get this int + challengeID := strconv.Itoa(challenges.ID) + url = config.BaseHackTheBoxAPIURL + "/challenge/own" payload = map[string]string{ "difficulty": difficultyString, From 6f39aee4900240c0e42043f3435b19f2aa51beea Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Wed, 17 Jan 2024 22:59:20 +0100 Subject: [PATCH 15/42] Fix get ip from season machine --- cmd/start.go | 28 +++++++++++++++++++++++++++- lib/utils/utils.go | 18 ++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/cmd/start.go b/cmd/start.go index a029ea6..0ccfc2b 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -102,7 +102,33 @@ func coreStartCmd(machineChoosen string, machineID string) (string, error) { ip := "Undefined" switch { - case userSubscription == "vip+" || machineType == "release": + case machineType == "release": + s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) + setupSignalHandler(s) + s.Suffix = " Waiting for the machine to start in order to fetch the IP address (this might take a while)." + s.Start() + defer s.Stop() + timeout := time.After(10 * time.Minute) + LoopRelease: + for { + select { + case <-timeout: + fmt.Println("Timeout (10 min) ! Exiting") + s.Stop() + return "", nil + default: + ip, err = utils.GetActiveReleaseArenaMachineIP() + if err != nil { + return "", err + } + if ip != "Undefined" { + s.Stop() + break LoopRelease + } + time.Sleep(6 * time.Second) + } + } + case userSubscription == "vip+": s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) setupSignalHandler(s) s.Suffix = " Waiting for the machine to start in order to fetch the IP address (this might take a while)." diff --git a/lib/utils/utils.go b/lib/utils/utils.go index aa9914e..7c54732 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -331,6 +331,24 @@ func GetActiveMachineIP() (string, error) { return "Undefined", nil } +// GetActiveReleaseArenaMachineIP returns the ip of the active release arena machine +func GetActiveReleaseArenaMachineIP() (string, error) { + url := fmt.Sprintf("%s/season/machine/active", config.BaseHackTheBoxAPIURL) + resp, err := HtbRequest(http.MethodGet, url, nil) + if err != nil { + return "", err + } + data := ParseJsonMessage(resp, "data") + if data == nil { + return "", err + } + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Relase arena active machine informations: %v", data)) + if ipValue, ok := data.(map[string]interface{})["ip"].(string); ok { + return ipValue, nil + } + return "Undefined", nil +} + // HtbRequest makes an HTTP request to the Hackthebox API func HtbRequest(method string, urlParam string, jsonData []byte) (*http.Response, error) { s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) From 4d1cf385d7671691ddf3af868c38f56a40e85aa3 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Wed, 17 Jan 2024 23:11:12 +0100 Subject: [PATCH 16/42] Fix info from release arena machine --- cmd/info.go | 7 ++++++- lib/utils/utils.go | 6 ++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cmd/info.go b/cmd/info.go index e37fbb1..55cac49 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -325,7 +325,12 @@ func displayActiveMachine(header string) error { ip := "Undefined" _ = ip switch { - case userSubscription == "vip+" || machineType == "release": + case machineType == "release": + ip, err = utils.GetActiveReleaseArenaMachineIP() + if err != nil { + return err + } + case userSubscription == "vip+": ip, err = utils.GetActiveMachineIP() if err != nil { return err diff --git a/lib/utils/utils.go b/lib/utils/utils.go index 7c54732..697d0d7 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -343,10 +343,8 @@ func GetActiveReleaseArenaMachineIP() (string, error) { return "", err } config.GlobalConfig.Logger.Debug(fmt.Sprintf("Relase arena active machine informations: %v", data)) - if ipValue, ok := data.(map[string]interface{})["ip"].(string); ok { - return ipValue, nil - } - return "Undefined", nil + + return fmt.Sprintf("%v", data.(map[string]interface{})["ip"].(string)), nil } // HtbRequest makes an HTTP request to the Hackthebox API From 2a11f58d2c7c6b006b62e8170ebc4b978638b497 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Wed, 17 Jan 2024 23:24:49 +0100 Subject: [PATCH 17/42] Fix crash when show active release arena machine informations --- cmd/info.go | 20 +++++++++++++++++++- lib/utils/utils.go | 16 ++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/cmd/info.go b/cmd/info.go index 55cac49..ee129f6 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -228,10 +228,27 @@ func displayActiveMachine(header string) error { if err != nil { return err } - expiresTime, err := utils.GetActiveExpiredTime() + machineType, err := utils.GetMachineType(machineID) if err != nil { return err } + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Machine Type: %s", machineType)) + + var expiresTime string + switch { + case machineType == "release": + expiresTime, err = utils.GetReleaseArenaExpiredTime() + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Expires Time: %s", expiresTime)) + if err != nil { + return err + } + default: + expiresTime, err = utils.GetActiveExpiredTime() + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Expires Time:: %s", expiresTime)) + if err != nil { + return err + } + } if machineID != "" { config.GlobalConfig.Logger.Info("Active machine found !") @@ -300,6 +317,7 @@ func displayActiveMachine(header string) error { return err } info := utils.ParseJsonMessage(resp, "info") + // info := utils.ParseJsonMessage(resp, "data") data := info.(map[string]interface{}) status := utils.SetStatus(data) diff --git a/lib/utils/utils.go b/lib/utils/utils.go index 697d0d7..1602851 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -313,6 +313,22 @@ func GetActiveExpiredTime() (string, error) { return fmt.Sprintf("%s", info.(map[string]interface{})["expires_at"]), nil } +func GetReleaseArenaExpiredTime() (string, error) { + url := fmt.Sprintf("%s/season/machine/active", config.BaseHackTheBoxAPIURL) + resp, err := HtbRequest(http.MethodGet, url, nil) + if err != nil { + return "", err + } + info := ParseJsonMessage(resp, "data") + if info == nil { + return "", nil + } + data := info.(map[string]interface{}) + playInfo := data["play_info"].(map[string]interface{}) + expiresAt := playInfo["expires_at"].(string) + return expiresAt, nil +} + // GetActiveMachineIP returns the ip of the active machine func GetActiveMachineIP() (string, error) { url := fmt.Sprintf("%s/machine/active", config.BaseHackTheBoxAPIURL) From 60f65960757fe2d52843b161d83a43544fdd4cfe Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Wed, 17 Jan 2024 23:27:33 +0100 Subject: [PATCH 18/42] Fix lint --- lib/utils/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/utils/utils.go b/lib/utils/utils.go index 1602851..7aa3280 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -740,7 +740,7 @@ func processResponse(resp *http.Response) []ChallengeFinder { defer resp.Body.Close() var cr ChallengeResponseFinder if err := json.NewDecoder(resp.Body).Decode(&cr); err != nil { - fmt.Errorf("JSON decoding error %v", err) + fmt.Println("JSON decoding error %v", err) os.Exit(1) } return cr.ChallengesFinder @@ -750,7 +750,7 @@ func sendRequest(url string, respChan chan<- *http.Response, wg *sync.WaitGroup) defer wg.Done() resp, err := HtbRequest(http.MethodGet, url, nil) if err != nil { - fmt.Errorf("error sending request : %v", err) + fmt.Println("error sending request : %v", err) os.Exit(1) } respChan <- resp From 7f862df237727a1a45cebc9ba750cdfe13f2f429 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Wed, 17 Jan 2024 23:28:39 +0100 Subject: [PATCH 19/42] Fix lint --- lib/utils/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/utils/utils.go b/lib/utils/utils.go index 7aa3280..5169129 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -740,7 +740,7 @@ func processResponse(resp *http.Response) []ChallengeFinder { defer resp.Body.Close() var cr ChallengeResponseFinder if err := json.NewDecoder(resp.Body).Decode(&cr); err != nil { - fmt.Println("JSON decoding error %v", err) + fmt.Printf("JSON decoding error %v", err) os.Exit(1) } return cr.ChallengesFinder @@ -750,7 +750,7 @@ func sendRequest(url string, respChan chan<- *http.Response, wg *sync.WaitGroup) defer wg.Done() resp, err := HtbRequest(http.MethodGet, url, nil) if err != nil { - fmt.Println("error sending request : %v", err) + fmt.Printf("error sending request : %v", err) os.Exit(1) } respChan <- resp From ba489d756ab4bc72d46ccb28969e83ca0de98703 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Thu, 18 Jan 2024 17:02:15 +0100 Subject: [PATCH 20/42] Start hosts command --- cmd/hosts.go | 65 ++++++++++++++++++++++++++ lib/hosts/hosts.go | 111 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 cmd/hosts.go create mode 100644 lib/hosts/hosts.go diff --git a/cmd/hosts.go b/cmd/hosts.go new file mode 100644 index 0000000..5a8a146 --- /dev/null +++ b/cmd/hosts.go @@ -0,0 +1,65 @@ +package cmd + +import ( + "os" + + "github.com/GoToolSharing/htb-cli/config" + "github.com/GoToolSharing/htb-cli/lib/hosts" + "github.com/spf13/cobra" + "go.uber.org/zap" +) + +var hostsCmd = &cobra.Command{ + Use: "hosts", + Short: "Interact with hosts file", + Run: func(cmd *cobra.Command, args []string) { + config.GlobalConfig.Logger.Info("Hosts command executed") + addParam, err := cmd.Flags().GetString("add") + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + + deleteParam, err := cmd.Flags().GetString("delete") + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + + ipParam, err := cmd.Flags().GetString("ip") + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + + if addParam != "" && deleteParam != "" { + config.GlobalConfig.Logger.Error("Only one parameter is allowed") + os.Exit(1) + } + + if addParam != "" { + err = hosts.AddEntryToHosts(ipParam, addParam) + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + } + + if deleteParam != "" { + err = hosts.RemoveEntryFromHosts(ipParam, deleteParam) + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + } + + config.GlobalConfig.Logger.Info("Exit hosts command correctly") + }, +} + +func init() { + rootCmd.AddCommand(hostsCmd) + hostsCmd.Flags().StringP("add", "a", "", "Add a new entry") + hostsCmd.Flags().StringP("ip", "i", "", "IP Address") + hostsCmd.Flags().StringP("delete", "d", "", "Delete an entry") +} diff --git a/lib/hosts/hosts.go b/lib/hosts/hosts.go new file mode 100644 index 0000000..4682d42 --- /dev/null +++ b/lib/hosts/hosts.go @@ -0,0 +1,111 @@ +package hosts + +import ( + "bufio" + "bytes" + "fmt" + "os" + "os/exec" + "strings" +) + +const hostsFile = "/etc/hosts" + +func readHostsFile(processLine func(string) (string, bool)) (string, bool, error) { + file, err := os.Open(hostsFile) + if err != nil { + return "", false, err + } + defer file.Close() + + var buffer bytes.Buffer + changeMade := false + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.TrimSpace(line) == "" { + buffer.WriteString("\n") + continue + } + + processedLine, changed := processLine(line) + if changed { + changeMade = true + } + buffer.WriteString(processedLine + "\n") + } + + return buffer.String(), changeMade, scanner.Err() +} + +func updateHostsFile(newContent string) error { + cmd := exec.Command("sh", "-c", fmt.Sprintf("echo '%s' | sudo tee /etc/hosts", newContent)) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func AddEntryToHosts(ip string, host string) error { + fmt.Println("IP :", ip) + fmt.Println("Host :", host) + processLine := func(line string) (string, bool) { + fields := strings.Fields(line) + if fields[0] != ip || strings.Contains(line, host) { + fmt.Println("Inside :", line) + return line, false + } + return line + " " + host, true + } + + newContent, changeMade, err := readHostsFile(processLine) + if err != nil { + return err + } + + fmt.Println("New content :", newContent) + + if changeMade { + if err := updateHostsFile(newContent); err != nil { + return err + } + fmt.Println("Entrée mise à jour ou ajoutée avec succès.") + return nil + } + + fmt.Println("L'entrée existe déjà.") + return nil +} + +func RemoveEntryFromHosts(ip string, host string) error { + processLine := func(line string) (string, bool) { + fields := strings.Fields(line) + if fields[0] != ip { + return line, false + } + var newFields []string + for _, field := range fields { + if field != host { + newFields = append(newFields, field) + } + } + return strings.Join(newFields, " "), len(newFields) != len(fields) + } + + newContent, changeMade, err := readHostsFile(processLine) + if err != nil { + return err + } + + if changeMade { + if err := updateHostsFile(newContent); err != nil { + return err + } + fmt.Println("Entrée supprimée avec succès.") + return nil + } + + fmt.Println("L'entrée n'a pas été trouvée.") + return nil +} From f59920bd2851d7dbdb6dd713aaa2fc0dfe198a54 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Thu, 18 Jan 2024 17:02:32 +0100 Subject: [PATCH 21/42] Fix typo --- README.md | 2 +- lib/update/update.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fb5f799..bc4f66c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@
current version - current version + current version current version
amd64 diff --git a/lib/update/update.go b/lib/update/update.go index 7cbde0b..82ef0f9 100644 --- a/lib/update/update.go +++ b/lib/update/update.go @@ -14,7 +14,7 @@ func Check(newVersion string) (string, error) { config.GlobalConfig.Logger.Debug(fmt.Sprintf("config.Version: %s", config.Version)) if config.Version == "dev" { config.GlobalConfig.Logger.Info("Development version detected") - return "Development version (git pull to update)", nil + return "Development version", nil } // Main version From 13382c0255b1ab30a7a4cea80562b6c9d2412fe3 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Thu, 18 Jan 2024 21:52:29 +0100 Subject: [PATCH 22/42] Fix hosts command --- lib/hosts/hosts.go | 75 +++++++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/lib/hosts/hosts.go b/lib/hosts/hosts.go index 4682d42..5cd077d 100644 --- a/lib/hosts/hosts.go +++ b/lib/hosts/hosts.go @@ -40,7 +40,7 @@ func readHostsFile(processLine func(string) (string, bool)) (string, bool, error } func updateHostsFile(newContent string) error { - cmd := exec.Command("sh", "-c", fmt.Sprintf("echo '%s' | sudo tee /etc/hosts", newContent)) + cmd := exec.Command("sh", "-c", fmt.Sprintf("echo '%s' | sudo tee /etc/hosts > /dev/null", newContent)) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -48,15 +48,26 @@ func updateHostsFile(newContent string) error { } func AddEntryToHosts(ip string, host string) error { - fmt.Println("IP :", ip) - fmt.Println("Host :", host) + ipFound := false + hostAdded := false + processLine := func(line string) (string, bool) { - fields := strings.Fields(line) - if fields[0] != ip || strings.Contains(line, host) { - fmt.Println("Inside :", line) + trimmedLine := strings.TrimSpace(line) + if trimmedLine == "" { return line, false } - return line + " " + host, true + + fields := strings.Fields(trimmedLine) + if fields[0] == ip { + ipFound = true + for _, field := range fields[1:] { + if field == host { + return line, false + } + } + return line + " " + host, true + } + return line, false } newContent, changeMade, err := readHostsFile(processLine) @@ -64,33 +75,51 @@ func AddEntryToHosts(ip string, host string) error { return err } - fmt.Println("New content :", newContent) + if !ipFound { + newContent = strings.TrimSpace(newContent) + "\n" + ip + " " + host + hostAdded = true + } else { + hostAdded = changeMade + } - if changeMade { - if err := updateHostsFile(newContent); err != nil { + if hostAdded { + if err := updateHostsFile(strings.TrimSpace(newContent)); err != nil { return err } - fmt.Println("Entrée mise à jour ou ajoutée avec succès.") + fmt.Println("Entry successfully updated or added.") return nil } - fmt.Println("L'entrée existe déjà.") + fmt.Println("Entry already exists.") return nil } func RemoveEntryFromHosts(ip string, host string) error { + hostRemoved := false + processLine := func(line string) (string, bool) { - fields := strings.Fields(line) - if fields[0] != ip { + trimmedLine := strings.TrimSpace(line) + if trimmedLine == "" { return line, false } - var newFields []string - for _, field := range fields { - if field != host { - newFields = append(newFields, field) + + fields := strings.Fields(trimmedLine) + if fields[0] == ip { + var newFields []string + newFields = append(newFields, ip) + + for _, field := range fields[1:] { + if field != host { + newFields = append(newFields, field) + } } + + if len(newFields) > 1 { + return strings.Join(newFields, " "), true + } + return "", true } - return strings.Join(newFields, " "), len(newFields) != len(fields) + return line, false } newContent, changeMade, err := readHostsFile(processLine) @@ -99,13 +128,17 @@ func RemoveEntryFromHosts(ip string, host string) error { } if changeMade { + hostRemoved = true + newContent = strings.TrimSpace(newContent) if err := updateHostsFile(newContent); err != nil { return err } - fmt.Println("Entrée supprimée avec succès.") + fmt.Println("Entry successfully deleted.") return nil } - fmt.Println("L'entrée n'a pas été trouvée.") + if !hostRemoved { + fmt.Println("Entry not found.") + } return nil } From cfc43eb5a25a0a3e0b86fcb9ae56de98a72bf181 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Fri, 19 Jan 2024 11:14:03 +0100 Subject: [PATCH 23/42] Retry when the server returns an error --- lib/vpn/vpn.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/vpn/vpn.go b/lib/vpn/vpn.go index 4f5ebbe..f8797a0 100644 --- a/lib/vpn/vpn.go +++ b/lib/vpn/vpn.go @@ -31,6 +31,11 @@ func downloadVPN(url string) error { return nil } + if resp.StatusCode == 500 { + fmt.Println("The server returned an error. New attempt to download the VPN.") + downloadVPN(url) + } + if resp.StatusCode != http.StatusOK { return fmt.Errorf("error: Bad status code : %d", resp.StatusCode) } From d152356af81f9cdff66baa182f52eb10984af4cf Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Fri, 19 Jan 2024 11:18:05 +0100 Subject: [PATCH 24/42] Update status code --- lib/vpn/vpn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vpn/vpn.go b/lib/vpn/vpn.go index f8797a0..61fa4cd 100644 --- a/lib/vpn/vpn.go +++ b/lib/vpn/vpn.go @@ -31,7 +31,7 @@ func downloadVPN(url string) error { return nil } - if resp.StatusCode == 500 { + if resp.StatusCode == 500 || resp.StatusCode == 400 { fmt.Println("The server returned an error. New attempt to download the VPN.") downloadVPN(url) } From d7fd6c2dfc3e062d10aa4d61c00911134329b9e0 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Fri, 19 Jan 2024 11:26:28 +0100 Subject: [PATCH 25/42] Add status code for rate limit --- lib/vpn/vpn.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/vpn/vpn.go b/lib/vpn/vpn.go index 61fa4cd..2cf7586 100644 --- a/lib/vpn/vpn.go +++ b/lib/vpn/vpn.go @@ -31,11 +31,6 @@ func downloadVPN(url string) error { return nil } - if resp.StatusCode == 500 || resp.StatusCode == 400 { - fmt.Println("The server returned an error. New attempt to download the VPN.") - downloadVPN(url) - } - if resp.StatusCode != http.StatusOK { return fmt.Errorf("error: Bad status code : %d", resp.StatusCode) } @@ -58,6 +53,16 @@ func downloadVPN(url string) error { } defer resp.Body.Close() + if resp.StatusCode == 429 { + fmt.Println("You have reached the limit for the number of requests. Please wait 1 minute and try again.") + os.Exit(0) + } + + if resp.StatusCode == 500 || resp.StatusCode == 400 { + fmt.Println("The server returned an error. New attempt to download the VPN.") + downloadVPN(url) + } + if resp.StatusCode != http.StatusOK { return fmt.Errorf("error: Bad status code : %d", resp.StatusCode) } From cad25337ac4321f4438c6e770f25f62bc00e2d0c Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Fri, 19 Jan 2024 11:59:20 +0100 Subject: [PATCH 26/42] Fix VPN download error when API lag --- lib/vpn/vpn.go | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/lib/vpn/vpn.go b/lib/vpn/vpn.go index 2cf7586..45054e5 100644 --- a/lib/vpn/vpn.go +++ b/lib/vpn/vpn.go @@ -9,14 +9,29 @@ import ( "net/http" "os" "os/exec" + "os/signal" "path/filepath" "strings" "sync" + "syscall" + "time" "github.com/GoToolSharing/htb-cli/config" "github.com/GoToolSharing/htb-cli/lib/utils" + "github.com/briandowns/spinner" ) +// setupSignalHandler configures a signal handler to stop the spinner and gracefully exit upon receiving specific signals. +func setupSignalHandler(s *spinner.Spinner) { + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + go func() { + <-sigs + s.Stop() + os.Exit(0) + }() +} + func downloadVPN(url string) error { resp, err := utils.HtbRequest(http.MethodGet, url, nil) if err != nil { @@ -24,9 +39,10 @@ func downloadVPN(url string) error { } defer resp.Body.Close() + parts := strings.Split(url, "=") + productValue := parts[len(parts)-1] + if resp.StatusCode == 401 { - parts := strings.Split(url, "=") - productValue := parts[len(parts)-1] fmt.Println("You do not have permissions to download the following vpn:", productValue) return nil } @@ -54,12 +70,14 @@ func downloadVPN(url string) error { defer resp.Body.Close() if resp.StatusCode == 429 { - fmt.Println("You have reached the limit for the number of requests. Please wait 1 minute and try again.") - os.Exit(0) + fmt.Println(fmt.Sprintf("[%s] - You have reached the limit for the number of requests. New attempt in 1 minute", productValue)) + time.Sleep(60 * time.Second) + downloadVPN(url) } - if resp.StatusCode == 500 || resp.StatusCode == 400 { + if resp.StatusCode == 500 || resp.StatusCode == 502 || resp.StatusCode == 400 { fmt.Println("The server returned an error. New attempt to download the VPN.") + time.Sleep(2 * time.Second) downloadVPN(url) } @@ -130,12 +148,6 @@ func DownloadAll() error { wg.Wait() close(errors) - for err := range errors { - if err != nil { - return err - } - } - fmt.Println("") message := fmt.Sprintf("VPNs are located at the following path : %s", config.BaseDirectory) From 6a665d8c2a6e74f3a301b47236237dc07ee36f40 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Fri, 19 Jan 2024 14:42:19 +0100 Subject: [PATCH 27/42] Add blank hosts command --- cmd/hosts.go | 71 +++++++++++++++++++++++++------------------------- lib/vpn/vpn.go | 2 +- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/cmd/hosts.go b/cmd/hosts.go index 5a8a146..60ff231 100644 --- a/cmd/hosts.go +++ b/cmd/hosts.go @@ -1,12 +1,10 @@ package cmd import ( - "os" + "fmt" "github.com/GoToolSharing/htb-cli/config" - "github.com/GoToolSharing/htb-cli/lib/hosts" "github.com/spf13/cobra" - "go.uber.org/zap" ) var hostsCmd = &cobra.Command{ @@ -14,44 +12,45 @@ var hostsCmd = &cobra.Command{ Short: "Interact with hosts file", Run: func(cmd *cobra.Command, args []string) { config.GlobalConfig.Logger.Info("Hosts command executed") - addParam, err := cmd.Flags().GetString("add") - if err != nil { - config.GlobalConfig.Logger.Error("", zap.Error(err)) - os.Exit(1) - } + fmt.Println("WIP") + // addParam, err := cmd.Flags().GetString("add") + // if err != nil { + // config.GlobalConfig.Logger.Error("", zap.Error(err)) + // os.Exit(1) + // } - deleteParam, err := cmd.Flags().GetString("delete") - if err != nil { - config.GlobalConfig.Logger.Error("", zap.Error(err)) - os.Exit(1) - } + // deleteParam, err := cmd.Flags().GetString("delete") + // if err != nil { + // config.GlobalConfig.Logger.Error("", zap.Error(err)) + // os.Exit(1) + // } - ipParam, err := cmd.Flags().GetString("ip") - if err != nil { - config.GlobalConfig.Logger.Error("", zap.Error(err)) - os.Exit(1) - } + // ipParam, err := cmd.Flags().GetString("ip") + // if err != nil { + // config.GlobalConfig.Logger.Error("", zap.Error(err)) + // os.Exit(1) + // } - if addParam != "" && deleteParam != "" { - config.GlobalConfig.Logger.Error("Only one parameter is allowed") - os.Exit(1) - } + // if addParam != "" && deleteParam != "" { + // config.GlobalConfig.Logger.Error("Only one parameter is allowed") + // os.Exit(1) + // } - if addParam != "" { - err = hosts.AddEntryToHosts(ipParam, addParam) - if err != nil { - config.GlobalConfig.Logger.Error("", zap.Error(err)) - os.Exit(1) - } - } + // if addParam != "" { + // err = hosts.AddEntryToHosts(ipParam, addParam) + // if err != nil { + // config.GlobalConfig.Logger.Error("", zap.Error(err)) + // os.Exit(1) + // } + // } - if deleteParam != "" { - err = hosts.RemoveEntryFromHosts(ipParam, deleteParam) - if err != nil { - config.GlobalConfig.Logger.Error("", zap.Error(err)) - os.Exit(1) - } - } + // if deleteParam != "" { + // err = hosts.RemoveEntryFromHosts(ipParam, deleteParam) + // if err != nil { + // config.GlobalConfig.Logger.Error("", zap.Error(err)) + // os.Exit(1) + // } + // } config.GlobalConfig.Logger.Info("Exit hosts command correctly") }, diff --git a/lib/vpn/vpn.go b/lib/vpn/vpn.go index 45054e5..e4cea9c 100644 --- a/lib/vpn/vpn.go +++ b/lib/vpn/vpn.go @@ -70,7 +70,7 @@ func downloadVPN(url string) error { defer resp.Body.Close() if resp.StatusCode == 429 { - fmt.Println(fmt.Sprintf("[%s] - You have reached the limit for the number of requests. New attempt in 1 minute", productValue)) + fmt.Printf("[%s] - You have reached the limit for the number of requests. New attempt in 1 minute\n", productValue) time.Sleep(60 * time.Second) downloadVPN(url) } From 7c717e0f0b9ec7573a0a6ae36a402a859d2a9aec Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Fri, 19 Jan 2024 14:45:26 +0100 Subject: [PATCH 28/42] Fix lint --- lib/hosts/hosts.go | 1 - lib/vpn/vpn.go | 24 ++++++++---------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/lib/hosts/hosts.go b/lib/hosts/hosts.go index 5cd077d..e095624 100644 --- a/lib/hosts/hosts.go +++ b/lib/hosts/hosts.go @@ -128,7 +128,6 @@ func RemoveEntryFromHosts(ip string, host string) error { } if changeMade { - hostRemoved = true newContent = strings.TrimSpace(newContent) if err := updateHostsFile(newContent); err != nil { return err diff --git a/lib/vpn/vpn.go b/lib/vpn/vpn.go index e4cea9c..18126a8 100644 --- a/lib/vpn/vpn.go +++ b/lib/vpn/vpn.go @@ -9,29 +9,15 @@ import ( "net/http" "os" "os/exec" - "os/signal" "path/filepath" "strings" "sync" - "syscall" "time" "github.com/GoToolSharing/htb-cli/config" "github.com/GoToolSharing/htb-cli/lib/utils" - "github.com/briandowns/spinner" ) -// setupSignalHandler configures a signal handler to stop the spinner and gracefully exit upon receiving specific signals. -func setupSignalHandler(s *spinner.Spinner) { - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - go func() { - <-sigs - s.Stop() - os.Exit(0) - }() -} - func downloadVPN(url string) error { resp, err := utils.HtbRequest(http.MethodGet, url, nil) if err != nil { @@ -72,13 +58,19 @@ func downloadVPN(url string) error { if resp.StatusCode == 429 { fmt.Printf("[%s] - You have reached the limit for the number of requests. New attempt in 1 minute\n", productValue) time.Sleep(60 * time.Second) - downloadVPN(url) + err := downloadVPN(url) + if err != nil { + return err + } } if resp.StatusCode == 500 || resp.StatusCode == 502 || resp.StatusCode == 400 { fmt.Println("The server returned an error. New attempt to download the VPN.") time.Sleep(2 * time.Second) - downloadVPN(url) + err := downloadVPN(url) + if err != nil { + return err + } } if resp.StatusCode != http.StatusOK { From 317fefaae76ec7d37c523dd1a43ce60760fe54d9 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Fri, 19 Jan 2024 16:56:06 +0100 Subject: [PATCH 29/42] Add SSH login and check if user.txt exists --- cmd/getflag.go | 42 +++++++++++++++++++++++++++++++ go.mod | 7 +++--- go.sum | 14 ++++++----- lib/ssh/ssh.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 cmd/getflag.go create mode 100644 lib/ssh/ssh.go diff --git a/cmd/getflag.go b/cmd/getflag.go new file mode 100644 index 0000000..bba1bbb --- /dev/null +++ b/cmd/getflag.go @@ -0,0 +1,42 @@ +package cmd + +import ( + "os" + + "github.com/GoToolSharing/htb-cli/config" + "github.com/GoToolSharing/htb-cli/lib/ssh" + "github.com/spf13/cobra" + "go.uber.org/zap" +) + +var getflagCmd = &cobra.Command{ + Use: "getflag", + Short: "Retrieves and submits flags from an SSH connection", + Run: func(cmd *cobra.Command, args []string) { + config.GlobalConfig.Logger.Info("Getflag command executed") + username, err := cmd.Flags().GetString("username") + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + password, err := cmd.Flags().GetString("password") + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + host, err := cmd.Flags().GetString("host") + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + ssh.Connect(username, password, host) + config.GlobalConfig.Logger.Info("Exit getflag command correctly") + }, +} + +func init() { + rootCmd.AddCommand(getflagCmd) + getflagCmd.Flags().StringP("username", "", "", "SSH username") + getflagCmd.Flags().StringP("password", "", "", "SSH password") + getflagCmd.Flags().StringP("host", "", "", "SSH host") +} diff --git a/go.mod b/go.mod index 7cf8b00..7e9a48b 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,8 @@ require ( github.com/sahilm/fuzzy v0.1.0 github.com/spf13/cobra v1.5.0 go.uber.org/zap v1.26.0 - golang.org/x/term v0.13.0 + golang.org/x/crypto v0.18.0 + golang.org/x/term v0.16.0 ) require ( @@ -30,6 +31,6 @@ require ( github.com/spf13/pflag v1.0.5 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index af6da87..46b5559 100644 --- a/go.sum +++ b/go.sum @@ -73,6 +73,8 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -91,20 +93,20 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/lib/ssh/ssh.go b/lib/ssh/ssh.go new file mode 100644 index 0000000..f6b0b6e --- /dev/null +++ b/lib/ssh/ssh.go @@ -0,0 +1,68 @@ +package ssh + +import ( + "fmt" + "strings" + + "golang.org/x/crypto/ssh" +) + +func Connect(username, password, host string) { + config := &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.Password(password), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + client, err := ssh.Dial("tcp", host, config) + if err != nil { + panic(err) + } + defer client.Close() + + session, err := client.NewSession() + if err != nil { + panic(err) + } + defer session.Close() + + cmd := "ls /home" + output, err := session.CombinedOutput(cmd) + if err != nil { + panic(err) + } + + users := strings.Split(string(output), "\n") + for _, user := range users { + if user == "" { + continue + } + checkAndReadFile(client, "/home/"+user+"/user.txt") + } +} +func checkAndReadFile(client *ssh.Client, filePath string) { + session, err := client.NewSession() + if err != nil { + panic(err) + } + defer session.Close() + + cmd := fmt.Sprintf(`if [ -f %s ]; then + if [ -r %s ]; then + cat %s; + else + echo "File '%s' exists, but no read permission"; + fi; + else + echo "File '%s' does not exist"; + fi`, filePath, filePath, filePath, filePath, filePath) + + output, err := session.CombinedOutput(cmd) + if err != nil { + panic(err) + } + + fmt.Println(string(output)) +} From 4c83c68af398e9aa96f477389e9049efd4654ca0 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Fri, 19 Jan 2024 18:00:26 +0100 Subject: [PATCH 30/42] Add blooder info for challenges --- cmd/submit.go | 2 -- lib/submit/submit.go | 19 +++++++++++++++++-- lib/utils/models.go | 20 ++++++++++++++++++++ lib/utils/utils.go | 23 +++++++++++++++++++++++ 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/cmd/submit.go b/cmd/submit.go index ff346db..5fdd4d3 100644 --- a/cmd/submit.go +++ b/cmd/submit.go @@ -92,8 +92,6 @@ var submitCmd = &cobra.Command{ os.Exit(1) } - fmt.Println(output) - err = webhooks.SendToDiscord("submit", output) if err != nil { config.GlobalConfig.Logger.Error("", zap.Error(err)) diff --git a/lib/submit/submit.go b/lib/submit/submit.go index 45a30be..a1eb822 100644 --- a/lib/submit/submit.go +++ b/lib/submit/submit.go @@ -11,6 +11,7 @@ import ( "github.com/GoToolSharing/htb-cli/config" "github.com/GoToolSharing/htb-cli/lib/utils" + "go.uber.org/zap" "golang.org/x/term" ) @@ -26,6 +27,7 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri } var url string + var challengeID string if modeType == "challenge" { config.GlobalConfig.Logger.Info("Challenge submit requested") @@ -36,7 +38,7 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri config.GlobalConfig.Logger.Debug(fmt.Sprintf("Challenge found: %v", challenges)) // TODO: get this int - challengeID := strconv.Itoa(challenges.ID) + challengeID = strconv.Itoa(challenges.ID) url = config.BaseHackTheBoxAPIURL + "/challenge/own" payload = map[string]string{ @@ -137,5 +139,18 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri if !ok { return "", errors.New("unexpected response format") } - return message, nil + + if modeType == "challenge" { + blooderName, err := utils.GetChallengeBlooder(challengeID) + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + fmt.Println("Blooder :", blooderName) + } + + fmt.Println("") + fmt.Println(message) + + return "", nil } diff --git a/lib/utils/models.go b/lib/utils/models.go index a690cee..f5aba46 100644 --- a/lib/utils/models.go +++ b/lib/utils/models.go @@ -73,3 +73,23 @@ type ChallengeFinder struct { type ChallengeResponseFinder struct { ChallengesFinder []ChallengeFinder `json:"challenges"` } + +// Activity + +type Activity struct { + CreatedAt string `json:"created_at"` + Date string `json:"date"` + DateDiff string `json:"date_diff"` + UserID int `json:"user_id"` + UserName string `json:"user_name"` + UserAvatar string `json:"user_avatar"` + Type string `json:"type"` +} + +type InfoActivity struct { + Activities []Activity `json:"activity"` +} + +type DataActivity struct { + Info InfoActivity `json:"info"` +} diff --git a/lib/utils/utils.go b/lib/utils/utils.go index 5169129..f97a03b 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -755,3 +755,26 @@ func sendRequest(url string, respChan chan<- *http.Response, wg *sync.WaitGroup) } respChan <- resp } + +func GetChallengeBlooder(challengeID string) (string, error) { + url := fmt.Sprintf("%s/challenge/activity/%s", config.BaseHackTheBoxAPIURL, challengeID) + resp, err := HtbRequest(http.MethodGet, url, nil) + if err != nil { + return "", err + } + jsonData, _ := io.ReadAll(resp.Body) + + var data DataActivity + err = json.Unmarshal([]byte(jsonData), &data) + if err != nil { + panic(err) + } + + for _, activity := range data.Info.Activities { + if activity.Type == "blood" { + return activity.UserName, nil + } + } + + return "Not defined", nil +} From 1f657418bac635473b63f028ed4deb5c99a18179 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Sun, 21 Jan 2024 12:59:39 +0100 Subject: [PATCH 31/42] Add Code of conduct + contributing files --- CODE_OF_CONDUCT.md | 128 +++++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 1 + 2 files changed, 129 insertions(+) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f9d8ad4 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +github@qu35t-mail.simplelogin.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a9cbad8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +Don't hesitate to contribute to the project with a pull request. All ideas are welcome to improve the project ! \ No newline at end of file From 896f5a4ad67a531c431e789f2845ae5e7fb21612 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Sun, 21 Jan 2024 13:03:56 +0100 Subject: [PATCH 32/42] Add pull request template file --- .github/PULL_REQUEST_TEMPLATE.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..dc709e9 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,27 @@ +# Description + +Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change. + +Fixes # (issue) + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix +- [ ] New feature +- [ ] This change requires a documentation update + +# How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration + +- [ ] Test A +- [ ] Test B + +# Checklist: + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] My changes generate no new warnings \ No newline at end of file From b6ee87b633e8bba2c9133f9dca01f1332f215927 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Mon, 22 Jan 2024 16:48:00 +0100 Subject: [PATCH 33/42] Added debug messages and improved file content detection --- cmd/getflag.go | 13 ++++--- lib/ssh/ssh.go | 97 ++++++++++++++++++++++++++++++++------------------ 2 files changed, 72 insertions(+), 38 deletions(-) diff --git a/cmd/getflag.go b/cmd/getflag.go index bba1bbb..fb88882 100644 --- a/cmd/getflag.go +++ b/cmd/getflag.go @@ -29,14 +29,19 @@ var getflagCmd = &cobra.Command{ config.GlobalConfig.Logger.Error("", zap.Error(err)) os.Exit(1) } - ssh.Connect(username, password, host) + err = ssh.Connect(username, password, host, 22) + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } config.GlobalConfig.Logger.Info("Exit getflag command correctly") }, } func init() { rootCmd.AddCommand(getflagCmd) - getflagCmd.Flags().StringP("username", "", "", "SSH username") - getflagCmd.Flags().StringP("password", "", "", "SSH password") - getflagCmd.Flags().StringP("host", "", "", "SSH host") + getflagCmd.Flags().StringP("username", "u", "", "SSH username") + getflagCmd.Flags().StringP("password", "p", "", "SSH password") + getflagCmd.Flags().StringP("port", "P", "", "(Optional) SSH Port (Default 22)") + getflagCmd.Flags().StringP("host", "", "", "(Optional) SSH host") } diff --git a/lib/ssh/ssh.go b/lib/ssh/ssh.go index f6b0b6e..d75e5d8 100644 --- a/lib/ssh/ssh.go +++ b/lib/ssh/ssh.go @@ -2,12 +2,13 @@ package ssh import ( "fmt" + "path/filepath" "strings" "golang.org/x/crypto/ssh" ) -func Connect(username, password, host string) { +func Connect(username, password, host string, port int) error { config := &ssh.ClientConfig{ User: username, Auth: []ssh.AuthMethod{ @@ -15,54 +16,82 @@ func Connect(username, password, host string) { }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } - - client, err := ssh.Dial("tcp", host, config) + connection, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", host, port), config) if err != nil { - panic(err) + return fmt.Errorf("Connection error: %s\n", err) } - defer client.Close() + defer connection.Close() + fmt.Println("Connection established") - session, err := client.NewSession() + session, err := connection.NewSession() if err != nil { - panic(err) + return fmt.Errorf("Session creation error: %s\n", err) } defer session.Close() - cmd := "ls /home" + cmd := "cat /etc/passwd | grep -E '/home|/users' | cut -d: -f6" output, err := session.CombinedOutput(cmd) if err != nil { - panic(err) + return fmt.Errorf("Error during command execution: %s\n", err) } + homes := strings.Split(string(output), "\n") + fmt.Println("Homes :", homes) - users := strings.Split(string(output), "\n") - for _, user := range users { - if user == "" { + fileFound := false + for _, home := range homes { + if home == "" { continue } - checkAndReadFile(client, "/home/"+user+"/user.txt") - } -} -func checkAndReadFile(client *ssh.Client, filePath string) { - session, err := client.NewSession() - if err != nil { - panic(err) - } - defer session.Close() + filePath := filepath.Join(home, "user.txt") + fileSession, err := connection.NewSession() + if err != nil { + fmt.Printf("Error creating file session: %s\n", err) + continue + } + cmd := fmt.Sprintf("if [ -f %s ]; then echo found; else echo not found; fi", filePath) + fileOutput, err := fileSession.CombinedOutput(cmd) + fileSession.Close() - cmd := fmt.Sprintf(`if [ -f %s ]; then - if [ -r %s ]; then - cat %s; - else - echo "File '%s' exists, but no read permission"; - fi; - else - echo "File '%s' does not exist"; - fi`, filePath, filePath, filePath, filePath, filePath) + if strings.TrimSpace(string(fileOutput)) == "found" { + fileFound = true + fmt.Printf("File found: %s\n", filePath) - output, err := session.CombinedOutput(cmd) - if err != nil { - panic(err) + contentSession, err := connection.NewSession() + if err != nil { + fmt.Printf("Error creating content session: %s\n", err) + continue + } + contentCmd := fmt.Sprintf("cat %s", filePath) + contentOutput, err := contentSession.CombinedOutput(contentCmd) + if err != nil { + fmt.Printf("File read error %s: %s\n", filePath, err) + permSession, err := connection.NewSession() + if err != nil { + fmt.Printf("Error creating permissions session: %s\n", err) + continue + } + permCmd := fmt.Sprintf("ls -la %s", filePath) + permOutput, err := permSession.CombinedOutput(permCmd) + if err != nil { + fmt.Printf("Error obtaining permissions: %s\n", err) + } else { + fmt.Printf("Permissions required: %s\n", string(permOutput)) + } + permSession.Close() + continue + } + fmt.Printf("%s: %s\n", filePath, string(contentOutput)) + if len(contentOutput) == 32 { + fmt.Println("HTB flag detected") + // TODO: auto Submission + } + contentSession.Close() + break + } } - fmt.Println(string(output)) + if !fileFound { + fmt.Println("user.txt file not found in home directories") + } + return nil } From 1a23802cb5e37f97088c518e1db7685f110ac3f2 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Mon, 22 Jan 2024 17:08:57 +0100 Subject: [PATCH 34/42] Start autosubmission feature --- lib/ssh/ssh.go | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/lib/ssh/ssh.go b/lib/ssh/ssh.go index d75e5d8..754f612 100644 --- a/lib/ssh/ssh.go +++ b/lib/ssh/ssh.go @@ -2,10 +2,14 @@ package ssh import ( "fmt" + "os" "path/filepath" + "strconv" "strings" + "github.com/GoToolSharing/htb-cli/lib/submit" "golang.org/x/crypto/ssh" + "golang.org/x/term" ) func Connect(username, password, host string, port int) error { @@ -83,7 +87,42 @@ func Connect(username, password, host string, port int) error { fmt.Printf("%s: %s\n", filePath, string(contentOutput)) if len(contentOutput) == 32 { fmt.Println("HTB flag detected") - // TODO: auto Submission + // TODO: auto submission with hostname research + hostnameSession, err := connection.NewSession() + if err != nil { + fmt.Printf("Error creating hostname session: %s\n", err) + continue + } + cmd := "hostname" + sessionOutput, err := hostnameSession.CombinedOutput(cmd) + hostnameSession.Close() + hostname := strings.ReplaceAll(string(sessionOutput), "\n", "") + fmt.Println("Hostname :", hostname) + + // machineID, err := utils.SearchItemIDByName(hostname, "Machine") + // if err != nil { + // return err + // } + + // fmt.Println("Machine ID :", machineID) + + // submit.CoreSubmitCmd(difficultyParam, modeType, modeValue) + + fmt.Print("Difficuly (1-10) : ") + difficultyByte, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + return err + } + difficultyOriginal := string(difficultyByte) + difficulty := strings.ReplaceAll(difficultyOriginal, " ", "") + difficultyInt, err := strconv.Atoi(difficulty) + if err != nil { + return err + } + submit.CoreSubmitCmd(difficultyInt, "machine", hostname) + + // config.GlobalConfig.Logger.Debug(fmt.Sprintf("Difficulty: %s", difficulty)) + } contentSession.Close() break From dc37cb089f89598574eb0ae017da59d8985ff4d4 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Sat, 27 Jan 2024 13:52:57 +0100 Subject: [PATCH 35/42] Add auto submit when user flag is found for machines and release arena --- cmd/getflag.go | 32 ++++++++++++- lib/ssh/ssh.go | 112 +++++++++++++++++++++++-------------------- lib/submit/submit.go | 29 ++++++----- 3 files changed, 109 insertions(+), 64 deletions(-) diff --git a/cmd/getflag.go b/cmd/getflag.go index fb88882..3b45dce 100644 --- a/cmd/getflag.go +++ b/cmd/getflag.go @@ -1,17 +1,20 @@ package cmd import ( + "fmt" "os" + "strings" "github.com/GoToolSharing/htb-cli/config" "github.com/GoToolSharing/htb-cli/lib/ssh" + "github.com/GoToolSharing/htb-cli/lib/submit" "github.com/spf13/cobra" "go.uber.org/zap" ) var getflagCmd = &cobra.Command{ Use: "getflag", - Short: "Retrieves and submits flags from an SSH connection", + Short: "Retrieves and submits flags from an SSH connection (linux only)", Run: func(cmd *cobra.Command, args []string) { config.GlobalConfig.Logger.Info("Getflag command executed") username, err := cmd.Flags().GetString("username") @@ -29,11 +32,36 @@ var getflagCmd = &cobra.Command{ config.GlobalConfig.Logger.Error("", zap.Error(err)) os.Exit(1) } - err = ssh.Connect(username, password, host, 22) + connection, err := ssh.Connect(username, password, host, 22) if err != nil { config.GlobalConfig.Logger.Error("", zap.Error(err)) os.Exit(1) } + userFlag, err := ssh.GetUserFlag(connection) + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + userFlag = strings.ReplaceAll(userFlag, "\n", "") + hostname, err := ssh.GetHostname(connection) + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + connection.Close() + url, payload, err := ssh.BuildSubmitStuff(hostname, userFlag) + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + + message, err := submit.SubmitFlag(url, payload) + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + fmt.Println(message) + config.GlobalConfig.Logger.Info("Exit getflag command correctly") }, } diff --git a/lib/ssh/ssh.go b/lib/ssh/ssh.go index 754f612..20d14aa 100644 --- a/lib/ssh/ssh.go +++ b/lib/ssh/ssh.go @@ -2,17 +2,15 @@ package ssh import ( "fmt" - "os" "path/filepath" - "strconv" "strings" - "github.com/GoToolSharing/htb-cli/lib/submit" + "github.com/GoToolSharing/htb-cli/config" + "github.com/GoToolSharing/htb-cli/lib/utils" "golang.org/x/crypto/ssh" - "golang.org/x/term" ) -func Connect(username, password, host string, port int) error { +func Connect(username, password, host string, port int) (*ssh.Client, error) { config := &ssh.ClientConfig{ User: username, Auth: []ssh.AuthMethod{ @@ -22,24 +20,27 @@ func Connect(username, password, host string, port int) error { } connection, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", host, port), config) if err != nil { - return fmt.Errorf("Connection error: %s\n", err) + return nil, fmt.Errorf("Connection error: %s\n", err) } - defer connection.Close() - fmt.Println("Connection established") + fmt.Println("SSH connection established") + return connection, nil +} + +func GetUserFlag(connection *ssh.Client) (string, error) { session, err := connection.NewSession() if err != nil { - return fmt.Errorf("Session creation error: %s\n", err) + return "", fmt.Errorf("Session creation error: %s\n", err) } defer session.Close() cmd := "cat /etc/passwd | grep -E '/home|/users' | cut -d: -f6" output, err := session.CombinedOutput(cmd) if err != nil { - return fmt.Errorf("Error during command execution: %s\n", err) + return "", fmt.Errorf("Error during command execution: %s\n", err) } homes := strings.Split(string(output), "\n") - fmt.Println("Homes :", homes) + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Users homes : %v", homes)) fileFound := false for _, home := range homes { @@ -58,7 +59,7 @@ func Connect(username, password, host string, port int) error { if strings.TrimSpace(string(fileOutput)) == "found" { fileFound = true - fmt.Printf("File found: %s\n", filePath) + config.GlobalConfig.Logger.Debug(fmt.Sprintf("User flag found: %s\n", filePath)) contentSession, err := connection.NewSession() if err != nil { @@ -85,44 +86,9 @@ func Connect(username, password, host string, port int) error { continue } fmt.Printf("%s: %s\n", filePath, string(contentOutput)) - if len(contentOutput) == 32 { - fmt.Println("HTB flag detected") - // TODO: auto submission with hostname research - hostnameSession, err := connection.NewSession() - if err != nil { - fmt.Printf("Error creating hostname session: %s\n", err) - continue - } - cmd := "hostname" - sessionOutput, err := hostnameSession.CombinedOutput(cmd) - hostnameSession.Close() - hostname := strings.ReplaceAll(string(sessionOutput), "\n", "") - fmt.Println("Hostname :", hostname) - - // machineID, err := utils.SearchItemIDByName(hostname, "Machine") - // if err != nil { - // return err - // } - - // fmt.Println("Machine ID :", machineID) - - // submit.CoreSubmitCmd(difficultyParam, modeType, modeValue) - - fmt.Print("Difficuly (1-10) : ") - difficultyByte, err := term.ReadPassword(int(os.Stdin.Fd())) - if err != nil { - return err - } - difficultyOriginal := string(difficultyByte) - difficulty := strings.ReplaceAll(difficultyOriginal, " ", "") - difficultyInt, err := strconv.Atoi(difficulty) - if err != nil { - return err - } - submit.CoreSubmitCmd(difficultyInt, "machine", hostname) - - // config.GlobalConfig.Logger.Debug(fmt.Sprintf("Difficulty: %s", difficulty)) - + if len(contentOutput) == 32 || len(contentOutput) == 33 { + config.GlobalConfig.Logger.Info("HTB flag detected") + return (string(contentOutput)), nil } contentSession.Close() break @@ -132,5 +98,49 @@ func Connect(username, password, host string, port int) error { if !fileFound { fmt.Println("user.txt file not found in home directories") } - return nil + return "", nil +} + +func GetHostname(connection *ssh.Client) (string, error) { + hostnameSession, err := connection.NewSession() + if err != nil { + return "", fmt.Errorf("Error creating hostname session: %s\n", err) + } + cmd := "hostname" + sessionOutput, err := hostnameSession.CombinedOutput(cmd) + hostnameSession.Close() + hostname := strings.ReplaceAll(string(sessionOutput), "\n", "") + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Hotname: %s", hostname)) + return hostname, nil +} + +func BuildSubmitStuff(hostname string, userFlag string) (string, map[string]string, error) { + // Can be release arena or machine + var payload map[string]string + var url string + + machineID, err := utils.SearchItemIDByName(hostname, "Machine") + if err != nil { + return "", nil, err + } + machineType, err := utils.GetMachineType(machineID) + if err != nil { + return "", nil, err + } + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Machine Type: %s", machineType)) + + if machineType == "release" { + url = config.BaseHackTheBoxAPIURL + "/arena/own" + payload = map[string]string{ + "flag": userFlag, + } + } else { + url = config.BaseHackTheBoxAPIURL + "/machine/own" + payload = map[string]string{ + "id": machineID, + "flag": userFlag, + } + } + + return url, payload, nil } diff --git a/lib/submit/submit.go b/lib/submit/submit.go index 45a30be..3a9c573 100644 --- a/lib/submit/submit.go +++ b/lib/submit/submit.go @@ -14,6 +14,23 @@ import ( "golang.org/x/term" ) +func SubmitFlag(url string, payload map[string]string) (string, error) { + jsonData, err := json.Marshal(payload) + if err != nil { + return "", fmt.Errorf("failed to create JSON data: %w", err) + } + resp, err := utils.HtbRequest(http.MethodPost, url, jsonData) + if err != nil { + return "", err + } + + message, ok := utils.ParseJsonMessage(resp, "message").(string) + if !ok { + return "", errors.New("unexpected response format") + } + return message, nil +} + // coreSubmitCmd handles the submission of flags for machines or challenges, returning a status message or error. func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (string, error) { var payload map[string]string @@ -123,19 +140,9 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri payload["flag"] = flag - jsonData, err := json.Marshal(payload) - if err != nil { - return "", fmt.Errorf("failed to create JSON data: %w", err) - } - - resp, err := utils.HtbRequest(http.MethodPost, url, jsonData) + message, err := SubmitFlag(url, payload) if err != nil { return "", err } - - message, ok := utils.ParseJsonMessage(resp, "message").(string) - if !ok { - return "", errors.New("unexpected response format") - } return message, nil } From 298536f45d6f0317c5a2e2b9e6529b6389be99d6 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Sat, 27 Jan 2024 13:58:42 +0100 Subject: [PATCH 36/42] Add custom port --- cmd/getflag.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/getflag.go b/cmd/getflag.go index 3b45dce..9eed163 100644 --- a/cmd/getflag.go +++ b/cmd/getflag.go @@ -32,7 +32,12 @@ var getflagCmd = &cobra.Command{ config.GlobalConfig.Logger.Error("", zap.Error(err)) os.Exit(1) } - connection, err := ssh.Connect(username, password, host, 22) + port, err := cmd.Flags().GetInt("port") + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + connection, err := ssh.Connect(username, password, host, port) if err != nil { config.GlobalConfig.Logger.Error("", zap.Error(err)) os.Exit(1) @@ -70,6 +75,6 @@ func init() { rootCmd.AddCommand(getflagCmd) getflagCmd.Flags().StringP("username", "u", "", "SSH username") getflagCmd.Flags().StringP("password", "p", "", "SSH password") - getflagCmd.Flags().StringP("port", "P", "", "(Optional) SSH Port (Default 22)") + getflagCmd.Flags().IntP("port", "P", 22, "(Optional) SSH Port (Default 22)") getflagCmd.Flags().StringP("host", "", "", "(Optional) SSH host") } From ff01f6bfc91c10a57faaea9ab492b8e5fe214dd8 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Sat, 27 Jan 2024 14:08:29 +0100 Subject: [PATCH 37/42] Remove difficulty flag for machines + RA --- cmd/submit.go | 4 +++- lib/submit/submit.go | 19 ++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/cmd/submit.go b/cmd/submit.go index 5fdd4d3..f8b24bd 100644 --- a/cmd/submit.go +++ b/cmd/submit.go @@ -53,7 +53,7 @@ var submitCmd = &cobra.Command{ os.Exit(1) } - if challengeNameParam != "" || machineNameParam != "" { + if challengeNameParam != "" { if difficultyParam == 0 { fmt.Println("required flag(s) 'difficulty' not set") os.Exit(1) @@ -92,6 +92,8 @@ var submitCmd = &cobra.Command{ os.Exit(1) } + fmt.Println(output) + err = webhooks.SendToDiscord("submit", output) if err != nil { config.GlobalConfig.Logger.Error("", zap.Error(err)) diff --git a/lib/submit/submit.go b/lib/submit/submit.go index 2f0ae35..db83137 100644 --- a/lib/submit/submit.go +++ b/lib/submit/submit.go @@ -35,18 +35,17 @@ func SubmitFlag(url string, payload map[string]string) (string, error) { func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (string, error) { var payload map[string]string var difficultyString string - if difficultyParam != 0 { - if difficultyParam < 1 || difficultyParam > 10 { - return "", errors.New("difficulty must be set between 1 and 10") - } - difficultyString = strconv.Itoa(difficultyParam * 10) - } - var url string var challengeID string if modeType == "challenge" { config.GlobalConfig.Logger.Info("Challenge submit requested") + if difficultyParam != 0 { + if difficultyParam < 1 || difficultyParam > 10 { + return "", errors.New("difficulty must be set between 1 and 10") + } + difficultyString = strconv.Itoa(difficultyParam * 10) + } challenges, err := utils.SearchChallengeByName(modeValue) if err != nil { return "", err @@ -80,8 +79,7 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri } payload = map[string]string{ - "difficulty": difficultyString, - "id": machineID, + "id": machineID, } } else if modeType == "fortress" { config.GlobalConfig.Logger.Info("Fortress submit requested") @@ -123,8 +121,7 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri config.GlobalConfig.Logger.Debug(fmt.Sprintf("Release Arena ID : %s", releaseID)) url = fmt.Sprintf("%s/arena/own", config.BaseHackTheBoxAPIURL) payload = map[string]string{ - "difficulty": difficultyString, - "id": releaseID, + "id": releaseID, } } From bdf9a59cd7300287be8fa09203569cf2f674cfe8 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Sat, 27 Jan 2024 14:10:15 +0100 Subject: [PATCH 38/42] Fix lint --- lib/ssh/ssh.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/ssh/ssh.go b/lib/ssh/ssh.go index 20d14aa..a265502 100644 --- a/lib/ssh/ssh.go +++ b/lib/ssh/ssh.go @@ -55,6 +55,9 @@ func GetUserFlag(connection *ssh.Client) (string, error) { } cmd := fmt.Sprintf("if [ -f %s ]; then echo found; else echo not found; fi", filePath) fileOutput, err := fileSession.CombinedOutput(cmd) + if err != nil { + return "", err + } fileSession.Close() if strings.TrimSpace(string(fileOutput)) == "found" { @@ -108,6 +111,9 @@ func GetHostname(connection *ssh.Client) (string, error) { } cmd := "hostname" sessionOutput, err := hostnameSession.CombinedOutput(cmd) + if err != nil { + return "", err + } hostnameSession.Close() hostname := strings.ReplaceAll(string(sessionOutput), "\n", "") config.GlobalConfig.Logger.Debug(fmt.Sprintf("Hotname: %s", hostname)) From c923a383024d27c455ed7ecece3824b3968507ec Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Sat, 27 Jan 2024 14:28:09 +0100 Subject: [PATCH 39/42] Fix info search error --- cmd/info.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cmd/info.go b/cmd/info.go index ee129f6..fe6c946 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -58,12 +58,7 @@ func fetchAndDisplayInfo(url, header string, params []string, elementType string // TODO: get this int itemID = strconv.Itoa(challenges.ID) } else { - itemID, err := utils.SearchItemIDByName(param, elementType) - _ = itemID - if err != nil { - fmt.Println(err) - return nil - } + itemID, _ = utils.SearchItemIDByName(param, elementType) } resp, err := utils.HtbRequest(http.MethodGet, (url + itemID), nil) From 225087617a09ab57a6ec37ff2d57641ac8c10615 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Sat, 27 Jan 2024 14:48:02 +0100 Subject: [PATCH 40/42] Add achievement link --- cmd/submit.go | 10 +++++++++- lib/submit/submit.go | 40 ++++++++++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/cmd/submit.go b/cmd/submit.go index f8b24bd..4387875 100644 --- a/cmd/submit.go +++ b/cmd/submit.go @@ -86,7 +86,7 @@ var submitCmd = &cobra.Command{ config.GlobalConfig.Logger.Debug(fmt.Sprintf("Mode type: %s", modeType)) config.GlobalConfig.Logger.Debug(fmt.Sprintf("Mode value: %s", modeValue)) - output, err := submit.CoreSubmitCmd(difficultyParam, modeType, modeValue) + output, machineID, err := submit.CoreSubmitCmd(difficultyParam, modeType, modeValue) if err != nil { config.GlobalConfig.Logger.Error("", zap.Error(err)) os.Exit(1) @@ -94,6 +94,14 @@ var submitCmd = &cobra.Command{ fmt.Println(output) + link, err := submit.GetAchievementLink(machineID) + if err != nil { + config.GlobalConfig.Logger.Error("", zap.Error(err)) + os.Exit(1) + } + + fmt.Println(link) + err = webhooks.SendToDiscord("submit", output) if err != nil { config.GlobalConfig.Logger.Error("", zap.Error(err)) diff --git a/lib/submit/submit.go b/lib/submit/submit.go index db83137..2bf2734 100644 --- a/lib/submit/submit.go +++ b/lib/submit/submit.go @@ -32,23 +32,24 @@ func SubmitFlag(url string, payload map[string]string) (string, error) { } // coreSubmitCmd handles the submission of flags for machines or challenges, returning a status message or error. -func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (string, error) { +func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (string, string, error) { var payload map[string]string var difficultyString string var url string var challengeID string + var mID string if modeType == "challenge" { config.GlobalConfig.Logger.Info("Challenge submit requested") if difficultyParam != 0 { if difficultyParam < 1 || difficultyParam > 10 { - return "", errors.New("difficulty must be set between 1 and 10") + return "", "", errors.New("difficulty must be set between 1 and 10") } difficultyString = strconv.Itoa(difficultyParam * 10) } challenges, err := utils.SearchChallengeByName(modeValue) if err != nil { - return "", err + return "", "", err } config.GlobalConfig.Logger.Debug(fmt.Sprintf("Challenge found: %v", challenges)) @@ -64,11 +65,12 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri config.GlobalConfig.Logger.Info("Machine submit requested") machineID, err := utils.SearchItemIDByName(modeValue, "Machine") if err != nil { - return "", err + return "", "", err } + mID = machineID machineType, err := utils.GetMachineType(machineID) if err != nil { - return "", err + return "", "", err } config.GlobalConfig.Logger.Debug(fmt.Sprintf("Machine Type: %s", machineType)) @@ -85,7 +87,7 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri config.GlobalConfig.Logger.Info("Fortress submit requested") fortressID, err := utils.SearchFortressID(modeValue) if err != nil { - return "", err + return "", "", err } config.GlobalConfig.Logger.Debug(fmt.Sprintf("Fortress ID : %d", fortressID)) url = fmt.Sprintf("%s/fortress/%d/flag", config.BaseHackTheBoxAPIURL, fortressID) @@ -94,7 +96,7 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri config.GlobalConfig.Logger.Info("Endgame submit requested") endgameID, err := utils.SearchEndgameID(modeValue) if err != nil { - return "", err + return "", "", err } config.GlobalConfig.Logger.Debug(fmt.Sprintf("Endgame ID : %d", endgameID)) url = fmt.Sprintf("%s/endgame/%d/flag", config.BaseHackTheBoxAPIURL, endgameID) @@ -103,7 +105,7 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri config.GlobalConfig.Logger.Info("Prolab submit requested") prolabID, err := utils.SearchProlabID(modeValue) if err != nil { - return "", err + return "", "", err } config.GlobalConfig.Logger.Debug(fmt.Sprintf("Prolab ID : %d", prolabID)) url = fmt.Sprintf("%s/prolab/%d/flag", config.BaseHackTheBoxAPIURL, prolabID) @@ -112,24 +114,25 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri config.GlobalConfig.Logger.Info("Release Arena submit requested") isConfirmed := utils.AskConfirmation("Would you like to submit a flag for the release arena ?") if !isConfirmed { - return "", nil + return "", "", nil } releaseID, err := utils.SearchLastReleaseArenaMachine() if err != nil { - return "", err + return "", "", err } config.GlobalConfig.Logger.Debug(fmt.Sprintf("Release Arena ID : %s", releaseID)) url = fmt.Sprintf("%s/arena/own", config.BaseHackTheBoxAPIURL) payload = map[string]string{ "id": releaseID, } + mID = releaseID } fmt.Print("Flag : ") flagByte, err := term.ReadPassword(int(os.Stdin.Fd())) if err != nil { fmt.Println("Error reading flag") - return "", fmt.Errorf("error reading flag") + return "", "", fmt.Errorf("error reading flag") } flagOriginal := string(flagByte) flag := strings.ReplaceAll(flagOriginal, " ", "") @@ -139,8 +142,21 @@ func CoreSubmitCmd(difficultyParam int, modeType string, modeValue string) (stri payload["flag"] = flag message, err := SubmitFlag(url, payload) + if err != nil { + return "", "", err + } + return message, mID, nil +} + +func GetAchievementLink(machineID string) (string, error) { + resp, err := utils.HtbRequest(http.MethodGet, fmt.Sprintf("%s/user/info", config.BaseHackTheBoxAPIURL), nil) if err != nil { return "", err } - return message, nil + info := utils.ParseJsonMessage(resp, "info") + infoMap, _ := info.(map[string]interface{}) + config.GlobalConfig.Logger.Debug(fmt.Sprintf("User ID: %s", infoMap["id"])) + config.GlobalConfig.Logger.Debug(fmt.Sprintf("Machine ID: %s", machineID)) + + return fmt.Sprintf("\nAchievement link: https://labs.hackthebox.com/achievement/machine/%v/%s", infoMap["id"], machineID), nil } From 49ed17d5a8d951759fd102b879fbb51cb1ed92a7 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Sat, 27 Jan 2024 14:56:50 +0100 Subject: [PATCH 41/42] Add time to spawn --- cmd/start.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/start.go b/cmd/start.go index 0ccfc2b..8126575 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -101,6 +101,7 @@ func coreStartCmd(machineChoosen string, machineID string) (string, error) { } ip := "Undefined" + startTime := time.Now() switch { case machineType == "release": s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) @@ -125,7 +126,7 @@ func coreStartCmd(machineChoosen string, machineID string) (string, error) { s.Stop() break LoopRelease } - time.Sleep(6 * time.Second) + time.Sleep(3 * time.Second) } } case userSubscription == "vip+": @@ -151,7 +152,7 @@ func coreStartCmd(machineChoosen string, machineID string) (string, error) { s.Stop() break Loop } - time.Sleep(6 * time.Second) + time.Sleep(3 * time.Second) } } default: @@ -162,8 +163,8 @@ func coreStartCmd(machineChoosen string, machineID string) (string, error) { } ip = activeMachineData["ip"].(string) } - - message = fmt.Sprintf("%s\nTarget: %s", message, ip) + tts := time.Since(startTime) + message = fmt.Sprintf("%s\nTarget: %s\nTime to spawn was %s !", message, ip, tts) return message, nil } From 43a19634e5a147422380ab191a5df781844b9ca0 Mon Sep 17 00:00:00 2001 From: QU35T-code Date: Sat, 27 Jan 2024 15:15:21 +0100 Subject: [PATCH 42/42] Display achievement link only when user pwned the machine --- lib/submit/submit.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/submit/submit.go b/lib/submit/submit.go index 2bf2734..de76e08 100644 --- a/lib/submit/submit.go +++ b/lib/submit/submit.go @@ -158,5 +158,14 @@ func GetAchievementLink(machineID string) (string, error) { config.GlobalConfig.Logger.Debug(fmt.Sprintf("User ID: %s", infoMap["id"])) config.GlobalConfig.Logger.Debug(fmt.Sprintf("Machine ID: %s", machineID)) - return fmt.Sprintf("\nAchievement link: https://labs.hackthebox.com/achievement/machine/%v/%s", infoMap["id"], machineID), nil + resp, err = utils.HtbRequest(http.MethodGet, fmt.Sprintf("%s/user/achievement/machine/%v/%s", config.BaseHackTheBoxAPIURL, infoMap["id"], machineID), nil) + if err != nil { + return "", err + } + _, ok := utils.ParseJsonMessage(resp, "message").(string) + if !ok { + return fmt.Sprintf("\nAchievement link: https://labs.hackthebox.com/achievement/machine/%v/%s", infoMap["id"], machineID), nil + } + return "", nil + }