Skip to content

Commit 3bba277

Browse files
Added support for indeterminate verification for letter Z detectors (#4165)
* initial commit * handle and set verification errors * some fixes
1 parent ea5fc39 commit 3bba277

File tree

17 files changed

+503
-217
lines changed

17 files changed

+503
-217
lines changed

pkg/detectors/zapierwebhook/zapierwebhook.go

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package zapierwebhook
22

33
import (
44
"context"
5+
"fmt"
6+
"io"
57
"net/http"
68
"strings"
79

@@ -44,17 +46,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
4446
}
4547

4648
if verify {
47-
req, err := http.NewRequestWithContext(ctx, "POST", "https://hooks.zapier.com/hooks/catch/"+resMatch, nil)
48-
if err != nil {
49-
continue
50-
}
51-
res, err := client.Do(req)
52-
if err == nil {
53-
defer res.Body.Close()
54-
if res.StatusCode >= 200 && res.StatusCode < 300 {
55-
s1.Verified = true
56-
}
57-
}
49+
isVerified, verificationErr := verifyZapierWebhook(ctx, client, resMatch)
50+
s1.Verified = isVerified
51+
s1.SetVerificationError(verificationErr, resMatch)
5852
}
5953

6054
results = append(results, s1)
@@ -70,3 +64,29 @@ func (s Scanner) Type() detectorspb.DetectorType {
7064
func (s Scanner) Description() string {
7165
return "Zapier is an automation tool that connects your apps and services. Zapier webhooks can be used to automate workflows by sending HTTP requests to a unique URL."
7266
}
67+
68+
func verifyZapierWebhook(ctx context.Context, client *http.Client, key string) (bool, error) {
69+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://hooks.zapier.com/hooks/catch/"+key, http.NoBody)
70+
if err != nil {
71+
return false, err
72+
}
73+
74+
resp, err := client.Do(req)
75+
if err != nil {
76+
return false, err
77+
}
78+
79+
defer func() {
80+
_, _ = io.Copy(io.Discard, resp.Body)
81+
_ = resp.Body.Close()
82+
}()
83+
84+
switch resp.StatusCode {
85+
case http.StatusOK:
86+
return true, nil
87+
case http.StatusUnauthorized:
88+
return false, nil
89+
default:
90+
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
91+
}
92+
}

pkg/detectors/zendeskapi/zendeskapi.go

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,17 @@ package zendeskapi
22

33
import (
44
"context"
5-
b64 "encoding/base64"
65
"fmt"
6+
"io"
77
"net/http"
8-
"strings"
98

109
regexp "github.com/wasilibs/go-re2"
1110

1211
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
1312
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
1413
)
1514

16-
type Scanner struct{
15+
type Scanner struct {
1716
detectors.DefaultMultiPartCredentialProvider
1817
}
1918

@@ -25,7 +24,7 @@ var (
2524

2625
token = regexp.MustCompile(detectors.PrefixRegex([]string{"zendesk"}) + `([A-Za-z0-9_-]{40})`)
2726
email = regexp.MustCompile(`\b([a-zA-Z-0-9-]{5,16}\@[a-zA-Z-0-9]{4,16}\.[a-zA-Z-0-9]{3,6})\b`)
28-
domain = regexp.MustCompile(`\b([a-zA-Z-0-9]{3,16}\.zendesk\.com)\b`)
27+
domain = regexp.MustCompile(`\b([a-zA-Z-0-9]{3,25}\.zendesk\.com)\b`)
2928
)
3029

3130
// Keywords are used for efficiently pre-filtering chunks.
@@ -38,40 +37,32 @@ func (s Scanner) Keywords() []string {
3837
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
3938
dataStr := string(data)
4039

41-
tokens := token.FindAllStringSubmatch(dataStr, -1)
42-
domains := domain.FindAllStringSubmatch(dataStr, -1)
43-
emails := email.FindAllStringSubmatch(dataStr, -1)
40+
var uniqueEmails, uniqueTokens, uniqueDomains = make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})
4441

45-
for _, token := range tokens {
46-
resMatch := strings.TrimSpace(token[1])
42+
for _, match := range email.FindAllStringSubmatch(dataStr, -1) {
43+
uniqueEmails[match[1]] = struct{}{}
44+
}
4745

48-
var resDomain string
49-
for _, domain := range domains {
50-
resDomain = strings.TrimSpace(domain[1])
46+
for _, match := range token.FindAllStringSubmatch(dataStr, -1) {
47+
uniqueTokens[match[1]] = struct{}{}
48+
}
5149

52-
for _, email := range emails {
53-
resEmail := strings.TrimSpace(email[1])
50+
for _, match := range domain.FindAllStringSubmatch(dataStr, -1) {
51+
uniqueDomains[match[1]] = struct{}{}
52+
}
5453

54+
for token := range uniqueTokens {
55+
for email := range uniqueEmails {
56+
for domain := range uniqueDomains {
5557
s1 := detectors.Result{
5658
DetectorType: detectorspb.DetectorType_ZendeskApi,
57-
Raw: []byte(resMatch),
59+
Raw: []byte(token),
5860
}
5961

6062
if verify {
61-
data := fmt.Sprintf("%s/token:%s", resEmail, resMatch)
62-
sEnc := b64.StdEncoding.EncodeToString([]byte(data))
63-
req, err := http.NewRequestWithContext(ctx, "GET", "https://"+resDomain+"/api/v2/users.json", nil)
64-
if err != nil {
65-
continue
66-
}
67-
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", sEnc))
68-
res, err := client.Do(req)
69-
if err == nil {
70-
defer res.Body.Close()
71-
if res.StatusCode >= 200 && res.StatusCode < 300 {
72-
s1.Verified = true
73-
}
74-
}
63+
isVerified, verificationErr := verifyZendesk(ctx, client, email, token, domain)
64+
s1.Verified = isVerified
65+
s1.SetVerificationError(verificationErr, token)
7566
}
7667

7768
results = append(results, s1)
@@ -91,3 +82,31 @@ func (s Scanner) Type() detectorspb.DetectorType {
9182
func (s Scanner) Description() string {
9283
return "Zendesk is a customer service platform. Zendesk API tokens can be used to access and modify customer service data."
9384
}
85+
86+
func verifyZendesk(ctx context.Context, client *http.Client, email, token, domain string) (bool, error) {
87+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://"+domain+"/api/v2/users.json", http.NoBody)
88+
if err != nil {
89+
return false, err
90+
}
91+
92+
// docs: https://developer.zendesk.com/api-reference/introduction/security-and-auth/
93+
req.SetBasicAuth(email+"/token", token)
94+
resp, err := client.Do(req)
95+
if err != nil {
96+
return false, err
97+
}
98+
99+
defer func() {
100+
_, _ = io.Copy(io.Discard, resp.Body)
101+
_ = resp.Body.Close()
102+
}()
103+
104+
switch resp.StatusCode {
105+
case http.StatusOK:
106+
return true, nil
107+
case http.StatusUnauthorized, http.StatusNotFound:
108+
return false, nil
109+
default:
110+
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
111+
}
112+
}

pkg/detectors/zenkitapi/zenkitapi.go

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package zenkitapi
22

33
import (
44
"context"
5-
regexp "github.com/wasilibs/go-re2"
5+
"fmt"
6+
"io"
67
"net/http"
78
"strings"
89

10+
regexp "github.com/wasilibs/go-re2"
11+
912
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
1013
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
1114
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
@@ -44,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
4447
}
4548

4649
if verify {
47-
req, err := http.NewRequestWithContext(ctx, "GET", "https://base.zenkit.com/api/v1/users/me", nil)
48-
if err != nil {
49-
continue
50-
}
51-
req.Header.Add("Zenkit-API-Key", resMatch)
52-
res, err := client.Do(req)
53-
if err == nil {
54-
defer res.Body.Close()
55-
if res.StatusCode >= 200 && res.StatusCode < 300 {
56-
s1.Verified = true
57-
}
58-
}
50+
isVerified, verificationErr := verifyZenkitKey(ctx, client, resMatch)
51+
s1.Verified = isVerified
52+
s1.SetVerificationError(verificationErr, resMatch)
5953
}
6054

6155
results = append(results, s1)
@@ -71,3 +65,31 @@ func (s Scanner) Type() detectorspb.DetectorType {
7165
func (s Scanner) Description() string {
7266
return "Zenkit is a collaborative SaaS platform for project management, database building, and more. Zenkit API keys can be used to access and interact with Zenkit's services programmatically."
7367
}
68+
69+
func verifyZenkitKey(ctx context.Context, client *http.Client, key string) (bool, error) {
70+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://base.zenkit.com/api/v1/users/me", http.NoBody)
71+
if err != nil {
72+
return false, err
73+
}
74+
75+
req.Header.Add("Zenkit-API-Key", key)
76+
77+
resp, err := client.Do(req)
78+
if err != nil {
79+
return false, err
80+
}
81+
82+
defer func() {
83+
_, _ = io.Copy(io.Discard, resp.Body)
84+
_ = resp.Body.Close()
85+
}()
86+
87+
switch resp.StatusCode {
88+
case http.StatusOK:
89+
return true, nil
90+
case http.StatusUnauthorized:
91+
return false, nil
92+
default:
93+
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
94+
}
95+
}

pkg/detectors/zenrows/zenrows.go

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package zenrows
33
import (
44
"context"
55
"fmt"
6-
regexp "github.com/wasilibs/go-re2"
6+
"io"
77
"net/http"
88
"strings"
99

10+
regexp "github.com/wasilibs/go-re2"
11+
1012
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
1113
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
1214
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
@@ -45,17 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
4547
}
4648

4749
if verify {
48-
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.zenrows.com/v1/?apikey=%s&url=https://httpbin.org/anything", resMatch), nil)
49-
if err != nil {
50-
continue
51-
}
52-
res, err := client.Do(req)
53-
if err == nil {
54-
defer res.Body.Close()
55-
if res.StatusCode >= 200 && res.StatusCode < 300 {
56-
s1.Verified = true
57-
}
58-
}
50+
isVerified, verificationErr := verifyZenrowsKey(ctx, client, resMatch)
51+
s1.Verified = isVerified
52+
s1.SetVerificationError(verificationErr, resMatch)
5953
}
6054

6155
results = append(results, s1)
@@ -71,3 +65,29 @@ func (s Scanner) Type() detectorspb.DetectorType {
7165
func (s Scanner) Description() string {
7266
return "ZenRows is a web scraping API service that allows users to extract data from websites. ZenRows API keys can be used to access and scrape web data."
7367
}
68+
69+
func verifyZenrowsKey(ctx context.Context, client *http.Client, key string) (bool, error) {
70+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://api.zenrows.com/v1/?apikey=%s&url=https://httpbin.org/anything", key), http.NoBody)
71+
if err != nil {
72+
return false, err
73+
}
74+
75+
resp, err := client.Do(req)
76+
if err != nil {
77+
return false, err
78+
}
79+
80+
defer func() {
81+
_, _ = io.Copy(io.Discard, resp.Body)
82+
_ = resp.Body.Close()
83+
}()
84+
85+
switch resp.StatusCode {
86+
case http.StatusOK:
87+
return true, nil
88+
case http.StatusUnauthorized, http.StatusPaymentRequired: // docs: https://docs.zenrows.com/api-error-codes#402-payment-required
89+
return false, nil
90+
default:
91+
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
92+
}
93+
}

pkg/detectors/zenscrape/zenscrape.go

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package zenscrape
22

33
import (
44
"context"
5+
"fmt"
6+
"io"
57
"net/http"
68
"strings"
79

@@ -45,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
4547
}
4648

4749
if verify {
48-
req, err := http.NewRequestWithContext(ctx, "GET", "https://app.zenscrape.com/api/v1/status", nil)
49-
if err != nil {
50-
continue
51-
}
52-
req.Header.Add("apikey", resMatch)
53-
res, err := client.Do(req)
54-
if err == nil {
55-
defer res.Body.Close()
56-
if (res.StatusCode >= 200 && res.StatusCode < 300) || res.StatusCode == 429 {
57-
s1.Verified = true
58-
}
59-
}
50+
isVerified, verificationErr := verifyZenScrapeKey(ctx, client, resMatch)
51+
s1.Verified = isVerified
52+
s1.SetVerificationError(verificationErr, resMatch)
6053
}
6154

6255
results = append(results, s1)
@@ -72,3 +65,31 @@ func (s Scanner) Type() detectorspb.DetectorType {
7265
func (s Scanner) Description() string {
7366
return "Zenscrape is a web scraping service that provides an API to extract data from websites. Zenscrape API keys can be used to access and scrape data from web pages."
7467
}
68+
69+
func verifyZenScrapeKey(ctx context.Context, client *http.Client, key string) (bool, error) {
70+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://app.zenscrape.com/api/v1/status", http.NoBody)
71+
if err != nil {
72+
return false, err
73+
}
74+
75+
req.Header.Add("apikey", key)
76+
77+
resp, err := client.Do(req)
78+
if err != nil {
79+
return false, err
80+
}
81+
82+
defer func() {
83+
_, _ = io.Copy(io.Discard, resp.Body)
84+
_ = resp.Body.Close()
85+
}()
86+
87+
switch resp.StatusCode {
88+
case http.StatusOK, http.StatusTooManyRequests:
89+
return true, nil
90+
case http.StatusUnauthorized, http.StatusForbidden:
91+
return false, nil
92+
default:
93+
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
94+
}
95+
}

0 commit comments

Comments
 (0)