diff --git a/pkg/detectors/pusherchannelkey/pusherchannelkey.go b/pkg/detectors/pusherchannelkey/pusherchannelkey.go index 01a9ef25b..829878c91 100644 --- a/pkg/detectors/pusherchannelkey/pusherchannelkey.go +++ b/pkg/detectors/pusherchannelkey/pusherchannelkey.go @@ -6,19 +6,22 @@ import ( "crypto/md5" "crypto/sha256" "encoding/hex" - regexp "github.com/wasilibs/go-re2" + "fmt" + "io" "net/http" "net/url" "strconv" "strings" "time" + regexp "github.com/wasilibs/go-re2" + "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -type Scanner struct{ +type Scanner struct { detectors.DefaultMultiPartCredentialProvider } @@ -49,72 +52,33 @@ const ( func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { dataStr := string(data) - keyMatches := keyPat.FindAllStringSubmatch(dataStr, -1) - appMatches := appIdPat.FindAllStringSubmatch(dataStr, -1) - secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1) + uniqueKeyMatches, uniqueAppMatches, uniqueSecretMatches := make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{}) - for _, appMatch := range appMatches { - if len(appMatch) != 2 { - continue - } - resappMatch := strings.TrimSpace(appMatch[1]) + for _, keyMatch := range keyPat.FindAllStringSubmatch(dataStr, -1) { + uniqueKeyMatches[keyMatch[1]] = struct{}{} + } - for _, keyMatch := range keyMatches { - if len(keyMatch) != 2 { - continue - } - reskeyMatch := strings.TrimSpace(keyMatch[1]) + for _, appMatch := range appIdPat.FindAllStringSubmatch(dataStr, -1) { + uniqueAppMatches[appMatch[1]] = struct{}{} + } - for _, secretMatch := range secretMatches { - if len(secretMatch) != 2 { - continue - } - ressecretMatch := strings.TrimSpace(secretMatch[1]) + for _, secretMatch := range secretPat.FindAllStringSubmatch(dataStr, -1) { + uniqueSecretMatches[secretMatch[1]] = struct{}{} + } + for app := range uniqueAppMatches { + for key := range uniqueKeyMatches { + for secret := range uniqueSecretMatches { s1 := detectors.Result{ DetectorType: detectorspb.DetectorType_PusherChannelKey, - Raw: []byte(resappMatch), - RawV2: []byte(resappMatch + reskeyMatch), + Raw: []byte(app), + RawV2: []byte(app + key), } if verify { - - method := "POST" - path := "/apps/" + resappMatch + "/events" - - stringPayload := `{"channels":["my-channel"],"data":"{\"message\":\"hello world\"}","name":"my_event"}` - payload := strings.NewReader(stringPayload) - _bodyMD5 := md5.New() - _bodyMD5.Write([]byte(stringPayload)) - hash := hex.EncodeToString(_bodyMD5.Sum(nil)) - - timestamp := strconv.FormatInt(time.Now().Unix(), 10) - params := url.Values{ - "auth_key": {reskeyMatch}, - "auth_timestamp": {timestamp}, - "auth_version": {auth_version}, - "body_md5": {hash}, - } - - usecd, _ := url.QueryUnescape(params.Encode()) - - stringToSign := strings.Join([]string{method, path, usecd}, "\n") - signature := hex.EncodeToString(hmacBytes([]byte(stringToSign), []byte(ressecretMatch))) - - md5Str := "https://api-ap1.pusher.com/apps/" + resappMatch + "/events?auth_key=" + reskeyMatch + "&auth_signature=" + signature + "&auth_timestamp=" + timestamp + "&auth_version=1.0&body_md5=" + hash - - req, err := http.NewRequestWithContext(ctx, method, md5Str, payload) - if err != nil { - continue - } - req.Header.Add("Content-Type", "application/json") - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - s1.Verified = true - } - } + isVerified, verificationErr := verifyPusherChannelKey(ctx, client, app, key, secret) + s1.Verified = isVerified + s1.SetVerificationError(verificationErr) } results = append(results, s1) @@ -139,3 +103,55 @@ func (s Scanner) Type() detectorspb.DetectorType { func (s Scanner) Description() string { return "Pusher is a service for adding real-time functionality to web and mobile apps. Pusher Channel keys can be used to authenticate and send messages to channels." } + +func verifyPusherChannelKey(ctx context.Context, client *http.Client, app, key, secret string) (bool, error) { + method := "POST" + path := "/apps/" + app + "/events" + + stringPayload := `{"channels":["my-channel"],"data":"{\"message\":\"hello world\"}","name":"my_event"}` + payload := strings.NewReader(stringPayload) + _bodyMD5 := md5.New() + _bodyMD5.Write([]byte(stringPayload)) + hash := hex.EncodeToString(_bodyMD5.Sum(nil)) + + timestamp := strconv.FormatInt(time.Now().Unix(), 10) + params := url.Values{ + "auth_key": {key}, + "auth_timestamp": {timestamp}, + "auth_version": {auth_version}, + "body_md5": {hash}, + } + + usecd, _ := url.QueryUnescape(params.Encode()) + + stringToSign := strings.Join([]string{method, path, usecd}, "\n") + signature := hex.EncodeToString(hmacBytes([]byte(stringToSign), []byte(secret))) + + md5Str := "https://api-ap1.pusher.com/apps/" + app + "/events?auth_key=" + key + "&auth_signature=" + signature + "&auth_timestamp=" + timestamp + "&auth_version=1.0&body_md5=" + hash + + req, err := http.NewRequestWithContext(ctx, method, md5Str, payload) + if err != nil { + return false, err + } + + req.Header.Add("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + return false, nil + } + + defer func() { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + + }() + + switch resp.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusUnauthorized, http.StatusForbidden: + return false, nil + default: + return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } +} diff --git a/pkg/detectors/pusherchannelkey/pusherchannelkey_integration_test.go b/pkg/detectors/pusherchannelkey/pusherchannelkey_integration_test.go index e6eac1483..e1c50947a 100644 --- a/pkg/detectors/pusherchannelkey/pusherchannelkey_integration_test.go +++ b/pkg/detectors/pusherchannelkey/pusherchannelkey_integration_test.go @@ -52,6 +52,12 @@ func TestPusherChannelKey_FromChunk(t *testing.T) { { DetectorType: detectorspb.DetectorType_PusherChannelKey, Verified: true, + RawV2: []byte(appId + key), + }, + { + DetectorType: detectorspb.DetectorType_PusherChannelKey, + Verified: false, + RawV2: []byte(appId + key), }, }, wantErr: false, @@ -68,6 +74,12 @@ func TestPusherChannelKey_FromChunk(t *testing.T) { { DetectorType: detectorspb.DetectorType_PusherChannelKey, Verified: false, + RawV2: []byte(appId + key), + }, + { + DetectorType: detectorspb.DetectorType_PusherChannelKey, + Verified: false, + RawV2: []byte(appId + key), }, }, wantErr: false,