Allow rules to register against multiple ast nodes

Update the AddRule interface to allow rules to register interest in
multiple ast.Nodes. Adds more flexibility to how rules can work, and was
needed to fix the hard coded credentials test specifically.
This commit is contained in:
Grant Murphy 2016-11-13 12:55:31 -08:00
parent c6587df4a5
commit bf103da519
16 changed files with 69 additions and 102 deletions

View file

@ -142,7 +142,8 @@ 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)
@ -150,6 +151,7 @@ func (gas *Analyzer) AddRule(r Rule, n ast.Node) {
gas.ruleset[t] = []Rule{r}
}
}
}
// Process reads in a source file, convert it to an AST and traverse it.
// Rule methods added with AddRule will be invoked as necessary.

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

@ -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)}
}