Added support for indeterminate verification for letter B detectors (#4049)

* first 10 detectors

* more detectors

---------

Co-authored-by: Nabeel Alam <[email protected]>
This commit is contained in:
Kashif Khan
2025-04-18 16:41:16 +05:00
committed by GitHub
co-authored by Nabeel Alam
parent b73fa5f818
commit 8e5ef0f0ec
31 changed files with 1062 additions and 495 deletions
+33 -12
View File
@@ -3,6 +3,7 @@ package bannerbear
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -46,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bannerbear.com/v2/auth", nil) isVerified, verificationErr := verifyBannerBear(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr, resMatch)
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -73,3 +65,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Bannerbear is an API for generating dynamic images, videos, and GIFs. Bannerbear API keys can be used to access and manipulate these resources." return "Bannerbear is an API for generating dynamic images, videos, and GIFs. Bannerbear API keys can be used to access and manipulate these resources."
} }
// docs: https://developers.bannerbear.com/
func verifyBannerBear(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bannerbear.com/v2/auth", http.NoBody)
if err != nil {
return false, err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
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:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+33 -12
View File
@@ -3,6 +3,7 @@ package baremetrics
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -52,18 +53,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.baremetrics.com/v1/account", nil) isVerified, verificationErr := verifyBaremetrics(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr, resMatch)
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -79,3 +71,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Baremetrics is a subscription analytics and insights tool. Baremetrics API keys can be used to access and analyze subscription data." return "Baremetrics is a subscription analytics and insights tool. Baremetrics API keys can be used to access and analyze subscription data."
} }
// docs: https://developers.baremetrics.com/reference/authentication
func verifyBaremetrics(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.baremetrics.com/v1/account", http.NoBody)
if err != nil {
return false, err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
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:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+33 -12
View File
@@ -2,6 +2,8 @@ package beamer
import ( import (
"context" "context"
"fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -45,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.getbeamer.com/v0/url", nil) isVerified, verificationErr := verifyBeamer(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr, resMatch)
}
req.Header.Add("Beamer-Api-Key", resMatch)
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -72,3 +65,31 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Beamer is a user engagement platform that helps you communicate product updates and other important information to your users. Beamer API keys can be used to access and manage this information." return "Beamer is a user engagement platform that helps you communicate product updates and other important information to your users. Beamer API keys can be used to access and manage this information."
} }
func verifyBeamer(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.getbeamer.com/v0/url", http.NoBody)
if err != nil {
return false, err
}
req.Header.Add("Beamer-Api-Key", key)
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+36 -17
View File
@@ -2,8 +2,8 @@ package beebole
import ( import (
"context" "context"
b64 "encoding/base64"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -47,22 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
data := fmt.Sprintf("%s:X", resMatch) isVerified, verificationErr := verifyBeebole(ctx, client, resMatch)
sEnc := b64.StdEncoding.EncodeToString([]byte(data)) s1.Verified = isVerified
payload := strings.NewReader(`{"service": "custom_field.list"}`) s1.SetVerificationError(verificationErr, resMatch)
req, err := http.NewRequestWithContext(ctx, "POST", "https://beebole-apps.com/api/v2", payload)
if err != nil {
continue
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", sEnc))
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -78,3 +65,35 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Beebole is a time tracking and business management tool. Beebole API keys can be used to access and manage time tracking data and other business-related information." return "Beebole is a time tracking and business management tool. Beebole API keys can be used to access and manage time tracking data and other business-related information."
} }
// docs: https://beebole.com/help/api/
func verifyBeebole(ctx context.Context, client *http.Client, key string) (bool, error) {
payload := strings.NewReader(`{"service": "custom_field.list"}`)
req, err := http.NewRequestWithContext(ctx, "POST", "https://beebole-apps.com/api/v2", payload)
if err != nil {
return false, err
}
req.Header.Add("Content-Type", "application/json")
req.SetBasicAuth(key, "x")
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+33 -15
View File
@@ -2,8 +2,8 @@ package besnappy
import ( import (
"context" "context"
b64 "encoding/base64"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -46,20 +46,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
Raw: []byte(resMatch), Raw: []byte(resMatch),
} }
if verify { if verify {
data := fmt.Sprintf("%s:x", resMatch) isVerified, verificationErr := verifyBesnappy(ctx, client, resMatch)
sEnc := b64.StdEncoding.EncodeToString([]byte(data)) s1.Verified = isVerified
req, err := http.NewRequestWithContext(ctx, "GET", "https://app.besnappy.com/api/v1/accounts", nil) s1.SetVerificationError(verificationErr, resMatch)
if err != nil {
continue
}
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", sEnc))
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -75,3 +64,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Besnappy is a customer service platform. The detected key can be used to access Besnappy's API, potentially exposing sensitive customer service data." return "Besnappy is a customer service platform. The detected key can be used to access Besnappy's API, potentially exposing sensitive customer service data."
} }
// docs: https://github.com/BeSnappy/api-docs
func verifyBesnappy(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://app.besnappy.com/api/v1/accounts", http.NoBody)
if err != nil {
return false, err
}
req.SetBasicAuth(key, "x")
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+41 -17
View File
@@ -2,6 +2,7 @@ package besttime
import ( import (
"context" "context"
"fmt"
"io" "io"
"net/http" "net/http"
"strings" "strings"
@@ -46,23 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://besttime.app/api/v1/keys/"+resMatch, nil) isVerified, verificationErr := verifyBesttime(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr, resMatch)
}
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
bodyBytes, err := io.ReadAll(res.Body)
if err != nil {
continue
}
body := string(bodyBytes)
if strings.Contains(body, `"status": "OK"`) {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -78,3 +65,40 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Besttime is a service used to predict the best time to visit a place. Besttime API keys can be used to access and utilize this service." return "Besttime is a service used to predict the best time to visit a place. Besttime API keys can be used to access and utilize this service."
} }
// docs: https://documentation.besttime.app/#api-reference
func verifyBesttime(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://besttime.app/api/v1/keys/"+key, nil)
if err != nil {
return false, err
}
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return false, err
}
body := string(bodyBytes)
if strings.Contains(body, `"status": "OK"`) {
return true, nil
} else if strings.Contains(body, `"message": "Invalid api_key_private`) {
return false, nil
}
return false, fmt.Errorf("unexpected response body: %s", body)
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+34 -19
View File
@@ -3,6 +3,7 @@ package betterstack
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -51,25 +52,10 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
if client == nil { if client == nil {
client = defaultClient client = defaultClient
} }
req, err := http.NewRequestWithContext(ctx, "GET", "https://uptime.betterstack.com/api/v2/monitors", nil)
if err != nil { isVerified, verificationErr := verifyBetterStack(ctx, client, resMatch)
continue s1.Verified = isVerified
} s1.SetVerificationError(verificationErr, resMatch)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
} else if res.StatusCode == 401 {
// The secret is determinately not verified (nothing to do)
} else {
err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
s1.SetVerificationError(err, resMatch)
}
} else {
s1.SetVerificationError(err, resMatch)
}
} }
results = append(results, s1) results = append(results, s1)
@@ -85,3 +71,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Betterstack is a monitoring service for uptime and performance of websites and APIs. Betterstack API keys can be used to access and manage these monitoring services." return "Betterstack is a monitoring service for uptime and performance of websites and APIs. Betterstack API keys can be used to access and manage these monitoring services."
} }
// docs: https://betterstack.com/docs/uptime/api/list-all-existing-monitors/
func verifyBetterStack(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://uptime.betterstack.com/api/v2/monitors", nil)
if err != nil {
return false, err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+34 -13
View File
@@ -3,6 +3,7 @@ package billomat
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -53,19 +54,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://%s.billomat.net/api/v2/clients/myself", resId), nil) isVerified, verificationErr := verifyBillomat(ctx, client, resId, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr, resMatch)
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("X-BillomatApiKey", resMatch)
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -82,3 +73,33 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Billomat is an online invoicing software. Billomat API keys can be used to access and manage invoices, clients, and other related data." return "Billomat is an online invoicing software. Billomat API keys can be used to access and manage invoices, clients, and other related data."
} }
// docs: https://www.billomat.com/en/api/basics/authentication/
func verifyBillomat(ctx context.Context, client *http.Client, id, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://%s.billomat.net/api/v2/clients/myself", id), nil)
if err != nil {
return false, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("X-BillomatApiKey", key)
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+35 -17
View File
@@ -2,8 +2,8 @@ package bitbar
import ( import (
"context" "context"
b64 "encoding/base64"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -23,7 +23,7 @@ var (
client = common.SaneHttpClient() client = common.SaneHttpClient()
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives. // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bitbar"}) + `\b([0-9a-z]{32})\b`) keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"bitbar"}) + `\b([0-9a-zA-Z]{32})\b`)
) )
// Keywords are used for efficiently pre-filtering chunks. // Keywords are used for efficiently pre-filtering chunks.
@@ -47,21 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
data := fmt.Sprintf("%s:", resMatch) isVerified, verificationErr := verifyBitBar(ctx, client, resMatch)
baseToken := b64.StdEncoding.EncodeToString([]byte(data)) s1.Verified = isVerified
req, err := http.NewRequestWithContext(ctx, "GET", "https://cloud.bitbar.com/api/me", nil) s1.SetVerificationError(verificationErr, resMatch)
if err != nil {
continue
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", baseToken))
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -77,3 +65,33 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Bitbar provides a cloud-based mobile app testing platform. Bitbar API keys can be used to access and manage testing resources and data." return "Bitbar provides a cloud-based mobile app testing platform. Bitbar API keys can be used to access and manage testing resources and data."
} }
// docs: https://support.smartbear.com/bitbar/docs/en/use-rest-apis-with-bitbar.html
func verifyBitBar(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://cloud.bitbar.com/api/me", http.NoBody)
if err != nil {
return false, err
}
req.Header.Add("Content-Type", "application/json")
req.SetBasicAuth(key, "")
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+44 -19
View File
@@ -3,6 +3,8 @@ package bitcoinaverage
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -49,26 +51,11 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_BitcoinAverage, DetectorType: detectorspb.DetectorType_BitcoinAverage,
Raw: []byte(resMatch), Raw: []byte(resMatch),
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://apiv2.bitcoinaverage.com/websocket/v3/get_ticket", nil) isVerified, verificationErr := verifyBitcoinAverage(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr, resMatch)
}
req.Header.Add("x-ba-key", resMatch)
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
resp := &response{}
if err = json.NewDecoder(res.Body).Decode(resp); err != nil {
s1.SetVerificationError(err, resMatch)
continue
}
if resp.Success {
s1.Verified = true
}
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -84,3 +71,41 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "BitcoinAverage is a service that provides cryptocurrency market data. BitcoinAverage API keys can be used to access and retrieve this market data." return "BitcoinAverage is a service that provides cryptocurrency market data. BitcoinAverage API keys can be used to access and retrieve this market data."
} }
// docs: https://apiv2.bitcoinaverage.com/#authentication
func verifyBitcoinAverage(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://apiv2.bitcoinaverage.com/websocket/v3/get_ticket", nil)
if err != nil {
return false, err
}
req.Header.Add("x-ba-key", key)
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
apiResponse := &response{}
if err = json.NewDecoder(resp.Body).Decode(apiResponse); err != nil {
return false, err
}
if apiResponse.Success {
return true, nil
}
return false, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+34 -34
View File
@@ -43,50 +43,34 @@ func (s Scanner) Keywords() []string {
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data) dataStr := string(data)
apiKeyMatches := apiKeyPat.FindAllStringSubmatch(dataStr, -1) var uniqueAPIKeys, uniqueAPISecrets = make(map[string]struct{}), make(map[string]struct{})
apiSecretMatches := apiSecretPat.FindAllStringSubmatch(dataStr, -1)
for _, apiKeyMatch := range apiKeyMatches { for _, apiKey := range apiKeyPat.FindAllStringSubmatch(dataStr, -1) {
apiKeyRes := strings.TrimSpace(apiKeyMatch[1]) uniqueAPIKeys[apiKey[1]] = struct{}{}
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Bitfinex,
Raw: []byte(apiKeyRes),
} }
for _, apiSecretMatch := range apiSecretMatches { for _, apiSecret := range apiSecretPat.FindAllStringSubmatch(dataStr, -1) {
apiSecretRes := strings.TrimSpace(apiSecretMatch[1]) uniqueAPISecrets[apiSecret[1]] = struct{}{}
}
if apiKeyRes == apiSecretRes { for apiKey := range uniqueAPIKeys {
for apiSecret := range uniqueAPISecrets {
// as both patterns are same, avoid verifying same string for both
if apiKey == apiSecret {
continue continue
} }
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Bitfinex,
Raw: []byte(apiKey),
}
if verify { if verify {
// thankfully official golang examples exist but you just need to dig their many repos https://github.com/bitfinexcom/bitfinex-api-go/blob/master/examples/v2/rest-orders/main.go isVerified, verificationErr := verifyBitfinex(apiKey, apiSecret)
key := apiKeyRes s1.Verified = isVerified
secret := apiSecretRes s1.SetVerificationError(verificationErr)
http.DefaultClient = client // filed https://github.com/bitfinexcom/bitfinex-api-go/issues/238 to improve this
c := rest.NewClientWithURL(*api).Credentials(key, secret)
isValid := true // assume valid
_, err = c.Orders.AllHistory()
if err != nil {
if strings.HasPrefix(err.Error(), "POST https://") { // eg POST https://api-pub.bitfinex.com/v2/auth/r/orders/hist: 500 apikey: digest invalid (10100)
isValid = false
}
} }
s1.Verified = isValid
// If there is a valid one, we need to stop iterating now and return the valid result
if isValid {
break
}
}
}
// By appending results in the outer loop we can reduce false positives if there are multiple
// combinations of secrets and IDs found.
if len(apiSecretMatches) > 0 {
results = append(results, s1) results = append(results, s1)
} }
} }
@@ -101,3 +85,19 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Bitfinex is a cryptocurrency exchange offering various trading options. Bitfinex API keys can be used to access and manage trading accounts." return "Bitfinex is a cryptocurrency exchange offering various trading options. Bitfinex API keys can be used to access and manage trading accounts."
} }
// docs: https://docs.bitfinex.com/docs/introduction
func verifyBitfinex(key, secret string) (bool, error) {
// thankfully official golang examples exist but you just need to dig their many repos https://github.com/bitfinexcom/bitfinex-api-go/blob/master/examples/v2/rest-orders/main.go
http.DefaultClient = client
c := rest.NewClientWithURL(*api).Credentials(key, secret)
_, err := c.Orders.AllHistory()
if err != nil {
if strings.HasPrefix(err.Error(), "POST https://") { // eg POST https://api-pub.bitfinex.com/v2/auth/r/orders/hist: 500 apikey: digest invalid (10100)
return false, nil
}
}
return true, nil
}
+49 -30
View File
@@ -5,6 +5,8 @@ import (
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"fmt"
"io"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
@@ -59,28 +61,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
isVerified, verificationErr := verifyBitmex(ctx, client, resMatch, resSecretMatch)
timestamp := strconv.FormatInt(time.Now().Unix()+5, 10) s1.Verified = isVerified
action := "GET" s1.SetVerificationError(verificationErr)
path := "/api/v1/user"
payload := url.Values{}
signature := getBitmexSignature(timestamp, resSecretMatch, action, path, payload.Encode())
req, err := http.NewRequestWithContext(ctx, action, "https://www.bitmex.com"+path, strings.NewReader(payload.Encode()))
if err != nil {
continue
}
req.Header.Add("api-expires", timestamp)
req.Header.Add("api-key", resMatch)
req.Header.Add("api-signature", signature)
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -90,14 +73,6 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
return results, nil return results, nil
} }
func getBitmexSignature(timeStamp string, secret string, action string, path string, payload string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(action + path + timeStamp + payload))
macsum := mac.Sum(nil)
return hex.EncodeToString(macsum)
}
func (s Scanner) Type() detectorspb.DetectorType { func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Bitmex return detectorspb.DetectorType_Bitmex
} }
@@ -105,3 +80,47 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Bitmex is a cryptocurrency exchange and derivative trading platform. Bitmex API keys can be used to access and trade on the platform programmatically." return "Bitmex is a cryptocurrency exchange and derivative trading platform. Bitmex API keys can be used to access and trade on the platform programmatically."
} }
// docs: https://www.bitmex.com/app/apiKeysUsage
func verifyBitmex(ctx context.Context, client *http.Client, key, secret string) (bool, error) {
timestamp := strconv.FormatInt(time.Now().Unix()+5, 10)
action := "GET"
path := "/api/v1/user"
payload := url.Values{}
signature := getBitmexSignature(timestamp, secret, action, path, payload.Encode())
req, err := http.NewRequestWithContext(ctx, action, "https://www.bitmex.com"+path, strings.NewReader(payload.Encode()))
if err != nil {
return false, err
}
req.Header.Add("api-expires", timestamp)
req.Header.Add("api-key", key)
req.Header.Add("api-signature", signature)
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
func getBitmexSignature(timeStamp string, secret string, action string, path string, payload string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(action + path + timeStamp + payload))
macsum := mac.Sum(nil)
return hex.EncodeToString(macsum)
}
+34 -13
View File
@@ -3,6 +3,7 @@ package blazemeter
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -46,19 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.runscope.com/account", nil) isVerified, verificationErr := verifyBlazeMeter(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr)
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -74,3 +65,33 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Blazemeter is a continuous testing platform for DevOps. The keys can be used to access and manage performance tests and other resources." return "Blazemeter is a continuous testing platform for DevOps. The keys can be used to access and manage performance tests and other resources."
} }
// docs: https://help.blazemeter.com/apidocs/api-monitoring/account.htm?tocpath=API%20Monitoring%7C_____12
func verifyBlazeMeter(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.runscope.com/account", nil)
if err != nil {
return false, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
resp, err := client.Do(req)
if err != nil {
return false, err
}
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)
}
}
+34 -12
View File
@@ -2,6 +2,8 @@ package blitapp
import ( import (
"context" "context"
"fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -45,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://blitapp.com/api/apps/all", nil) isVerified, verificationErr := verifyBlitApp(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr)
}
req.Header.Add("API-Key", resMatch)
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -72,3 +65,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "BlitApp is a service used for managing applications. BlitApp API keys can be used to access and modify application data." return "BlitApp is a service used for managing applications. BlitApp API keys can be used to access and modify application data."
} }
// docs: https://blitapp.com/api/#/App/get_apps_all
func verifyBlitApp(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://blitapp.com/api/apps/all", nil)
if err != nil {
return false, nil
}
req.Header.Add("API-Key", key)
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:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+34 -17
View File
@@ -2,6 +2,7 @@ package blocknative
import ( import (
"context" "context"
"fmt"
"io" "io"
"net/http" "net/http"
"strings" "strings"
@@ -46,23 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.blocknative.com/gasprices/blockprices", nil) isVerified, verificationErr := verifyBlocknative(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr)
}
req.Header.Add("Authorization", resMatch)
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
bodyBytes, err := io.ReadAll(res.Body)
if err != nil {
continue
}
body := string(bodyBytes)
if !strings.Contains(body, "valid") {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -78,3 +65,33 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Blocknative is a platform that provides real-time blockchain transaction monitoring and notification services. Blocknative API keys can be used to access and interact with these services." return "Blocknative is a platform that provides real-time blockchain transaction monitoring and notification services. Blocknative API keys can be used to access and interact with these services."
} }
// docs: https://docs.blocknative.com/gas-prediction/gas-platform#api-endpoint
func verifyBlocknative(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.blocknative.com/gasprices/blockprices", nil)
if err != nil {
return false, err
}
req.Header.Add("Authorization", key)
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
// Right now the blocknative API logic is broken and return 200 for invalid key as well
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized, http.StatusTooManyRequests:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+32 -11
View File
@@ -2,6 +2,8 @@ package blogger
import ( import (
"context" "context"
"fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -44,17 +46,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://www.googleapis.com/blogger/v3/blogs/2399953?key="+resMatch, nil) isVerified, verificationErr := verifyBlogger(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr)
}
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -70,3 +64,30 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Blogger API keys can be used to access and manage blogs on the Blogger platform." return "Blogger API keys can be used to access and manage blogs on the Blogger platform."
} }
// docs: https://developers.google.com/blogger/docs/3.0/using
func verifyBlogger(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://www.googleapis.com/blogger/v3/blogs/2399953?key="+key, nil)
if err != nil {
return false, err
}
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusBadRequest, http.StatusForbidden:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+34 -13
View File
@@ -2,6 +2,8 @@ package bombbomb
import ( import (
"context" "context"
"fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -45,19 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
// Reference : https://developer.bombbomb.com/api#operations-Users-UserInfo isVerified, verificationErr := verifyBombBomb(ctx, client, resMatch)
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bombbomb.com/v2/user/", nil) s1.Verified = isVerified
if err != nil { s1.SetVerificationError(verificationErr)
continue
}
req.Header.Add("Authorization", "Bearer "+resMatch)
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -73,3 +65,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "BombBomb is a video messaging platform that allows users to create and send video emails. BombBomb API keys can be used to access and manage video email campaigns and contacts." return "BombBomb is a video messaging platform that allows users to create and send video emails. BombBomb API keys can be used to access and manage video email campaigns and contacts."
} }
// docs: https://developer.bombbomb.com/api#operations-Users-UserInfo
func verifyBombBomb(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bombbomb.com/v2/user/", nil)
if err != nil {
return false, err
}
req.Header.Add("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+33 -12
View File
@@ -3,6 +3,7 @@ package boostnote
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -46,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://boostnote.io/api/docs", nil) isVerified, verificationErr := verifyBoostnote(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr)
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -73,3 +65,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "BoostNote is a note-taking application. The secret detected here is likely an API key or token used to access BoostNote services." return "BoostNote is a note-taking application. The secret detected here is likely an API key or token used to access BoostNote services."
} }
// docs: https://boostnote.io/features/public-api
func verifyBoostnote(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://boostnote.io/api/docs", nil)
if err != nil {
return false, err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+49 -25
View File
@@ -48,31 +48,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
timeout := 10 * time.Second isVerified, verificationErr := verifyBorgbase(ctx, client, resMatch)
client.Timeout = timeout s1.Verified = isVerified
payload := strings.NewReader(`{"query":"{ sshList {id, name}}"}`) s1.SetVerificationError(verificationErr)
req, err := http.NewRequestWithContext(ctx, "POST", "https://api.borgbase.com/graphql", payload)
if err != nil {
continue
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
res, err := client.Do(req)
if err == nil {
bodyBytes, err := io.ReadAll(res.Body)
if err == nil {
bodyString := string(bodyBytes)
validResponse := strings.Contains(bodyString, `"sshList":[]`)
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
if validResponse {
s1.Verified = true
} else {
s1.Verified = false
}
}
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -88,3 +66,49 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Borgbase is a service for hosting Borg repositories. Borgbase API keys can be used to manage and access these repositories." return "Borgbase is a service for hosting Borg repositories. Borgbase API keys can be used to manage and access these repositories."
} }
// docs: https://docs.borgbase.com/api
func verifyBorgbase(ctx context.Context, client *http.Client, key string) (bool, error) {
timeout := 10 * time.Second
client.Timeout = timeout
payload := strings.NewReader(`{"query":"{ sshList {id, name}}"}`)
req, err := http.NewRequestWithContext(ctx, "POST", "https://api.borgbase.com/graphql", payload)
if err != nil {
return false, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key))
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return false, err
}
bodyString := string(bodyBytes)
validResponse := strings.Contains(bodyString, `"sshList":[]`)
if validResponse {
return true, nil
}
return false, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
@@ -3,11 +3,12 @@ package braintreepayments
import ( import (
"context" "context"
"fmt" "fmt"
regexp "github.com/wasilibs/go-re2"
"io" "io"
"net/http" "net/http"
"strings" "strings"
regexp "github.com/wasilibs/go-re2"
"github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
@@ -105,7 +106,10 @@ func verifyBraintree(ctx context.Context, client *http.Client, url, pubKey, priv
if err != nil { if err != nil {
return false, err return false, err
} }
defer res.Body.Close() defer func() {
_, _ = io.Copy(io.Discard, res.Body)
_ = res.Body.Close()
}()
bodyString := string(bodyBytes) bodyString := string(bodyBytes)
if !(res.StatusCode == http.StatusOK) { if !(res.StatusCode == http.StatusOK) {
+39 -16
View File
@@ -2,6 +2,8 @@ package brandfetch
import ( import (
"context" "context"
"fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -45,22 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
payload := strings.NewReader(`{ isVerified, verificationErr := verifyBrandFetch(ctx, client, resMatch)
"domain": "www.example.com" s1.Verified = isVerified
}`) s1.SetVerificationError(verificationErr)
req, err := http.NewRequestWithContext(ctx, "POST", "https://api.brandfetch.io/v1/color", payload)
if err != nil {
continue
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("x-api-key", resMatch)
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -76,3 +65,37 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Brandfetch is a service that provides brand data, including logos, colors, fonts, and more. Brandfetch API keys can be used to access this data." return "Brandfetch is a service that provides brand data, including logos, colors, fonts, and more. Brandfetch API keys can be used to access this data."
} }
// docs: https://docs.brandfetch.com/docs/brand-api#overview
func verifyBrandFetch(ctx context.Context, client *http.Client, key string) (bool, error) {
payload := strings.NewReader(`{
"domain": "www.example.com"
}`)
req, err := http.NewRequestWithContext(ctx, "POST", "https://api.brandfetch.io/v1/color", payload)
if err != nil {
return false, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("x-api-key", key)
resp, err := client.Do(req)
if err != nil {
return false, err
}
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)
}
}
+32 -11
View File
@@ -2,6 +2,8 @@ package browshot
import ( import (
"context" "context"
"fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -45,17 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.browshot.com/api/v1/instance/list?key="+resMatch, nil) isVerified, verificationErr := verifyBrowshot(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr)
}
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -71,3 +65,30 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Browshot is a service that allows you to take screenshots of web pages from different browsers and devices. Browshot API keys can be used to automate and manage these screenshots." return "Browshot is a service that allows you to take screenshots of web pages from different browsers and devices. Browshot API keys can be used to automate and manage these screenshots."
} }
// docs: https://browshot.com/api/documentation#instance_list
func verifyBrowshot(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.browshot.com/api/v1/instance/list?key="+key, nil)
if err != nil {
return false, err
}
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusBadRequest, http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+42 -17
View File
@@ -2,6 +2,7 @@ package bscscan
import ( import (
"context" "context"
"fmt"
"io" "io"
"net/http" "net/http"
"strings" "strings"
@@ -46,23 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bscscan.com/api?module=account&action=balance&address=0x70F657164e5b75689b64B7fd1fA275F334f28e18&apikey="+resMatch, nil) isVerified, verificationErr := verifyBscScan(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr)
}
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
bodyBytes, err := io.ReadAll(res.Body)
if err != nil {
continue
}
body := string(bodyBytes)
if !strings.Contains(body, "NOTOK") {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -78,3 +65,41 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "BscScan is a block explorer and analytics platform for Binance Smart Chain. BscScan API keys can be used to access data from the Binance Smart Chain blockchain." return "BscScan is a block explorer and analytics platform for Binance Smart Chain. BscScan API keys can be used to access data from the Binance Smart Chain blockchain."
} }
// docs: https://docs.bscscan.com/api-endpoints/accounts
func verifyBscScan(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bscscan.com/api?module=account&action=balance&address=0x70F657164e5b75689b64B7fd1fA275F334f28e18&apikey="+key, nil)
if err != nil {
return false, err
}
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return false, err
}
body := string(bodyBytes)
if !strings.Contains(body, "NOTOK") {
return true, nil
}
return false, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+34 -13
View File
@@ -3,6 +3,7 @@ package buddyns
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -46,19 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://www.buddyns.com/api/v2/zone/", nil) isVerified, verificationErr := verifyBuddyns(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr)
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("Token %s", resMatch))
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -74,3 +65,33 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "BuddyNS is a DNS hosting service. BuddyNS API keys can be used to manage DNS zones and records." return "BuddyNS is a DNS hosting service. BuddyNS API keys can be used to manage DNS zones and records."
} }
// docs: https://www.buddyns.com/support/api/v2/
func verifyBuddyns(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://www.buddyns.com/api/v2/zone/", nil)
if err != nil {
return false, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("Token %s", key))
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+38 -25
View File
@@ -3,6 +3,7 @@ package budibase
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -52,31 +53,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
client = defaultClient client = defaultClient
} }
// URL: https://docs.budibase.com/reference/appsearch isVerified, verificationErr := verifyBudibase(ctx, client, resMatch)
// API searches for the app with given name, since we only need to check api key, sending any appname will work. s1.Verified = isVerified
payload := strings.NewReader(`{"name":"qwerty"}`) s1.SetVerificationError(verificationErr)
req, err := http.NewRequestWithContext(ctx, "POST", "https://budibase.app/api/public/v1/applications/search", payload)
if err != nil {
continue
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("x-budibase-api-key", resMatch)
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
} else if res.StatusCode == 401 {
// The secret is determinately not verified (nothing to do)
} else {
err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
s1.SetVerificationError(err, resMatch)
}
} else {
s1.SetVerificationError(err, resMatch)
}
} }
results = append(results, s1) results = append(results, s1)
@@ -92,3 +71,37 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Budibase is a low-code platform for creating internal tools. Budibase API keys can be used to access and modify applications and data within the platform." return "Budibase is a low-code platform for creating internal tools. Budibase API keys can be used to access and modify applications and data within the platform."
} }
// docs: https://docs.budibase.com/docs/rest
func verifyBudibase(ctx context.Context, client *http.Client, key string) (bool, error) {
// URL: https://docs.budibase.com/reference/appsearch
// API searches for the app with given name, since we only need to check api key, sending any appname will work.
payload := strings.NewReader(`{"name":"qwerty"}`)
req, err := http.NewRequestWithContext(ctx, "POST", "https://budibase.app/api/public/v1/applications/search", payload)
if err != nil {
return false, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("x-budibase-api-key", key)
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
@@ -69,7 +69,7 @@ func TestBudibase_FromChunk(t *testing.T) {
want: func() []detectors.Result { want: func() []detectors.Result {
r := detectors.Result{ r := detectors.Result{
DetectorType: detectorspb.DetectorType_Budibase, DetectorType: detectorspb.DetectorType_Budibase,
Verified: true, Verified: false,
} }
r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 403")) r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 403"))
return []detectors.Result{r} return []detectors.Result{r}
+36 -17
View File
@@ -2,12 +2,13 @@ package bugherd
import ( import (
"context" "context"
b64 "encoding/base64"
"fmt" "fmt"
regexp "github.com/wasilibs/go-re2" "io"
"net/http" "net/http"
"strings" "strings"
regexp "github.com/wasilibs/go-re2"
"github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
@@ -46,21 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
data := fmt.Sprintf("%s:x", resMatch) isVerified, verificationErr := verifyBugherd(ctx, client, resMatch)
sEnc := b64.StdEncoding.EncodeToString([]byte(data)) s1.Verified = isVerified
req, err := http.NewRequestWithContext(ctx, "GET", "https://www.bugherd.com/api_v2/projects.json", nil) s1.SetVerificationError(verificationErr)
if err != nil {
continue
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", sEnc))
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -76,3 +65,33 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Bugherd is a visual feedback and bug tracking tool for websites. Bugherd API keys can be used to access and manage projects, tasks, and feedback data." return "Bugherd is a visual feedback and bug tracking tool for websites. Bugherd API keys can be used to access and manage projects, tasks, and feedback data."
} }
// docs: https://www.bugherd.com/api_v2
func verifyBugherd(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://www.bugherd.com/api_v2/projects.json", nil)
if err != nil {
return false, err
}
req.Header.Add("Content-Type", "application/json")
req.SetBasicAuth(key, "x")
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+34 -13
View File
@@ -3,6 +3,7 @@ package bugsnag
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strings" "strings"
@@ -46,19 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bugsnag.com/user/organizations?admin", nil) isVerified, verificationErr := verifyBugsnag(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr)
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("token %s", resMatch))
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -74,3 +65,33 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Bugsnag is an error monitoring service for web and mobile applications. Bugsnag API keys can be used to report and manage errors." return "Bugsnag is an error monitoring service for web and mobile applications. Bugsnag API keys can be used to report and manage errors."
} }
// docs: https://docs.bugsnag.com/api/
func verifyBugsnag(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bugsnag.com/user/organizations?admin", nil)
if err != nil {
return false, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("token %s", key))
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+43 -25
View File
@@ -3,11 +3,12 @@ package bulbul
import ( import (
"context" "context"
"fmt" "fmt"
regexp "github.com/wasilibs/go-re2"
"io" "io"
"net/http" "net/http"
"strings" "strings"
regexp "github.com/wasilibs/go-re2"
"github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
@@ -46,30 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://prod-api.bulbul.io/view_all_users?api_key=%s", resMatch), nil) isVerified, verificationErr := verifyBulbul(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr)
}
res, err := client.Do(req)
if err == nil {
bodyBytes, err := io.ReadAll(res.Body)
if err != nil {
continue
}
bodyString := string(bodyBytes)
validResponse := strings.Contains(bodyString, `"message":"Successful",`)
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
if validResponse {
s1.Verified = true
} else {
s1.Verified = false
}
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -85,3 +65,41 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "Bulbul is an API service. Bulbul API keys can be used to access and modify data within the service." return "Bulbul is an API service. Bulbul API keys can be used to access and modify data within the service."
} }
// docs: https://docs.jungleworks.com/bulbul/bulbul-api-details
func verifyBulbul(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://prod-api.bulbul.io/view_all_users?api_key=%s", key), nil)
if err != nil {
return false, err
}
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return false, err
}
bodyString := string(bodyBytes)
if strings.Contains(bodyString, `"message":"Successful",`) {
return true, nil
} else {
return false, nil
}
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+33 -21
View File
@@ -2,6 +2,7 @@ package bulksms
import ( import (
"context" "context"
"fmt"
"io" "io"
"net/http" "net/http"
@@ -56,27 +57,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bulksms.com/v1/messages", nil) isVerified, verificationErr := verifyBulksms(ctx, client, id, key)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr)
}
req.SetBasicAuth(id, key)
res, err := client.Do(req)
if err == nil {
defer func() {
_, _ = io.Copy(io.Discard, res.Body)
_ = res.Body.Close()
}()
if res.StatusCode == http.StatusOK {
s1.Verified = true
results = append(results, s1)
// move to next id, by skipping remaining key's
break
}
} else {
s1.SetVerificationError(err, key)
}
} }
results = append(results, s1) results = append(results, s1)
@@ -93,3 +76,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "BulkSMS is a service used for sending SMS messages in bulk. BulkSMS credentials can be used to access and send messages through the BulkSMS API." return "BulkSMS is a service used for sending SMS messages in bulk. BulkSMS credentials can be used to access and send messages through the BulkSMS API."
} }
// docs: https://www.bulksms.com/developer/json/v1/
func verifyBulksms(ctx context.Context, client *http.Client, id, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bulksms.com/v1/messages", nil)
if err != nil {
return false, err
}
req.SetBasicAuth(id, key)
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}
+34 -13
View File
@@ -2,10 +2,13 @@ package buttercms
import ( import (
"context" "context"
regexp "github.com/wasilibs/go-re2" "fmt"
"io"
"net/http" "net/http"
"strings" "strings"
regexp "github.com/wasilibs/go-re2"
"github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
@@ -44,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} }
if verify { if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.buttercms.com/v2/posts/?auth_token="+resMatch, nil) isVerified, verificationErr := verifyButterCMS(ctx, client, resMatch)
if err != nil { s1.Verified = isVerified
continue s1.SetVerificationError(verificationErr)
}
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
} }
results = append(results, s1) results = append(results, s1)
@@ -71,3 +65,30 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string { func (s Scanner) Description() string {
return "ButterCMS is a headless CMS that enables developers to build websites and applications with a content management system. The API keys can be used to access and modify content stored in ButterCMS." return "ButterCMS is a headless CMS that enables developers to build websites and applications with a content management system. The API keys can be used to access and modify content stored in ButterCMS."
} }
// docs: https://buttercms.com/docs/api/#introduction
func verifyButterCMS(ctx context.Context, client *http.Client, key string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.buttercms.com/v2/posts/?auth_token="+key, nil)
if err != nil {
return false, err
}
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}