Skip to content

Commit 0f360b0

Browse files
fixed and improved squareapp detector (#3993)
1 parent 5e7fe54 commit 0f360b0

File tree

3 files changed

+128
-60
lines changed

3 files changed

+128
-60
lines changed

pkg/detectors/squareapp/squareapp.go

Lines changed: 106 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55
"context"
66
"encoding/json"
77
"fmt"
8+
"io"
89
"net/http"
10+
"strings"
911

1012
regexp "github.com/wasilibs/go-re2"
1113

@@ -14,18 +16,25 @@ import (
1416
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
1517
)
1618

17-
type Scanner struct{
19+
type Scanner struct {
1820
detectors.DefaultMultiPartCredentialProvider
1921
}
2022

2123
// Ensure the Scanner satisfies the interface at compile time.
2224
var _ detectors.Detector = (*Scanner)(nil)
2325

2426
var (
25-
// possibly always `sq0csp` for secret
26-
// and `sq0idb` for app
27-
keyPat = regexp.MustCompile(`[\w\-]*sq0i[a-z]{2}-[0-9A-Za-z\-_]{22,43}`)
28-
secPat = regexp.MustCompile(`[\w\-]*sq0c[a-z]{2}-[0-9A-Za-z\-_]{40,50}`)
27+
client = common.SaneHttpClient()
28+
/*
29+
The sandbox id and secret has word `sandbox-` as prefix
30+
possibly always `sq0csp` for secret and `sq0idb` for app
31+
*/
32+
keyPat = regexp.MustCompile(`(?:sandbox-)?sq0i[a-z]{2}-[0-9A-Za-z_-]{22,43}`)
33+
secPat = regexp.MustCompile(`(?:sandbox-)?sq0c[a-z]{2}-[0-9A-Za-z_-]{40,50}`)
34+
35+
// api endpoints
36+
sandboxEndpoint = "https://connect.squareupsandbox.com/oauth2/revoke"
37+
prodEndpoint = "https://connect.squareup.com/oauth2/revoke"
2938
)
3039

3140
// Keywords are used for efficiently pre-filtering chunks.
@@ -34,61 +43,114 @@ func (s Scanner) Keywords() []string {
3443
return []string{"sq0i"}
3544
}
3645

46+
func (s Scanner) Type() detectorspb.DetectorType {
47+
return detectorspb.DetectorType_SquareApp
48+
}
49+
50+
func (s Scanner) Description() string {
51+
return "Square is a financial services and mobile payment company. Square credentials can be used to access and manage payment processing and other financial services."
52+
}
53+
3754
// FromData will find and optionally verify SquareApp secrets in a given set of bytes.
3855
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
3956
dataStr := string(data)
4057

41-
matches := keyPat.FindAllString(dataStr, -1)
42-
secMatches := secPat.FindAllString(dataStr, -1)
43-
for _, match := range matches {
44-
for _, secMatch := range secMatches {
58+
var uniqueIDMatches, uniqueSecretMatches = make(map[string]struct{}), make(map[string]struct{})
59+
60+
for _, match := range keyPat.FindAllString(dataStr, -1) {
61+
uniqueIDMatches[match] = struct{}{}
62+
}
63+
64+
for _, match := range secPat.FindAllString(dataStr, -1) {
65+
uniqueSecretMatches[match] = struct{}{}
66+
}
67+
68+
for id := range uniqueIDMatches {
69+
for secret := range uniqueSecretMatches {
70+
// if both are not from same env, continue
71+
if !hasSamePrefix(id, secret) {
72+
continue
73+
}
4574

4675
result := detectors.Result{
4776
DetectorType: detectorspb.DetectorType_SquareApp,
48-
Raw: []byte(match),
49-
Redacted: match,
77+
Raw: []byte(id),
78+
Redacted: id,
79+
ExtraData: map[string]string{},
5080
}
5181

52-
if verify {
53-
baseURL := "https://connect.squareupsandbox.com/oauth2/revoke"
54-
55-
client := common.SaneHttpClient()
56-
reqData, err := json.Marshal(map[string]string{
57-
"client_id": match,
58-
"access_token": "fakeTruffleHogAccessTokenForVerification",
59-
})
60-
if err != nil {
61-
return results, err
62-
}
63-
64-
req, err := http.NewRequestWithContext(ctx, "POST", baseURL, bytes.NewReader(reqData))
65-
if err != nil {
66-
continue
67-
}
68-
req.Header.Add("Authorization", fmt.Sprintf("Client %s", secMatch))
69-
req.Header.Add("Content-Type", "application/json")
70-
71-
res, err := client.Do(req)
72-
if err == nil {
73-
res.Body.Close() // The request body is unused.
74-
75-
// 404 = Correct credentials. The fake access token should not be found.
76-
if res.StatusCode == http.StatusNotFound {
77-
result.Verified = true
78-
}
79-
}
82+
var isVerified bool
83+
var verificationErr error
84+
85+
// verify against sandbox endpoint
86+
if verify && isSandbox(id) {
87+
isVerified, verificationErr = verifySquareApp(ctx, client, sandboxEndpoint, id, secret)
88+
result.ExtraData["Env"] = "Sandbox"
89+
}
90+
91+
// verify against prod endpoint
92+
if verify && !isSandbox(id) {
93+
isVerified, verificationErr = verifySquareApp(ctx, client, prodEndpoint, id, secret)
94+
result.ExtraData["Env"] = "Production"
8095
}
8196

97+
result.Verified = isVerified
98+
result.SetVerificationError(verificationErr)
99+
82100
results = append(results, result)
101+
102+
// once a secret is verified with id, remove it from the list
103+
if isVerified {
104+
delete(uniqueSecretMatches, secret)
105+
}
83106
}
84107
}
85-
return
108+
109+
return results, nil
86110
}
87111

88-
func (s Scanner) Type() detectorspb.DetectorType {
89-
return detectorspb.DetectorType_SquareApp
112+
func verifySquareApp(ctx context.Context, client *http.Client, endpoint, id, secret string) (bool, error) {
113+
reqData, err := json.Marshal(map[string]string{
114+
"client_id": id,
115+
"access_token": "fakeTruffleHogAccessTokenForVerification",
116+
})
117+
if err != nil {
118+
return false, err
119+
}
120+
121+
req, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewReader(reqData))
122+
if err != nil {
123+
return false, err
124+
}
125+
req.Header.Add("Authorization", fmt.Sprintf("Client %s", secret))
126+
req.Header.Add("Content-Type", "application/json")
127+
128+
resp, err := client.Do(req)
129+
if err != nil {
130+
return false, err
131+
}
132+
133+
defer func() {
134+
_, _ = io.Copy(io.Discard, resp.Body)
135+
_ = resp.Body.Close()
136+
}()
137+
138+
switch resp.StatusCode {
139+
case http.StatusNotFound:
140+
return true, nil
141+
default:
142+
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
143+
}
90144
}
91145

92-
func (s Scanner) Description() string {
93-
return "Square is a financial services and mobile payment company. Square credentials can be used to access and manage payment processing and other financial services."
146+
func hasSamePrefix(id, secret string) bool {
147+
idHasPrefix := strings.HasPrefix(id, "sandbox-")
148+
secretHasPrefix := strings.HasPrefix(secret, "sandbox-")
149+
150+
return idHasPrefix == secretHasPrefix
151+
}
152+
153+
// isSandbox check if provided key(id or secret) is of sandbox env
154+
func isSandbox(key string) bool {
155+
return strings.HasPrefix(key, "sandbox-")
94156
}

pkg/detectors/squareapp/squareapp_integration_test.go

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ import (
1919
func TestSquareApp_FromChunk(t *testing.T) {
2020
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
2121
defer cancel()
22-
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
22+
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
2323
if err != nil {
2424
t.Fatalf("could not get test secrets from GCP: %s", err)
2525
}
26+
27+
id := testSecrets.MustGetField("SQUAREAPP_ID")
2628
secret := testSecrets.MustGetField("SQUAREAPP_SECRET")
2729
secretInactive := testSecrets.MustGetField("SQUAREAPP_INACTIVE")
28-
id := testSecrets.MustGetField("SQUAREAPP_ID")
30+
2931
type args struct {
3032
ctx context.Context
3133
data []byte
@@ -48,28 +50,23 @@ func TestSquareApp_FromChunk(t *testing.T) {
4850
},
4951
want: []detectors.Result{
5052
{
51-
DetectorType: detectorspb.DetectorType_Square,
53+
DetectorType: detectorspb.DetectorType_SquareApp,
5254
Verified: true,
5355
Redacted: id,
56+
ExtraData: map[string]string{"Env": "Sandbox"},
5457
},
5558
},
5659
wantErr: false,
5760
},
5861
{
59-
name: "found, unverified",
62+
name: "found, unverified - detected but not added in result due to mismatch of env",
6063
s: Scanner{},
6164
args: args{
6265
ctx: context.Background(),
6366
data: []byte(fmt.Sprintf("You can find a squareapp secret %s within awsId %s", secretInactive, id)),
6467
verify: true,
6568
},
66-
want: []detectors.Result{
67-
{
68-
DetectorType: detectorspb.DetectorType_Square,
69-
Verified: false,
70-
Redacted: id,
71-
},
72-
},
69+
want: []detectors.Result{},
7370
wantErr: false,
7471
},
7572
{

pkg/detectors/squareapp/squareapp_test.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@ import (
1212
)
1313

1414
var (
15-
validKey = "YJsq0ige-a9khwVJOSwlzBvX0wp4j8t90s2d"
16-
invalidKey = "YJsq0ige-a9kh?VJOSwlzBvX0wp4j8t90s2d"
17-
validSec = "4sSPeeM_jk0VZiTFZJqEwzvXHjcCd6fsq0cvn-4pvyBIQ1OvY6dOv4X2AK5r6UJaFf0Xkp5NjV6lGhtbM"
18-
invalidSec = "4sSPeeM_jk0VZiTFZJqEwz?XHjcCd6fsq0cvn-4pvyBIQ1OvY6dOv4X2AK5r6UJaFf0Xkp5NjV6lGhtbM"
19-
keyword = "squareapp"
15+
validKey = "sq0ige-a9khwVJOSwlzBvX0wp4j8t90s2d"
16+
invalidKey = "YJsq0ige-a9kh?VJOSwlzBvX0wp4j8t90s2d"
17+
validSec = "4sSPeeM_jk0VZiTFZJqEwzvXHjcCd6fsq0cvn-4pvyBIQ1OvY6dOv4X2AK5r6UJaFf0Xkp5NjV6lGhtbM"
18+
invalidSec = "4sSPeeM_jk0VZiTFZJqEwz?XHjcCd6fsq0cvn-4pvyBIQ1OvY6dOv4X2AK5r6UJaFf0Xkp5NjV6lGhtbM"
19+
20+
// sandbox
21+
validSandboxKey = "sandbox-sq0idb-hFAKEQrhLGgFAKELZEDgpo"
22+
validSandboxSecret = "sandbox-sq0csb-o6cs8xFAKExEgIDGbzn2hFAKEZPbzhe713Q-FAKEfbY"
23+
keyword = "squareapp"
2024
)
2125

2226
func TestSquareApp_Pattern(t *testing.T) {
@@ -32,6 +36,11 @@ func TestSquareApp_Pattern(t *testing.T) {
3236
input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, validKey, keyword, validSec),
3337
want: []string{validKey},
3438
},
39+
{
40+
name: "valid sandbox pattern - with keyword squareapp",
41+
input: fmt.Sprintf("token - '%s'\n secret - '%s'\n", validSandboxKey, validSandboxSecret),
42+
want: []string{validSandboxKey},
43+
},
3544
{
3645
name: "invalid pattern",
3746
input: fmt.Sprintf("%s token - '%s'\n%s token - '%s'\n", keyword, invalidKey, keyword, invalidSec),

0 commit comments

Comments
 (0)