Fix hardcoded_credentials rule to only match on more specific patterns (#1009)

* Fix hardcoded_credentials rule to only match on more specific patterns

Signed-off-by: Cosmin Cojocar <gcojocar@adobe.com>

* Fix lint warnings

Signed-off-by: Cosmin Cojocar <gcojocar@adobe.com>

* Fix double escape in regexps

Signed-off-by: Cosmin Cojocar <gcojocar@adobe.com>

---------

Signed-off-by: Cosmin Cojocar <gcojocar@adobe.com>
This commit is contained in:
Cosmin Cojocar 2023-09-05 18:00:02 +02:00 committed by GitHub
parent 325eb19a54
commit 6c93653a29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 198 additions and 93 deletions

View file

@ -15,6 +15,7 @@
package rules
import (
"fmt"
"go/ast"
"go/token"
"regexp"
@ -26,10 +27,169 @@ import (
"github.com/securego/gosec/v2/issue"
)
type secretPattern struct {
name string
regexp *regexp.Regexp
}
var secretsPatterns = [...]secretPattern{
{
name: "RSA private key",
regexp: regexp.MustCompile(`-----BEGIN RSA PRIVATE KEY-----`),
},
{
name: "SSH (DSA) private key",
regexp: regexp.MustCompile(`-----BEGIN DSA PRIVATE KEY-----`),
},
{
name: "SSH (EC) private key",
regexp: regexp.MustCompile(`-----BEGIN EC PRIVATE KEY-----`),
},
{
name: "PGP private key block",
regexp: regexp.MustCompile(`-----BEGIN PGP PRIVATE KEY BLOCK-----`),
},
{
name: "Slack Token",
regexp: regexp.MustCompile(`xox[pborsa]-[0-9]{12}-[0-9]{12}-[0-9]{12}-[a-z0-9]{32}`),
},
{
name: "AWS API Key",
regexp: regexp.MustCompile(`AKIA[0-9A-Z]{16}`),
},
{
name: "Amazon MWS Auth Token",
regexp: regexp.MustCompile(`amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`),
},
{
name: "AWS AppSync GraphQL Key",
regexp: regexp.MustCompile(`da2-[a-z0-9]{26}`),
},
{
name: "GitHub personal access token",
regexp: regexp.MustCompile(`ghp_[a-zA-Z0-9]{36}`),
},
{
name: "GitHub fine-grained access token",
regexp: regexp.MustCompile(`github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}`),
},
{
name: "GitHub action temporary token",
regexp: regexp.MustCompile(`ghs_[a-zA-Z0-9]{36}`),
},
{
name: "Google API Key",
regexp: regexp.MustCompile(`AIza[0-9A-Za-z\-_]{35}`),
},
{
name: "Google Cloud Platform API Key",
regexp: regexp.MustCompile(`AIza[0-9A-Za-z\-_]{35}`),
},
{
name: "Google Cloud Platform OAuth",
regexp: regexp.MustCompile(`[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com`),
},
{
name: "Google Drive API Key",
regexp: regexp.MustCompile(`AIza[0-9A-Za-z\-_]{35}`),
},
{
name: "Google Drive OAuth",
regexp: regexp.MustCompile(`[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com`),
},
{
name: "Google (GCP) Service-account",
regexp: regexp.MustCompile(`"type": "service_account"`),
},
{
name: "Google Gmail API Key",
regexp: regexp.MustCompile(`AIza[0-9A-Za-z\-_]{35}`),
},
{
name: "Google Gmail OAuth",
regexp: regexp.MustCompile(`[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com`),
},
{
name: "Google OAuth Access Token",
regexp: regexp.MustCompile(`ya29\.[0-9A-Za-z\-_]+`),
},
{
name: "Google YouTube API Key",
regexp: regexp.MustCompile(`AIza[0-9A-Za-z\-_]{35}`),
},
{
name: "Google YouTube OAuth",
regexp: regexp.MustCompile(`[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com`),
},
{
name: "Generic API Key",
regexp: regexp.MustCompile(`[aA][pP][iI]_?[kK][eE][yY].*[''|"][0-9a-zA-Z]{32,45}[''|"]`),
},
{
name: "Generic Secret",
regexp: regexp.MustCompile(`[sS][eE][cC][rR][eE][tT].*[''|"][0-9a-zA-Z]{32,45}[''|"]`),
},
{
name: "Heroku API Key",
regexp: regexp.MustCompile(`[hH][eE][rR][oO][kK][uU].*[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}`),
},
{
name: "MailChimp API Key",
regexp: regexp.MustCompile(`[0-9a-f]{32}-us[0-9]{1,2}`),
},
{
name: "Mailgun API Key",
regexp: regexp.MustCompile(`key-[0-9a-zA-Z]{32}`),
},
{
name: "Password in URL",
regexp: regexp.MustCompile(`[a-zA-Z]{3,10}://[^/\\s:@]{3,20}:[^/\\s:@]{3,20}@.{1,100}["'\\s]`),
},
{
name: "Slack Webhook",
regexp: regexp.MustCompile(`https://hooks\.slack\.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}`),
},
{
name: "Stripe API Key",
regexp: regexp.MustCompile(`sk_live_[0-9a-zA-Z]{24}`),
},
{
name: "Stripe API Key",
regexp: regexp.MustCompile(`sk_live_[0-9a-zA-Z]{24}`),
},
{
name: "Stripe Restricted API Key",
regexp: regexp.MustCompile(`rk_live_[0-9a-zA-Z]{24}`),
},
{
name: "Square Access Token",
regexp: regexp.MustCompile(`sq0atp-[0-9A-Za-z\-_]{22}`),
},
{
name: "Square OAuth Secret",
regexp: regexp.MustCompile(`sq0csp-[0-9A-Za-z\-_]{43}`),
},
{
name: "Telegram Bot API Key",
regexp: regexp.MustCompile(`[0-9]+:AA[0-9A-Za-z\-_]{33}`),
},
{
name: "Twilio API Key",
regexp: regexp.MustCompile(`SK[0-9a-fA-F]{32}`),
},
{
name: "Twitter Access Token",
regexp: regexp.MustCompile(`[tT][wW][iI][tT][tT][eE][rR].*[1-9][0-9]+-[0-9a-zA-Z]{40}`),
},
{
name: "Twitter OAuth",
regexp: regexp.MustCompile(`[tT][wW][iI][tT][tT][eE][rR].*[''|"][0-9a-zA-Z]{35,44}[''|"]`),
},
}
type credentials struct {
issue.MetaData
pattern *regexp.Regexp
patternValue *regexp.Regexp // Pattern for matching string values (LHS on assign statements)
entropyThreshold float64
perCharThreshold float64
truncate int
@ -56,6 +216,15 @@ func (r *credentials) isHighEntropyString(str string) bool {
entropyPerChar >= r.perCharThreshold))
}
func (r *credentials) isSecretPattern(str string) (bool, string) {
for _, pattern := range secretsPatterns {
if pattern.regexp.MatchString(str) {
return true, pattern.name
}
}
return false, ""
}
func (r *credentials) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error) {
switch node := n.(type) {
case *ast.AssignStmt:
@ -89,9 +258,9 @@ func (r *credentials) matchAssign(assign *ast.AssignStmt, ctx *gosec.Context) (*
continue
}
if r.patternValue.MatchString(val) {
if r.ignoreEntropy || r.isHighEntropyString(val) {
return ctx.NewIssue(assign, r.ID(), r.What, r.Severity, r.Confidence), nil
if r.ignoreEntropy || r.isHighEntropyString(val) {
if ok, patternName := r.isSecretPattern(val); ok {
return ctx.NewIssue(assign, r.ID(), fmt.Sprintf("%s: %s", r.What, patternName), r.Severity, r.Confidence), nil
}
}
}
@ -120,9 +289,9 @@ func (r *credentials) matchValueSpec(valueSpec *ast.ValueSpec, ctx *gosec.Contex
// Now that no variable names have been matched, match the actual values to find any creds
for _, ident := range valueSpec.Values {
if val, err := gosec.GetString(ident); err == nil {
if r.patternValue.MatchString(val) {
if r.ignoreEntropy || r.isHighEntropyString(val) {
return ctx.NewIssue(valueSpec, r.ID(), r.What, r.Severity, r.Confidence), nil
if r.ignoreEntropy || r.isHighEntropyString(val) {
if ok, patternName := r.isSecretPattern(val); ok {
return ctx.NewIssue(valueSpec, r.ID(), fmt.Sprintf("%s: %s", r.What, patternName), r.Severity, r.Confidence), nil
}
}
}
@ -159,9 +328,9 @@ func (r *credentials) matchEqualityCheck(binaryExpr *ast.BinaryExpr, ctx *gosec.
if ok && identStrConst.Kind == token.STRING {
s, _ := gosec.GetString(identStrConst)
if r.patternValue.MatchString(s) {
if r.ignoreEntropy || r.isHighEntropyString(s) {
return ctx.NewIssue(binaryExpr, r.ID(), r.What, r.Severity, r.Confidence), nil
if r.ignoreEntropy || r.isHighEntropyString(s) {
if ok, patternName := r.isSecretPattern(s); ok {
return ctx.NewIssue(binaryExpr, r.ID(), fmt.Sprintf("%s: %s", r.What, patternName), r.Severity, r.Confidence), nil
}
}
}
@ -173,7 +342,6 @@ func (r *credentials) matchEqualityCheck(binaryExpr *ast.BinaryExpr, ctx *gosec.
// assigned to variables that appear to be related to credentials.
func NewHardcodedCredentials(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
pattern := `(?i)passwd|pass|password|pwd|secret|token|pw|apiKey|bearer|cred`
patternValue := "(?i)(^(.*[:;,](\\s)*)?[a-f0-9]{64}$)|(AIza[0-9A-Za-z-_]{35})|(^(.*[:;,](\\s)*)?github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}$)|(^(.*[:;,](\\s)*)?[0-9a-zA-Z-_]{24}$)"
entropyThreshold := 80.0
perCharThreshold := 3.0
ignoreEntropy := false
@ -186,12 +354,6 @@ func NewHardcodedCredentials(id string, conf gosec.Config) (gosec.Rule, []ast.No
}
}
if configPatternValue, ok := conf["patternValue"]; ok {
if cfgPatternValue, ok := configPatternValue.(string); ok {
patternValue = cfgPatternValue
}
}
if configIgnoreEntropy, ok := conf["ignore_entropy"]; ok {
if cfgIgnoreEntropy, ok := configIgnoreEntropy.(bool); ok {
ignoreEntropy = cfgIgnoreEntropy
@ -222,7 +384,6 @@ func NewHardcodedCredentials(id string, conf gosec.Config) (gosec.Rule, []ast.No
return &credentials{
pattern: regexp.MustCompile(pattern),
patternValue: regexp.MustCompile(patternValue),
entropyThreshold: entropyThreshold,
perCharThreshold: perCharThreshold,
ignoreEntropy: ignoreEntropy,

View file

@ -273,45 +273,8 @@ package main
import "fmt"
func main() {
username := "admin"
key := "472bb6c8c1871887cc117742ead362d688707d0442de930f7588db9d5ba091cc"
fmt.Println("Logging in with: ", username, key)
}`}, 1, gosec.NewConfig()},
{[]string{`
package main
import "fmt"
const (
b = "Bearer: c0df7a0f9b4a6a336029689b5df0712459a4f396c609ab05fd21a9097b4264f7"
)
func main() {
fmt.Println(b)
}`}, 1, gosec.NewConfig()},
{[]string{`
package main
import "fmt"
const (
tooLongConst = "key: c0df7a0f9b4a6a336029689b5df0712459a4f396c609ab05fd21a9097b4264f71294129"
)
func main() {
fmt.Println(tooLongConst)
}`}, 0, gosec.NewConfig()},
{[]string{`
package main
import "fmt"
const (
tooShortConst = "key: c0df7a0f9b4a6a336029689b5df0712459a4f396c609ab05fd21a9097b4264f71294"
)
func main() {
fmt.Println(tooShortConst)
customerNameEnvKey := "FOO_CUSTOMER_NAME"
fmt.Println(customerNameEnvKey)
}`}, 0, gosec.NewConfig()},
{[]string{`
package main
@ -319,10 +282,17 @@ package main
import "fmt"
func main() {
compareStr := "test"
if compareStr == "b7997caa846af0c50c095d63d212be2fbaffd35c22c735a905ddba87d85618fd" {
fmt.Println(compareStr)
}
txnID := "3637cfcc1eec55a50f78a7c435914583ccbc75a21dec9a0e94dfa077647146d7"
fmt.Println(txnID)
}`}, 0, gosec.NewConfig()},
{[]string{`
package main
import "fmt"
func main() {
urlSecret := "https://username:abcdef0123456789abcdef0123456789abcdef01@contoso.com/"
fmt.Println(urlSecret)
}`}, 1, gosec.NewConfig()},
{[]string{`
package main
@ -330,22 +300,18 @@ package main
import "fmt"
func main() {
compareTooShort := "test"
if compareTooShort == "b7997caa846af0c50c095d63d212be2fbaffd35c22c735a905ddba87d85618d" {
fmt.Println(compareTooShort)
}
}`}, 0, gosec.NewConfig()},
githubToken := "ghp_iR54dhCYg9Tfmoywi9xLmmKZrrnAw438BYh3"
fmt.Println(githubToken)
}`}, 1, gosec.NewConfig()},
{[]string{`
package main
import "fmt"
func main() {
compareTooLong := "test"
if compareTooLong == "b7997caa846af0c50c095d63d212be2fbaffd35c22c735a905ddba87d85618fd11" {
fmt.Println(compareTooLong)
}
}`}, 0, gosec.NewConfig()},
awsAccessKeyID := "AKIAI44QH8DHBEXAMPLE"
fmt.Println(awsAccessKeyID)
}`}, 1, gosec.NewConfig()},
{[]string{`
package main
@ -356,28 +322,6 @@ func main() {
if compareGoogleAPI == "AIzajtGS_aJGkoiAmSbXzu9I-1eytAi9Lrlh-vT" {
fmt.Println(compareGoogleAPI)
}
}`}, 1, gosec.NewConfig()},
{[]string{`
package main
import "fmt"
const (
githubPAT = "key: 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"
)
func main() {
fmt.Println(githubPAT)
}`}, 1, gosec.NewConfig()},
{[]string{`
package main
import "fmt"
func main() {
username := "admin"
googOAuthSec := "uibYYslvAUKn2ORRJ7EaZtMs"
fmt.Println("Logging in with: ", username, googOAuthSec)
}`}, 1, gosec.NewConfig()},
}