Merge pull request #83 from GoASTScanner/experimental

Adjust rule interface to allow interest in multiple ast node types
This commit is contained in:
Grant Murphy 2016-11-13 13:08:58 -08:00 committed by GitHub
commit 93016846d2
18 changed files with 157 additions and 122 deletions

View file

@ -142,13 +142,15 @@ func (gas *Analyzer) process(filename string, source interface{}) error {
// AddRule adds a rule into a rule set list mapped to the given AST node's type.
// The node is only needed for its type and is not otherwise used.
func (gas *Analyzer) AddRule(r Rule, n ast.Node) {
func (gas *Analyzer) AddRule(r Rule, nodes []ast.Node) {
for _, n := range nodes {
t := reflect.TypeOf(n)
if val, ok := gas.ruleset[t]; ok {
gas.ruleset[t] = append(val, r)
} else {
gas.ruleset[t] = []Rule{r}
}
}
}
// Process reads in a source file, convert it to an AST and traverse it.

View file

@ -23,7 +23,7 @@ import (
type RuleInfo struct {
description string
build func(map[string]interface{}) (gas.Rule, ast.Node)
build func(map[string]interface{}) (gas.Rule, []ast.Node)
}
// GetFullRuleList get the full list of all rules available to GAS

View file

@ -39,8 +39,8 @@ func (r *BindsToAllNetworkInterfaces) Match(n ast.Node, c *gas.Context) (gi *gas
return
}
func NewBindsToAllNetworkInterfaces(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
r = &BindsToAllNetworkInterfaces{
func NewBindsToAllNetworkInterfaces(conf map[string]interface{}) (gas.Rule, []ast.Node) {
return &BindsToAllNetworkInterfaces{
call: regexp.MustCompile(`^net\.Listen$`),
pattern: regexp.MustCompile(`^(0.0.0.0|:).*$`),
MetaData: gas.MetaData{
@ -48,7 +48,5 @@ func NewBindsToAllNetworkInterfaces(conf map[string]interface{}) (r gas.Rule, n
Confidence: gas.High,
What: "Binds to all network interfaces",
},
}
n = (*ast.CallExpr)(nil)
return
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View file

@ -34,54 +34,46 @@ func (r *BlacklistImport) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err
return nil, nil
}
func NewBlacklist_crypto_md5(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
r = &BlacklistImport{
func NewBlacklist_crypto_md5(conf map[string]interface{}) (gas.Rule, []ast.Node) {
return &BlacklistImport{
MetaData: gas.MetaData{
Severity: gas.High,
Confidence: gas.High,
What: "Use of weak cryptographic primitive",
},
Path: `"crypto/md5"`,
}
n = (*ast.ImportSpec)(nil)
return
}, []ast.Node{(*ast.ImportSpec)(nil)}
}
func NewBlacklist_crypto_des(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
r = &BlacklistImport{
func NewBlacklist_crypto_des(conf map[string]interface{}) (gas.Rule, []ast.Node) {
return &BlacklistImport{
MetaData: gas.MetaData{
Severity: gas.High,
Confidence: gas.High,
What: "Use of weak cryptographic primitive",
},
Path: `"crypto/des"`,
}
n = (*ast.ImportSpec)(nil)
return
}, []ast.Node{(*ast.ImportSpec)(nil)}
}
func NewBlacklist_crypto_rc4(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
r = &BlacklistImport{
func NewBlacklist_crypto_rc4(conf map[string]interface{}) (gas.Rule, []ast.Node) {
return &BlacklistImport{
MetaData: gas.MetaData{
Severity: gas.High,
Confidence: gas.High,
What: "Use of weak cryptographic primitive",
},
Path: `"crypto/rc4"`,
}
n = (*ast.ImportSpec)(nil)
return
}, []ast.Node{(*ast.ImportSpec)(nil)}
}
func NewBlacklist_net_http_cgi(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
r = &BlacklistImport{
func NewBlacklist_net_http_cgi(conf map[string]interface{}) (gas.Rule, []ast.Node) {
return &BlacklistImport{
MetaData: gas.MetaData{
Severity: gas.High,
Confidence: gas.High,
What: "Go code running under CGI is vulnerable to Httpoxy attack. (CVE-2016-5386)",
What: "Go versions < 1.6.3 are vulnerable to Httpoxy attack: (CVE-2016-5386)",
},
Path: `"net/http/cgi"`,
}
n = (*ast.ImportSpec)(nil)
return
}, []ast.Node{(*ast.ImportSpec)(nil)}
}

View file

@ -50,14 +50,12 @@ func (r *NoErrorCheck) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err err
return nil, nil
}
func NewNoErrorCheck(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
r = &NoErrorCheck{
func NewNoErrorCheck(conf map[string]interface{}) (gas.Rule, []ast.Node) {
return &NoErrorCheck{
MetaData: gas.MetaData{
Severity: gas.Low,
Confidence: gas.High,
What: "Errors unhandled.",
},
}
n = (*ast.AssignStmt)(nil)
return
}, []ast.Node{(*ast.AssignStmt)(nil)}
}

View file

@ -52,7 +52,7 @@ func (r *FilePermissions) Match(n ast.Node, c *gas.Context) (*gas.Issue, error)
return nil, nil
}
func NewFilePerms(conf map[string]interface{}) (gas.Rule, ast.Node) {
func NewFilePerms(conf map[string]interface{}) (gas.Rule, []ast.Node) {
mode := getConfiguredMode(conf, "G302", 0600)
return &FilePermissions{
mode: mode,
@ -63,10 +63,10 @@ func NewFilePerms(conf map[string]interface{}) (gas.Rule, ast.Node) {
Confidence: gas.High,
What: fmt.Sprintf("Expect file permissions to be %#o or less", mode),
},
}, (*ast.CallExpr)(nil)
}, []ast.Node{(*ast.CallExpr)(nil)}
}
func NewMkdirPerms(conf map[string]interface{}) (gas.Rule, ast.Node) {
func NewMkdirPerms(conf map[string]interface{}) (gas.Rule, []ast.Node) {
mode := getConfiguredMode(conf, "G301", 0700)
return &FilePermissions{
mode: mode,
@ -77,5 +77,5 @@ func NewMkdirPerms(conf map[string]interface{}) (gas.Rule, ast.Node) {
Confidence: gas.High,
What: fmt.Sprintf("Expect directory permissions to be %#o or less", mode),
},
}, (*ast.CallExpr)(nil)
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View file

@ -15,43 +15,71 @@
package rules
import (
"go/ast"
"regexp"
gas "github.com/GoASTScanner/gas/core"
"go/ast"
"go/token"
"regexp"
)
type CredsAssign struct {
type Credentials struct {
gas.MetaData
pattern *regexp.Regexp
}
func (r *CredsAssign) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
if node, ok := n.(*ast.AssignStmt); ok {
for _, i := range node.Lhs {
if ident, ok := i.(*ast.Ident); ok {
if r.pattern.MatchString(ident.Name) {
for _, e := range node.Rhs {
if _, ok := e.(*ast.BasicLit); ok {
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
func (r *Credentials) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) {
switch node := n.(type) {
case *ast.AssignStmt:
return r.matchAssign(node, ctx)
case *ast.GenDecl:
return r.matchGenDecl(node, ctx)
}
}
}
}
}
}
return
return nil, nil
}
func NewHardcodedCredentials(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
r = &CredsAssign{
pattern: regexp.MustCompile(`(?i)passwd|pass|password|pwd|secret|token`),
func (r *Credentials) matchAssign(assign *ast.AssignStmt, ctx *gas.Context) (*gas.Issue, error) {
for _, i := range assign.Lhs {
if ident, ok := i.(*ast.Ident); ok {
if r.pattern.MatchString(ident.Name) {
for _, e := range assign.Rhs {
if _, ok := e.(*ast.BasicLit); ok {
return gas.NewIssue(ctx, assign, r.What, r.Severity, r.Confidence), nil
}
}
}
}
}
return nil, nil
}
func (r *Credentials) matchGenDecl(decl *ast.GenDecl, ctx *gas.Context) (*gas.Issue, error) {
if decl.Tok != token.CONST && decl.Tok != token.VAR {
return nil, nil
}
for _, spec := range decl.Specs {
if valueSpec, ok := spec.(*ast.ValueSpec); ok {
for index, ident := range valueSpec.Names {
if r.pattern.MatchString(ident.Name) {
if _, ok := valueSpec.Values[index].(*ast.BasicLit); ok {
return gas.NewIssue(ctx, decl, r.What, r.Severity, r.Confidence), nil
}
}
}
}
}
return nil, nil
}
func NewHardcodedCredentials(conf map[string]interface{}) (gas.Rule, []ast.Node) {
pattern := `(?i)passwd|pass|password|pwd|secret|token`
if val, ok := conf["G101"]; ok {
pattern = val.(string)
}
return &Credentials{
pattern: regexp.MustCompile(pattern),
MetaData: gas.MetaData{
What: "Potential hardcoded credentials",
Confidence: gas.Low,
Severity: gas.High,
},
}
n = (*ast.AssignStmt)(nil)
return
}, []ast.Node{(*ast.AssignStmt)(nil), (*ast.GenDecl)(nil)}
}

View file

@ -39,3 +39,43 @@ func TestHardcoded(t *testing.T) {
checkTestResults(t, issues, 1, "Potential hardcoded credentials")
}
func TestHardcodedGlobalVar(t *testing.T) {
config := map[string]interface{}{"ignoreNosec": false}
analyzer := gas.NewAnalyzer(config, nil)
analyzer.AddRule(NewHardcodedCredentials(config))
issues := gasTestRunner(`
package samples
import "fmt"
var password = "admin"
func main() {
username := "admin"
fmt.Println("Doing something with: ", username, password)
}`, analyzer)
checkTestResults(t, issues, 1, "Potential hardcoded credentials")
}
func TestHardcodedConstant(t *testing.T) {
config := map[string]interface{}{"ignoreNosec": false}
analyzer := gas.NewAnalyzer(config, nil)
analyzer.AddRule(NewHardcodedCredentials(config))
issues := gasTestRunner(`
package samples
import "fmt"
const password = "secret"
func main() {
username := "admin"
fmt.Println("Doing something with: ", username, password)
}`, analyzer)
checkTestResults(t, issues, 1, "Potential hardcoded credentials")
}

View file

@ -32,5 +32,5 @@ func TestHttpoxy(t *testing.T) {
)
func main() {}`, analyzer)
checkTestResults(t, issues, 1, "Go code running under CGI is vulnerable to Httpoxy attack.")
checkTestResults(t, issues, 1, "Go versions < 1.6.3 are vulnerable to Httpoxy")
}

View file

@ -34,8 +34,8 @@ func (w *WeakRand) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
return nil, nil
}
func NewWeakRandCheck(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
r = &WeakRand{
func NewWeakRandCheck(conf map[string]interface{}) (gas.Rule, []ast.Node) {
return &WeakRand{
funcName: "Read",
packagePath: "math/rand",
MetaData: gas.MetaData{
@ -43,7 +43,5 @@ func NewWeakRandCheck(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
Confidence: gas.Medium,
What: "Use of weak random number generator (math/rand instead of crypto/rand)",
},
}
n = (*ast.CallExpr)(nil)
return
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View file

@ -37,9 +37,9 @@ func (w *WeakKeyStrength) Match(n ast.Node, c *gas.Context) (*gas.Issue, error)
return nil, nil
}
func NewWeakKeyStrength(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
func NewWeakKeyStrength(conf map[string]interface{}) (gas.Rule, []ast.Node) {
bits := 2048
r = &WeakKeyStrength{
return &WeakKeyStrength{
pattern: regexp.MustCompile(`^rsa\.GenerateKey$`),
bits: bits,
MetaData: gas.MetaData{
@ -47,7 +47,5 @@ func NewWeakKeyStrength(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
Confidence: gas.High,
What: fmt.Sprintf("RSA keys should be at least %d bits", bits),
},
}
n = (*ast.CallExpr)(nil)
return
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View file

@ -56,8 +56,8 @@ func (s *SqlStrConcat) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
return nil, nil
}
func NewSqlStrConcat(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
r = &SqlStrConcat{
func NewSqlStrConcat(conf map[string]interface{}) (gas.Rule, []ast.Node) {
return &SqlStrConcat{
SqlStatement: SqlStatement{
pattern: regexp.MustCompile(`(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) `),
MetaData: gas.MetaData{
@ -66,9 +66,7 @@ func NewSqlStrConcat(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
What: "SQL string concatenation",
},
},
}
n = (*ast.BinaryExpr)(nil)
return
}, []ast.Node{(*ast.BinaryExpr)(nil)}
}
type SqlStrFormat struct {
@ -86,8 +84,8 @@ func (s *SqlStrFormat) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err err
return nil, nil
}
func NewSqlStrFormat(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
r = &SqlStrFormat{
func NewSqlStrFormat(conf map[string]interface{}) (gas.Rule, []ast.Node) {
return &SqlStrFormat{
call: regexp.MustCompile(`^fmt\.Sprintf$`),
SqlStatement: SqlStatement{
pattern: regexp.MustCompile("(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) "),
@ -97,7 +95,5 @@ func NewSqlStrFormat(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
What: "SQL string formatting",
},
},
}
n = (*ast.CallExpr)(nil)
return
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View file

@ -49,10 +49,8 @@ func (r *Subprocess) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
return nil, nil
}
func NewSubproc(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
r = &Subprocess{
func NewSubproc(conf map[string]interface{}) (gas.Rule, []ast.Node) {
return &Subprocess{
pattern: regexp.MustCompile(`^exec\.Command|syscall\.Exec$`),
}
n = (*ast.CallExpr)(nil)
return
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View file

@ -36,8 +36,8 @@ func (t *BadTempFile) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err erro
return nil, nil
}
func NewBadTempFile(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
r = &BadTempFile{
func NewBadTempFile(conf map[string]interface{}) (gas.Rule, []ast.Node) {
return &BadTempFile{
call: regexp.MustCompile(`ioutil\.WriteFile|os\.Create`),
args: regexp.MustCompile(`^/tmp/.*$|^/var/tmp/.*$`),
MetaData: gas.MetaData{
@ -45,7 +45,5 @@ func NewBadTempFile(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
Confidence: gas.High,
What: "File creation in shared tmp directory without using ioutil.Tempfile",
},
}
n = (*ast.CallExpr)(nil)
return
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View file

@ -37,15 +37,13 @@ func (t *TemplateCheck) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err er
return nil, nil
}
func NewTemplateCheck(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
r = &TemplateCheck{
func NewTemplateCheck(conf map[string]interface{}) (gas.Rule, []ast.Node) {
return &TemplateCheck{
call: regexp.MustCompile(`^template\.(HTML|JS|URL)$`),
MetaData: gas.MetaData{
Severity: gas.Medium,
Confidence: gas.Low,
What: "this method will not auto-escape HTML. Verify data is well formed.",
},
}
n = (*ast.CallExpr)(nil)
return
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View file

@ -109,9 +109,9 @@ func (t *InsecureConfigTLS) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, er
return
}
func NewModernTlsCheck(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
func NewModernTlsCheck(conf map[string]interface{}) (gas.Rule, []ast.Node) {
// https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
r = &InsecureConfigTLS{
return &InsecureConfigTLS{
pattern: regexp.MustCompile(`^tls\.Config$`),
MinVersion: 0x0303, // TLS 1.2 only
MaxVersion: 0x0303,
@ -121,14 +121,12 @@ func NewModernTlsCheck(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
},
}
n = (*ast.CompositeLit)(nil)
return
}, []ast.Node{(*ast.CompositeLit)(nil)}
}
func NewIntermediateTlsCheck(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
func NewIntermediateTlsCheck(conf map[string]interface{}) (gas.Rule, []ast.Node) {
// https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29
r = &InsecureConfigTLS{
return &InsecureConfigTLS{
pattern: regexp.MustCompile(`^tls\.Config$`),
MinVersion: 0x0301, // TLS 1.2, 1.1, 1.0
MaxVersion: 0x0303,
@ -149,14 +147,12 @@ func NewIntermediateTlsCheck(conf map[string]interface{}) (r gas.Rule, n ast.Nod
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
},
}
n = (*ast.CompositeLit)(nil)
return
}, []ast.Node{(*ast.CompositeLit)(nil)}
}
func NewCompatTlsCheck(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
func NewCompatTlsCheck(conf map[string]interface{}) (gas.Rule, []ast.Node) {
// https://wiki.mozilla.org/Security/Server_Side_TLS#Old_compatibility_.28default.29
r = &InsecureConfigTLS{
return &InsecureConfigTLS{
pattern: regexp.MustCompile(`^tls\.Config$`),
MinVersion: 0x0301, // TLS 1.2, 1.1, 1.0
MaxVersion: 0x0303,
@ -179,7 +175,5 @@ func NewCompatTlsCheck(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
},
}
n = (*ast.CompositeLit)(nil)
return
}, []ast.Node{(*ast.CompositeLit)(nil)}
}

View file

@ -15,10 +15,9 @@
package rules
import (
gas "github.com/GoASTScanner/gas/core"
"go/ast"
"regexp"
gas "github.com/GoASTScanner/gas/core"
)
type UsingUnsafe struct {
@ -33,15 +32,13 @@ func (r *UsingUnsafe) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err erro
return nil, nil
}
func NewUsingUnsafe(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
r = &UsingUnsafe{
func NewUsingUnsafe(conf map[string]interface{}) (gas.Rule, []ast.Node) {
return &UsingUnsafe{
pattern: regexp.MustCompile(`unsafe\..*`),
MetaData: gas.MetaData{
What: "Use of unsafe calls should be audited",
Severity: gas.Low,
Confidence: gas.High,
},
}
n = (*ast.CallExpr)(nil)
return
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View file

@ -36,7 +36,7 @@ func (r *UsesWeakCryptography) Match(n ast.Node, c *gas.Context) (*gas.Issue, er
}
// Uses des.* md5.* or rc4.*
func NewUsesWeakCryptography(conf map[string]interface{}) (gas.Rule, ast.Node) {
func NewUsesWeakCryptography(conf map[string]interface{}) (gas.Rule, []ast.Node) {
calls := make(map[string][]string)
calls["crypto/des"] = []string{"NewCipher", "NewTripleDESCipher"}
calls["crypto/md5"] = []string{"New", "Sum"}
@ -49,5 +49,5 @@ func NewUsesWeakCryptography(conf map[string]interface{}) (gas.Rule, ast.Node) {
What: "Use of weak cryptographic primitive",
},
}
return rule, (*ast.CallExpr)(nil)
return rule, []ast.Node{(*ast.CallExpr)(nil)}
}