diff --git a/go.mod b/go.mod index 8c1cc444..9edb62ac 100644 --- a/go.mod +++ b/go.mod @@ -90,14 +90,14 @@ require ( github.com/projectdiscovery/sarif v0.0.1 github.com/projectdiscovery/tlsx v1.1.6-0.20231116215000-e842dc367a74 github.com/projectdiscovery/uncover v1.0.7 - github.com/projectdiscovery/utils v0.0.72 + github.com/projectdiscovery/utils v0.0.73-0.20240110205148-46f474b2947f github.com/projectdiscovery/wappalyzergo v0.0.109 github.com/redis/go-redis/v9 v9.1.0 github.com/ropnop/gokrb5/v8 v8.0.0-20201111231119-729746023c02 github.com/sashabaranov/go-openai v1.15.3 github.com/stretchr/testify v1.8.4 github.com/zmap/zgrab2 v0.1.8-0.20230806160807-97ba87c0e706 - golang.org/x/term v0.15.0 + golang.org/x/term v0.16.0 gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -286,7 +286,7 @@ require ( golang.org/x/crypto v0.17.0 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/mod v0.14.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.15.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 64007649..f814a3b9 100644 --- a/go.sum +++ b/go.sum @@ -851,8 +851,8 @@ github.com/projectdiscovery/tlsx v1.1.6-0.20231116215000-e842dc367a74 h1:G0gw+3z github.com/projectdiscovery/tlsx v1.1.6-0.20231116215000-e842dc367a74/go.mod h1:YH8el7/6pyZbNed1IibjzbGpeigiCVyvE28g5+LsPAw= github.com/projectdiscovery/uncover v1.0.7 h1:ut+2lTuvmftmveqF5RTjMWAgyLj8ltPQC7siFy9sj0A= github.com/projectdiscovery/uncover v1.0.7/go.mod h1:HFXgm1sRPuoN0D4oATljPIdmbo/EEh1wVuxQqo/dwFE= -github.com/projectdiscovery/utils v0.0.72 h1:sJ1lBcaWO6dJ65F+fVhSJbguhgWjixgy9mjj7jKBUW8= -github.com/projectdiscovery/utils v0.0.72/go.mod h1:VPnijH51D8wB1VJiEujUp7UZ+TUTKN8PpoW82nivUVY= +github.com/projectdiscovery/utils v0.0.73-0.20240110205148-46f474b2947f h1:gjp9RyGSh507ARIrUUBE0u13mMYPDWZxoZEOAneCT4E= +github.com/projectdiscovery/utils v0.0.73-0.20240110205148-46f474b2947f/go.mod h1:B+4cyms2mQtG6KC2qaV5zEVJgyIWzjCwrgO+kpSR5SY= github.com/projectdiscovery/wappalyzergo v0.0.109 h1:BERfwTRn1dvB1tbhyc5m67R8VkC9zbVuPsEq4VEm07k= github.com/projectdiscovery/wappalyzergo v0.0.109/go.mod h1:4Z3DKhi75zIPMuA+qSDDWxZvnhL4qTLmDx4dxNMu7MA= github.com/projectdiscovery/yamldoc-go v1.0.4 h1:eZoESapnMw6WAHiVgRwNqvbJEfNHEH148uthhFbG5jE= @@ -1348,8 +1348,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1361,8 +1361,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +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.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/pdcp/auth.go b/internal/pdcp/auth.go deleted file mode 100644 index c8776090..00000000 --- a/internal/pdcp/auth.go +++ /dev/null @@ -1,76 +0,0 @@ -// pdcp contains projectdiscovery cloud platform related features -// like result upload , dashboard etc. -package pdcp - -import ( - "fmt" - "os" - "strings" - - "github.com/projectdiscovery/gologger" - "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" - "github.com/projectdiscovery/utils/env" - "golang.org/x/term" -) - -var ( - DashBoardURL = "https://cloud.projectdiscovery.io" - DefaultApiServer = "https://api.projectdiscovery.io" -) - -// CheckNValidateCredentials checks if credentials exist on filesystem -// if not waits for user to enter credentials and validates them -// and saves them to filesystem -// when validate is true any existing credentials are validated -// Note: this is meant to be used in cli only (interactive mode) -func CheckNValidateCredentials(toolName string) { - h := &PDCPCredHandler{} - creds, err := h.GetCreds() - if err == nil { - // validate by fetching user profile - gotCreds, err := h.ValidateAPIKey(creds.APIKey, creds.Server, config.BinaryName) - if err == nil { - gologger.Info().Msgf("You are logged in as (@%v)", gotCreds.Username) - os.Exit(0) - } - gologger.Error().Msgf("Invalid API key found in file, please recheck or recreate your API key and retry.") - } - if err != nil && err != ErrNoCreds { - // this is unexpected error log it - gologger.Error().Msgf("Could not read credentials from file: %s\n", err) - } - - // if we are here, we need to get credentials from user - gologger.Info().Msgf("Get your free api key by signing up at %v", DashBoardURL) - fmt.Printf("[*] Enter PDCP API Key (exit to abort): ") - bin, err := term.ReadPassword(int(os.Stdin.Fd())) - if err != nil { - gologger.Fatal().Msgf("Could not read input from terminal: %s\n", err) - } - apiKey := string(bin) - if strings.EqualFold(apiKey, "exit") { - os.Exit(0) - } - fmt.Println() - // if env variable is set use that for validating api key - apiServer := env.GetEnvOrDefault(apiServerEnv, DefaultApiServer) - // validate by fetching user profile - validatedCreds, err := h.ValidateAPIKey(apiKey, apiServer, toolName) - if err == nil { - gologger.Info().Msgf("Successfully logged in as (@%v)", validatedCreds.Username) - if saveErr := h.SaveCreds(validatedCreds); saveErr != nil { - gologger.Warning().Msgf("Could not save credentials to file: %s\n", saveErr) - } - os.Exit(0) - } - gologger.Error().Msgf("Invalid API key '%v' got error: %v", maskKey(apiKey), err) - gologger.Fatal().Msgf("please recheck or recreate your API key and retry") -} - -func maskKey(key string) string { - if len(key) < 6 { - // this is invalid key - return key - } - return fmt.Sprintf("%v%v", key[:3], strings.Repeat("*", len(key)-3)) -} diff --git a/internal/pdcp/creds.go b/internal/pdcp/creds.go deleted file mode 100644 index e3049c0b..00000000 --- a/internal/pdcp/creds.go +++ /dev/null @@ -1,139 +0,0 @@ -package pdcp - -import ( - "encoding/json" - "fmt" - "io" - "os" - "path/filepath" - - "github.com/projectdiscovery/retryablehttp-go" - "github.com/projectdiscovery/utils/env" - fileutil "github.com/projectdiscovery/utils/file" - folderutil "github.com/projectdiscovery/utils/folder" - urlutil "github.com/projectdiscovery/utils/url" - "gopkg.in/yaml.v3" -) - -var ( - PDCPDir = filepath.Join(folderutil.HomeDirOrDefault(""), ".pdcp") - PDCPCredFile = filepath.Join(PDCPDir, "credentials.yaml") - ErrNoCreds = fmt.Errorf("no credentials found in %s", PDCPDir) -) - -const ( - userProfileURL = "https://%s/v1/user?utm_source=%s" - apiKeyEnv = "PDCP_API_KEY" - apiServerEnv = "PDCP_API_SERVER" - ApiKeyHeaderName = "X-Api-Key" - dashBoardEnv = "PDCP_DASHBOARD_URL" -) - -type PDCPCredentials struct { - Username string `yaml:"username"` - APIKey string `yaml:"api-key"` - Server string `yaml:"server"` -} - -type PDCPUserProfileResponse struct { - UserName string `json:"name"` - // there are more fields but we don't need them - /// below fields are added later on and not part of the response -} - -// PDCPCredHandler is interface for adding / retrieving pdcp credentials -// from file system -type PDCPCredHandler struct{} - -// GetCreds retrieves the credentials from the file system or environment variables -func (p *PDCPCredHandler) GetCreds() (*PDCPCredentials, error) { - credsFromEnv := p.getCredsFromEnv() - if credsFromEnv != nil { - return credsFromEnv, nil - } - if !fileutil.FolderExists(PDCPDir) || !fileutil.FileExists(PDCPCredFile) { - return nil, ErrNoCreds - } - bin, err := os.Open(PDCPCredFile) - if err != nil { - return nil, err - } - // for future use-cases - var creds []PDCPCredentials - err = yaml.NewDecoder(bin).Decode(&creds) - if err != nil { - return nil, err - } - if len(creds) == 0 { - return nil, ErrNoCreds - } - return &creds[0], nil -} - -// getCredsFromEnv retrieves the credentials from the environment -// if not or incomplete credentials are found it return nil -func (p *PDCPCredHandler) getCredsFromEnv() *PDCPCredentials { - apiKey := env.GetEnvOrDefault(apiKeyEnv, "") - apiServer := env.GetEnvOrDefault(apiServerEnv, "") - if apiKey == "" || apiServer == "" { - return nil - } - return &PDCPCredentials{APIKey: apiKey, Server: apiServer} -} - -// SaveCreds saves the credentials to the file system -func (p *PDCPCredHandler) SaveCreds(resp *PDCPCredentials) error { - if resp == nil { - return fmt.Errorf("invalid response") - } - if !fileutil.FolderExists(PDCPDir) { - _ = fileutil.CreateFolder(PDCPDir) - } - bin, err := yaml.Marshal([]*PDCPCredentials{resp}) - if err != nil { - return err - } - return os.WriteFile(PDCPCredFile, bin, 0600) -} - -// ValidateAPIKey validates the api key and retrieves associated user metadata like username -// from given api server/host -func (p *PDCPCredHandler) ValidateAPIKey(key string, host string, toolName string) (*PDCPCredentials, error) { - // get address from url - urlx, err := urlutil.Parse(host) - if err != nil { - return nil, err - } - req, err := retryablehttp.NewRequest("GET", fmt.Sprintf(userProfileURL, urlx.Host, toolName), nil) - if err != nil { - return nil, err - } - req.Header.Set(ApiKeyHeaderName, key) - resp, err := retryablehttp.DefaultHTTPClient.Do(req) - if err != nil { - return nil, err - } - if resp.StatusCode != 200 { - _, _ = io.Copy(io.Discard, resp.Body) - _ = resp.Body.Close() - return nil, fmt.Errorf("invalid status code: %d", resp.StatusCode) - } - defer resp.Body.Close() - bin, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - var profile PDCPUserProfileResponse - err = json.Unmarshal(bin, &profile) - if err != nil { - return nil, err - } - if profile.UserName == "" { - return nil, fmt.Errorf("invalid response from server got %v", string(bin)) - } - return &PDCPCredentials{Username: profile.UserName, APIKey: key, Server: host}, nil -} - -func init() { - DashBoardURL = env.GetEnvOrDefault("PDCP_DASHBOARD_URL", DashBoardURL) -} diff --git a/internal/pdcp/creds_test.go b/internal/pdcp/creds_test.go deleted file mode 100644 index bf800515..00000000 --- a/internal/pdcp/creds_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package pdcp - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/stretchr/testify/require" -) - -var exampleCred = ` -- username: test - api-key: testpassword - server: https://scanme.sh -` - -func TestLoadCreds(t *testing.T) { - // temporarily change PDCP file location for testing - f, err := os.CreateTemp("", "creds-test-*") - require.Nil(t, err) - _, _ = f.WriteString(strings.TrimSpace(exampleCred)) - defer os.Remove(f.Name()) - PDCPCredFile = f.Name() - PDCPDir = filepath.Dir(f.Name()) - h := &PDCPCredHandler{} - value, err := h.GetCreds() - require.Nil(t, err) - require.NotNil(t, value) - require.Equal(t, "test", value.Username) - require.Equal(t, "testpassword", value.APIKey) - require.Equal(t, "https://scanme.sh", value.Server) -} diff --git a/internal/pdcp/writer.go b/internal/pdcp/writer.go index 52ffc770..815633cc 100644 --- a/internal/pdcp/writer.go +++ b/internal/pdcp/writer.go @@ -16,6 +16,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/retryablehttp-go" + pdcpauth "github.com/projectdiscovery/utils/auth/pdcp" errorutil "github.com/projectdiscovery/utils/errors" fileutil "github.com/projectdiscovery/utils/file" folderutil "github.com/projectdiscovery/utils/folder" @@ -32,14 +33,14 @@ var _ output.Writer = &UploadWriter{} // server to enable web dashboard and more type UploadWriter struct { *output.StandardWriter - creds *PDCPCredentials + creds *pdcpauth.PDCPCredentials tempFile *os.File done atomic.Bool uploadURL *url.URL } // NewUploadWriter creates a new upload writer -func NewUploadWriter(creds *PDCPCredentials) (*UploadWriter, error) { +func NewUploadWriter(creds *pdcpauth.PDCPCredentials) (*UploadWriter, error) { if creds == nil { return nil, fmt.Errorf("no credentials provided") } @@ -107,7 +108,7 @@ func (u *UploadWriter) upload() (string, error) { if err != nil { return "", errorutil.NewWithErr(err).Msgf("could not create cloud upload request") } - req.Header.Set(ApiKeyHeaderName, u.creds.APIKey) + req.Header.Set(pdcpauth.ApiKeyHeaderName, u.creds.APIKey) req.Header.Set("Content-Type", "application/octet-stream") req.Header.Set("Accept", "application/json") @@ -149,7 +150,7 @@ func (u *UploadWriter) Close() { } func getScanDashBoardURL(id string) string { - ux, _ := urlutil.Parse(DashBoardURL) + ux, _ := urlutil.Parse(pdcpauth.DashBoardURL) ux.Path = "/scans/" + id ux.Update() return ux.String() diff --git a/internal/runner/banner.go b/internal/runner/banner.go index 3a5d7a05..a6e2d739 100644 --- a/internal/runner/banner.go +++ b/internal/runner/banner.go @@ -5,8 +5,8 @@ import ( "fmt" "github.com/projectdiscovery/gologger" - "github.com/projectdiscovery/nuclei/v3/internal/pdcp" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" + pdcpauth "github.com/projectdiscovery/utils/auth/pdcp" updateutils "github.com/projectdiscovery/utils/update" ) @@ -33,5 +33,5 @@ func NucleiToolUpdateCallback() { // AuthWithPDCP is used to authenticate with PDCP func AuthWithPDCP() { showBanner() - pdcp.CheckNValidateCredentials(config.BinaryName) + pdcpauth.CheckNValidateCredentials(config.BinaryName) } diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 54334542..b113af3d 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -16,6 +16,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/internal/pdcp" "github.com/projectdiscovery/nuclei/v3/pkg/installer" uncoverlib "github.com/projectdiscovery/uncover" + pdcpauth "github.com/projectdiscovery/utils/auth/pdcp" "github.com/projectdiscovery/utils/env" fileutil "github.com/projectdiscovery/utils/file" permissionutil "github.com/projectdiscovery/utils/permission" @@ -351,18 +352,18 @@ func (r *Runner) setupPDCPUpload(writer output.Writer) output.Writer { return writer } color := aurora.NewAurora(!r.options.NoColor) - h := &pdcp.PDCPCredHandler{} + h := &pdcpauth.PDCPCredHandler{} creds, err := h.GetCreds() if err != nil { - if err != pdcp.ErrNoCreds && !HideAutoSaveMsg { + if err != pdcpauth.ErrNoCreds && !HideAutoSaveMsg { gologger.Verbose().Msgf("Could not get credentials for cloud upload: %s\n", err) } - r.pdcpUploadErrMsg = fmt.Sprintf("[%v] To view results on Cloud Dashboard, Configure API key from %v", color.BrightYellow("WRN"), pdcp.DashBoardURL) + r.pdcpUploadErrMsg = fmt.Sprintf("[%v] To view results on Cloud Dashboard, Configure API key from %v", color.BrightYellow("WRN"), pdcpauth.DashBoardURL) return writer } uploadWriter, err := pdcp.NewUploadWriter(creds) if err != nil { - r.pdcpUploadErrMsg = fmt.Sprintf("[%v] PDCP (%v) Auto-Save Failed: %s\n", color.BrightYellow("WRN"), pdcp.DashBoardURL, err) + r.pdcpUploadErrMsg = fmt.Sprintf("[%v] PDCP (%v) Auto-Save Failed: %s\n", color.BrightYellow("WRN"), pdcpauth.DashBoardURL, err) return writer } return output.NewMultiWriter(writer, uploadWriter) @@ -592,7 +593,7 @@ func (r *Runner) displayExecutionInfo(store *loader.Store) { if r.pdcpUploadErrMsg != "" { gologger.Print().Msgf("%s", r.pdcpUploadErrMsg) } else { - gologger.Info().Msgf("To view results on cloud dashboard, visit %v/scans upon scan completion.", pdcp.DashBoardURL) + gologger.Info().Msgf("To view results on cloud dashboard, visit %v/scans upon scan completion.", pdcpauth.DashBoardURL) } }