Skip to content

Commit

Permalink
Merge pull request securego#114 from GoASTScanner/feature
Browse files Browse the repository at this point in the history
Consider entropy when warning on hardcoded credentials
  • Loading branch information
gcmurphy committed Jan 14, 2017
2 parents cc52ef5 + 4099783 commit f6aeaa8
Show file tree
Hide file tree
Showing 32 changed files with 8,604 additions and 15 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: go
before_script:
- go vet ./...
- go vet $(go list ./... | grep -v /vendor/)
go:
- 1.5
- tip
73 changes: 66 additions & 7 deletions rules/hardcoded_credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,34 @@ import (
"go/ast"
"go/token"
"regexp"

"github.com/nbutton23/zxcvbn-go"
"strconv"
)

type Credentials struct {
gas.MetaData
pattern *regexp.Regexp
pattern *regexp.Regexp
entropyThreshold float64
perCharThreshold float64
truncate int
ignoreEntropy bool
}

func truncate(s string, n int) string {
if n > len(s) {
return s
}
return s[:n]
}

func (r *Credentials) isHighEntropyString(str string) bool {
s := truncate(str, r.truncate)
info := zxcvbn.PasswordStrength(s, []string{})
entropyPerChar := info.Entropy / float64(len(s))
return (info.Entropy >= r.entropyThreshold ||
(info.Entropy >= (r.entropyThreshold/2) &&
entropyPerChar >= r.perCharThreshold))
}

