Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix] Update Azure Client, Tenant Ids and Client Secret Detection Regexs. #3362

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 52 additions & 29 deletions pkg/detectors/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,63 +19,86 @@ type Scanner struct {
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)

func mustFmtPat(id, pat string) *regexp.Regexp {
combinedID := strings.ReplaceAll(id, "_", "") + "|" + id
return regexp.MustCompile(fmt.Sprintf(pat, combinedID))
}

var (
// TODO: Azure storage access keys and investigate other types of creds.

// Azure App Oauth
idPatFmt = `(?i)(%s).{0,20}([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})`
clientIDPat = mustFmtPat("client_id", idPatFmt)
tenantIDPat = mustFmtPat("tenant_id", idPatFmt)

// TODO: support old patterns
secretPatFmt = `(?i)(%s).{0,20}([a-z0-9_\.\-~]{34})`
clientSecretPat = mustFmtPat("client_secret", secretPatFmt)
udidPatFmt = `([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})`
clientIDPat = regexp.MustCompile(detectors.PrefixRegex([]string{"client_id", "clientid"}) + udidPatFmt)
tenantIDPat = regexp.MustCompile(detectors.PrefixRegex([]string{"microsoftonline", "tenant_id", "tenantid"}) + udidPatFmt)

// According to the Microsoft documentation, the client secret can be 24, 32, 40, 44, 56, or 88 characters long.
// https://learn.microsoft.com/en-us/purview/sit-defn-client-secret-api-key
clientSecretSubPatFmt = `(\b[a-zA-Z0-9_\.\-~]{%d}\b)`

clientSecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{"client_secret", "clientsecret"}) + "(" +
fmt.Sprintf(clientSecretSubPatFmt, 24) + "|" +
fmt.Sprintf(clientSecretSubPatFmt, 32) + "|" +
fmt.Sprintf(clientSecretSubPatFmt, 40) + "|" +
fmt.Sprintf(clientSecretSubPatFmt, 44) + "|" +
fmt.Sprintf(clientSecretSubPatFmt, 56) + "|" +
fmt.Sprintf(clientSecretSubPatFmt, 88) + ")")
)

// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string {
return []string{"azure"}
return []string{"azure", "microsoftonline"}
}

func trimUniqueMatches(matches [][]string) (result map[string]struct{}) {
result = make(map[string]struct{})
for _, match := range matches {
if len(match) > 1 {
trimmedString := strings.TrimSpace(match[1])
result[trimmedString] = struct{}{}
}
}
return result
}

// FromData will find and optionally verify Azure secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)

clientSecretMatches := clientSecretPat.FindAllStringSubmatch(dataStr, -1)
for _, clientSecret := range clientSecretMatches {
tenantIDMatches := tenantIDPat.FindAllStringSubmatch(dataStr, -1)
for _, tenantID := range tenantIDMatches {
clientIDMatches := clientIDPat.FindAllStringSubmatch(dataStr, -1)
for _, clientID := range clientIDMatches {
res := detectors.Result{
uniqueClientSecretMatches := trimUniqueMatches(clientSecretPat.FindAllStringSubmatch(dataStr, -1))
uniqueClientIDMatches := trimUniqueMatches(clientIDPat.FindAllStringSubmatch(dataStr, -1))
uniqueTenantIDMatches := trimUniqueMatches(tenantIDPat.FindAllStringSubmatch(dataStr, -1))

for clientSecret := range uniqueClientSecretMatches {
for clientID := range uniqueClientIDMatches {
for tenantID := range uniqueTenantIDMatches {
// to reduce false positives, we can check if they are the same
if (tenantID == clientID) ||
(tenantID == clientSecret) ||
(clientID == clientSecret) {
continue
}

s := detectors.Result{
DetectorType: detectorspb.DetectorType_Azure,
Raw: []byte(clientSecret[2]),
RawV2: []byte(clientID[2] + clientSecret[2] + tenantID[2]),
Redacted: clientID[2],
ExtraData: map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/azure/",
},
Raw: []byte(clientSecret),
RawV2: []byte(clientID + clientSecret + tenantID),
Redacted: clientID,
}
// Set the RotationGuideURL in the ExtraData
s.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/azure/",
}

if verify {
cred := auth.NewClientCredentialsConfig(clientID[2], clientSecret[2], tenantID[2])
cred := auth.NewClientCredentialsConfig(clientID, clientSecret, tenantID)
token, err := cred.ServicePrincipalToken()
if err != nil {
continue
}
err = token.RefreshWithContext(ctx)
if err == nil {
res.Verified = true
s.Verified = true
}
}

results = append(results, res)
results = append(results, s)
}
}
}
Expand Down
Loading