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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -46,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bannerbear.com/v2/auth", nil)
if err != nil {
continue
}
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
}
}
isVerified, verificationErr := verifyBannerBear(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resMatch)
}
results = append(results, s1)
@@ -73,3 +65,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -52,18 +53,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.baremetrics.com/v1/account", nil)
if err != nil {
continue
}
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
}
}
isVerified, verificationErr := verifyBaremetrics(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resMatch)
}
results = append(results, s1)
@@ -79,3 +71,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -45,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.getbeamer.com/v0/url", nil)
if err != nil {
continue
}
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
}
}
isVerified, verificationErr := verifyBeamer(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resMatch)
}
results = append(results, s1)
@@ -72,3 +65,31 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
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 (
"context"
b64 "encoding/base64"
"fmt"
"io"
"net/http"
"strings"
@@ -47,22 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
data := fmt.Sprintf("%s:X", resMatch)
sEnc := b64.StdEncoding.EncodeToString([]byte(data))
payload := strings.NewReader(`{"service": "custom_field.list"}`)
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
}
}
isVerified, verificationErr := verifyBeebole(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resMatch)
}
results = append(results, s1)
@@ -78,3 +65,35 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
b64 "encoding/base64"
"fmt"
"io"
"net/http"
"strings"
@@ -46,20 +46,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
Raw: []byte(resMatch),
}
if verify {
data := fmt.Sprintf("%s:x", resMatch)
sEnc := b64.StdEncoding.EncodeToString([]byte(data))
req, err := http.NewRequestWithContext(ctx, "GET", "https://app.besnappy.com/api/v1/accounts", nil)
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
}
}
isVerified, verificationErr := verifyBesnappy(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resMatch)
}
results = append(results, s1)
@@ -75,3 +64,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -46,23 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://besttime.app/api/v1/keys/"+resMatch, nil)
if err != nil {
continue
}
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
}
}
isVerified, verificationErr := verifyBesttime(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resMatch)
}
results = append(results, s1)
@@ -78,3 +65,40 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -51,25 +52,10 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
if client == nil {
client = defaultClient
}
req, err := http.NewRequestWithContext(ctx, "GET", "https://uptime.betterstack.com/api/v2/monitors", nil)
if err != nil {
continue
}
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)
}
isVerified, verificationErr := verifyBetterStack(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resMatch)
}
results = append(results, s1)
@@ -85,3 +71,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -53,19 +54,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://%s.billomat.net/api/v2/clients/myself", resId), nil)
if err != nil {
continue
}
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
}
}
isVerified, verificationErr := verifyBillomat(ctx, client, resId, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resMatch)
}
results = append(results, s1)
@@ -82,3 +73,33 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
b64 "encoding/base64"
"fmt"
"io"
"net/http"
"strings"
@@ -23,7 +23,7 @@ var (
client = common.SaneHttpClient()
// 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.
@@ -47,21 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
data := fmt.Sprintf("%s:", resMatch)
baseToken := b64.StdEncoding.EncodeToString([]byte(data))
req, err := http.NewRequestWithContext(ctx, "GET", "https://cloud.bitbar.com/api/me", nil)
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
}
}
isVerified, verificationErr := verifyBitBar(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resMatch)
}
results = append(results, s1)
@@ -77,3 +65,33 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
@@ -49,26 +51,11 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_BitcoinAverage,
Raw: []byte(resMatch),
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://apiv2.bitcoinaverage.com/websocket/v3/get_ticket", nil)
if err != nil {
continue
}
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
}
}
}
isVerified, verificationErr := verifyBitcoinAverage(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resMatch)
}
results = append(results, s1)
@@ -84,3 +71,41 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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)
}
}
+35 -35
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) {
dataStr := string(data)
apiKeyMatches := apiKeyPat.FindAllStringSubmatch(dataStr, -1)
apiSecretMatches := apiSecretPat.FindAllStringSubmatch(dataStr, -1)
var uniqueAPIKeys, uniqueAPISecrets = make(map[string]struct{}), make(map[string]struct{})
for _, apiKeyMatch := range apiKeyMatches {
apiKeyRes := strings.TrimSpace(apiKeyMatch[1])
for _, apiKey := range apiKeyPat.FindAllStringSubmatch(dataStr, -1) {
uniqueAPIKeys[apiKey[1]] = struct{}{}
}
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Bitfinex,
Raw: []byte(apiKeyRes),
}
for _, apiSecret := range apiSecretPat.FindAllStringSubmatch(dataStr, -1) {
uniqueAPISecrets[apiSecret[1]] = struct{}{}
}
for _, apiSecretMatch := range apiSecretMatches {
apiSecretRes := strings.TrimSpace(apiSecretMatch[1])
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
}
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Bitfinex,
Raw: []byte(apiKey),
}
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
key := apiKeyRes
secret := apiSecretRes
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
}
isVerified, verificationErr := verifyBitfinex(apiKey, apiSecret)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
}
// 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)
}
}
@@ -101,3 +85,19 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
@@ -59,28 +61,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
timestamp := strconv.FormatInt(time.Now().Unix()+5, 10)
action := "GET"
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
}
}
isVerified, verificationErr := verifyBitmex(ctx, client, resMatch, resSecretMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -90,14 +73,6 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
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 {
return detectorspb.DetectorType_Bitmex
}
@@ -105,3 +80,47 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -46,19 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.runscope.com/account", nil)
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 {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
isVerified, verificationErr := verifyBlazeMeter(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -74,3 +65,33 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -45,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://blitapp.com/api/apps/all", nil)
if err != nil {
continue
}
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
}
}
isVerified, verificationErr := verifyBlitApp(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -72,3 +65,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -46,23 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.blocknative.com/gasprices/blockprices", nil)
if err != nil {
continue
}
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
}
}
isVerified, verificationErr := verifyBlocknative(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -78,3 +65,33 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -44,17 +46,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://www.googleapis.com/blogger/v3/blogs/2399953?key="+resMatch, nil)
if err != nil {
continue
}
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
isVerified, verificationErr := verifyBlogger(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -70,3 +64,30 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string {
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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -45,19 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
// Reference : https://developer.bombbomb.com/api#operations-Users-UserInfo
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bombbomb.com/v2/user/", nil)
if err != nil {
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
}
}
isVerified, verificationErr := verifyBombBomb(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -73,3 +65,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -46,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://boostnote.io/api/docs", nil)
if err != nil {
continue
}
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
}
}
isVerified, verificationErr := verifyBoostnote(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -73,3 +65,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 {
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 {
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
}
}
}
}
isVerified, verificationErr := verifyBorgbase(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -88,3 +66,49 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
regexp "github.com/wasilibs/go-re2"
"io"
"net/http"
"strings"
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"
@@ -105,7 +106,10 @@ func verifyBraintree(ctx context.Context, client *http.Client, url, pubKey, priv
if err != nil {
return false, err
}
defer res.Body.Close()
defer func() {
_, _ = io.Copy(io.Discard, res.Body)
_ = res.Body.Close()
}()
bodyString := string(bodyBytes)
if !(res.StatusCode == http.StatusOK) {
+39 -16
View File
@@ -2,6 +2,8 @@ package brandfetch
import (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -45,22 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
payload := strings.NewReader(`{
"domain": "www.example.com"
}`)
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
}
}
isVerified, verificationErr := verifyBrandFetch(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -76,3 +65,37 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -45,17 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.browshot.com/api/v1/instance/list?key="+resMatch, nil)
if err != nil {
continue
}
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
isVerified, verificationErr := verifyBrowshot(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -71,3 +65,30 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -46,23 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bscscan.com/api?module=account&action=balance&address=0x70F657164e5b75689b64B7fd1fA275F334f28e18&apikey="+resMatch, nil)
if err != nil {
continue
}
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
}
}
isVerified, verificationErr := verifyBscScan(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -78,3 +65,41 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -46,19 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://www.buddyns.com/api/v2/zone/", nil)
if err != nil {
continue
}
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
}
}
isVerified, verificationErr := verifyBuddyns(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -74,3 +65,33 @@ func (s Scanner) Type() detectorspb.DetectorType {
func (s Scanner) Description() string {
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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -52,31 +53,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
client = defaultClient
}
// 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 {
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)
}
isVerified, verificationErr := verifyBudibase(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -92,3 +71,37 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 {
r := detectors.Result{
DetectorType: detectorspb.DetectorType_Budibase,
Verified: true,
Verified: false,
}
r.SetVerificationError(fmt.Errorf("unexpected HTTP response status 403"))
return []detectors.Result{r}
+36 -17
View File
@@ -2,12 +2,13 @@ package bugherd
import (
"context"
b64 "encoding/base64"
"fmt"
regexp "github.com/wasilibs/go-re2"
"io"
"net/http"
"strings"
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"
@@ -46,21 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
data := fmt.Sprintf("%s:x", resMatch)
sEnc := b64.StdEncoding.EncodeToString([]byte(data))
req, err := http.NewRequestWithContext(ctx, "GET", "https://www.bugherd.com/api_v2/projects.json", nil)
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
}
}
isVerified, verificationErr := verifyBugherd(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -76,3 +65,33 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
"io"
"net/http"
"strings"
@@ -46,19 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bugsnag.com/user/organizations?admin", nil)
if err != nil {
continue
}
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
}
}
isVerified, verificationErr := verifyBugsnag(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -74,3 +65,33 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
regexp "github.com/wasilibs/go-re2"
"io"
"net/http"
"strings"
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"
@@ -46,30 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://prod-api.bulbul.io/view_all_users?api_key=%s", resMatch), nil)
if err != nil {
continue
}
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
}
}
}
isVerified, verificationErr := verifyBulbul(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -85,3 +65,41 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
"fmt"
"io"
"net/http"
@@ -56,27 +57,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.bulksms.com/v1/messages", nil)
if err != nil {
continue
}
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)
}
isVerified, verificationErr := verifyBulksms(ctx, client, id, key)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -93,3 +76,32 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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 (
"context"
regexp "github.com/wasilibs/go-re2"
"fmt"
"io"
"net/http"
"strings"
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"
@@ -44,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.buttercms.com/v2/posts/?auth_token="+resMatch, nil)
if err != nil {
continue
}
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
isVerified, verificationErr := verifyButterCMS(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}
results = append(results, s1)
@@ -71,3 +65,30 @@ func (s Scanner) Type() detectorspb.DetectorType {
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."
}
// 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)
}
}