func (r *Credentials) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) {
Expand All @@ -41,8 +64,10 @@ func (r *Credentials) matchAssign(assign *ast.AssignStmt, ctx *gas.Context) (*ga
if ident, ok := i.(*ast.Ident); ok {
if r.pattern.MatchString(ident.Name) {
for _, e := range assign.Rhs {
if rhs, ok := e.(*ast.BasicLit); ok && rhs.Kind == token.STRING {
return gas.NewIssue(ctx, assign, r.What, r.Severity, r.Confidence), nil
if val, err := gas.GetString(e); err == nil {
if r.ignoreEntropy || (!r.ignoreEntropy && r.isHighEntropyString(val)) {
return gas.NewIssue(ctx, assign, r.What, r.Severity, r.Confidence), nil
}
}
}
}
Expand All @@ -63,8 +88,10 @@ func (r *Credentials) matchGenDecl(decl *ast.GenDecl, ctx *gas.Context) (*gas.Is
if len(valueSpec.Values) <= index {
index = len(valueSpec.Values) - 1
}
if rhs, ok := valueSpec.Values[index].(*ast.BasicLit); ok && rhs.Kind == token.STRING {
return gas.NewIssue(ctx, valueSpec, r.What, r.Severity, r.Confidence), nil
if val, err := gas.GetString(valueSpec.Values[index]); err == nil {
if r.ignoreEntropy || (!r.ignoreEntropy && r.isHighEntropyString(val)) {
return gas.NewIssue(ctx, valueSpec, r.What, r.Severity, r.Confidence), nil
}
}
}
}
Expand All @@ -75,11 +102,43 @@ func (r *Credentials) matchGenDecl(decl *ast.GenDecl, ctx *gas.Context) (*gas.Is

func NewHardcodedCredentials(conf map[string]interface{}) (gas.Rule, []ast.Node) {
pattern := `(?i)passwd|pass|password|pwd|secret|token`
entropyThreshold := 80.0
perCharThreshold := 3.0
ignoreEntropy := false
var truncateString int = 16
if val, ok := conf["G101"]; ok {
pattern = val.(string)
conf := val.(map[string]string)
if configPattern, ok := conf["pattern"]; ok {
pattern = configPattern
}
if configIgnoreEntropy, ok := conf["ignore_entropy"]; ok {
if parsedBool, err := strconv.ParseBool(configIgnoreEntropy); err == nil {
ignoreEntropy = parsedBool
}
}
if configEntropyThreshold, ok := conf["entropy_threshold"]; ok {
if parsedNum, err := strconv.ParseFloat(configEntropyThreshold, 64); err == nil {
entropyThreshold = parsedNum
}
}
if configCharThreshold, ok := conf["per_char_threshold"]; ok {
if parsedNum, err := strconv.ParseFloat(configCharThreshold, 64); err == nil {
perCharThreshold = parsedNum
}
}
if configTruncate, ok := conf["truncate"]; ok {
if parsedInt, err := strconv.Atoi(configTruncate); err == nil {
truncateString = parsedInt
}
}
}

return &Credentials{
pattern: regexp.MustCompile(pattern),
pattern: regexp.MustCompile(pattern),
entropyThreshold: entropyThreshold,
perCharThreshold: perCharThreshold,
ignoreEntropy: ignoreEntropy,
truncate: truncateString,
MetaData: gas.MetaData{
What: "Potential hardcoded credentials",
Confidence: gas.Low,
Expand Down
55 changes: 50 additions & 5 deletions rules/hardcoded_credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,51 @@ func TestHardcoded(t *testing.T) {
analyzer := gas.NewAnalyzer(config, nil)
analyzer.AddRule(NewHardcodedCredentials(config))

issues := gasTestRunner(
`
package samples
import "fmt"
func main() {
username := "admin"
password := "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
fmt.Println("Doing something with: ", username, password)
}`, analyzer)

checkTestResults(t, issues, 1, "Potential hardcoded credentials")
}

func TestHardcodedWithEntropy(t *testing.T) {
config := map[string]interface{}{"ignoreNosec": false}
analyzer := gas.NewAnalyzer(config, nil)
analyzer.AddRule(NewHardcodedCredentials(config))

issues := gasTestRunner(
`
package samples
import "fmt"
func main() {
username := "admin"
password := "secret"
fmt.Println("Doing something with: ", username, password)
}`, analyzer)

checkTestResults(t, issues, 0, "Potential hardcoded credentials")
}

func TestHardcodedIgnoreEntropy(t *testing.T) {
config := map[string]interface{}{
"ignoreNosec": false,
"G101": map[string]string{
"ignore_entropy": "true",
},
}
analyzer := gas.NewAnalyzer(config, nil)
analyzer.AddRule(NewHardcodedCredentials(config))

issues := gasTestRunner(
`
package samples
Expand All @@ -50,7 +95,7 @@ func TestHardcodedGlobalVar(t *testing.T) {
import "fmt"
var password = "admin"
var password = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
func main() {
username := "admin"
Expand All @@ -70,7 +115,7 @@ func TestHardcodedConstant(t *testing.T) {
import "fmt"
const password = "secret"
const password = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
func main() {
username := "admin"
Expand All @@ -92,7 +137,7 @@ func TestHardcodedConstantMulti(t *testing.T) {
const (
username = "user"
password = "secret"
password = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
)
func main() {
Expand All @@ -110,7 +155,7 @@ func TestHardecodedVarsNotAssigned(t *testing.T) {
package main
var password string
func init() {
password = "this is a secret string"
password = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
}`, analyzer)
checkTestResults(t, issues, 1, "Potential hardcoded credentials")
}
Expand Down Expand Up @@ -140,7 +185,7 @@ func TestHardcodedConstString(t *testing.T) {
package main
const (
ATNStateTokenStart = "foo bar"
ATNStateTokenStart = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
)
func main() {
println(ATNStateTokenStart)
Expand Down
9 changes: 7 additions & 2 deletions vendor.conf
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
# Import path | revision | Repository(optional)
github.com/ryanuber/go-glob 572520ed46dbddaed19ea3d9541bdd0494163693
# package
github.com/GoAstScanner/gas

# import
github.com/GoASTScanner/gas cc52ef5
github.com/nbutton23/zxcvbn-go a22cb81
github.com/ryanuber/go-glob v0.1
2 changes: 2 additions & 0 deletions vendor/github.com/nbutton23/zxcvbn-go/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions vendor/github.com/nbutton23/zxcvbn-go/LICENSE.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 78 additions & 0 deletions vendor/github.com/nbutton23/zxcvbn-go/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f6aeaa8

Please sign in to comment.