Merge branch 'jonmcclintock-nosec-specify-rule'

This commit is contained in:
Grant Murphy 2018-03-09 11:31:05 +10:00
commit 15095a8bef
28 changed files with 307 additions and 128 deletions

View file

@ -25,6 +25,7 @@ import (
"os" "os"
"path" "path"
"reflect" "reflect"
"regexp"
"strings" "strings"
"path/filepath" "path/filepath"
@ -43,6 +44,7 @@ type Context struct {
Root *ast.File Root *ast.File
Config map[string]interface{} Config map[string]interface{}
Imports *ImportTracker Imports *ImportTracker
Ignores []map[string]bool
} }
// Metrics used when reporting information about a scanning run. // Metrics used when reporting information about a scanning run.
@ -87,9 +89,9 @@ func NewAnalyzer(conf Config, logger *log.Logger) *Analyzer {
// LoadRules instantiates all the rules to be used when analyzing source // LoadRules instantiates all the rules to be used when analyzing source
// packages // packages
func (gas *Analyzer) LoadRules(ruleDefinitions ...RuleBuilder) { func (gas *Analyzer) LoadRules(ruleDefinitions map[string]RuleBuilder) {
for _, builder := range ruleDefinitions { for id, def := range ruleDefinitions {
r, nodes := builder(gas.config) r, nodes := def(id, gas.config)
gas.ruleset.Register(r, nodes...) gas.ruleset.Register(r, nodes...)
} }
} }
@ -147,27 +149,72 @@ func (gas *Analyzer) Process(packagePaths ...string) error {
} }
// ignore a node (and sub-tree) if it is tagged with a "#nosec" comment // ignore a node (and sub-tree) if it is tagged with a "#nosec" comment
func (gas *Analyzer) ignore(n ast.Node) bool { func (gas *Analyzer) ignore(n ast.Node) ([]string, bool) {
if groups, ok := gas.context.Comments[n]; ok && !gas.ignoreNosec { if groups, ok := gas.context.Comments[n]; ok && !gas.ignoreNosec {
for _, group := range groups { for _, group := range groups {
if strings.Contains(group.Text(), "#nosec") { if strings.Contains(group.Text(), "#nosec") {
gas.stats.NumNosec++ gas.stats.NumNosec++
return true
// Pull out the specific rules that are listed to be ignored.
re := regexp.MustCompile("(G\\d{3})")
matches := re.FindAllStringSubmatch(group.Text(), -1)
// If no specific rules were given, ignore everything.
if matches == nil || len(matches) == 0 {
return nil, true
}
// Find the rule IDs to ignore.
var ignores []string
for _, v := range matches {
ignores = append(ignores, v[1])
}
return ignores, false
} }
} }
} }
return false return nil, false
} }
// Visit runs the GAS visitor logic over an AST created by parsing go code. // Visit runs the GAS visitor logic over an AST created by parsing go code.
// Rule methods added with AddRule will be invoked as necessary. // Rule methods added with AddRule will be invoked as necessary.
func (gas *Analyzer) Visit(n ast.Node) ast.Visitor { func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
if !gas.ignore(n) { // If we've reached the end of this branch, pop off the ignores stack.
if n == nil {
if len(gas.context.Ignores) > 0 {
gas.context.Ignores = gas.context.Ignores[1:]
}
return gas
}
// Get any new rule exclusions.
ignoredRules, ignoreAll := gas.ignore(n)
if ignoreAll {
return nil
}
// Now create the union of exclusions.
ignores := make(map[string]bool, 0)
if len(gas.context.Ignores) > 0 {
for k, v := range gas.context.Ignores[0] {
ignores[k] = v
}
}
for _, v := range ignoredRules {
ignores[v] = true
}
// Push the new set onto the stack.
gas.context.Ignores = append([]map[string]bool{ignores}, gas.context.Ignores...)
// Track aliased and initialization imports // Track aliased and initialization imports
gas.context.Imports.TrackImport(n) gas.context.Imports.TrackImport(n)
for _, rule := range gas.ruleset.RegisteredFor(n) { for _, rule := range gas.ruleset.RegisteredFor(n) {
if _, ok := ignores[rule.ID()]; ok {
continue
}
issue, err := rule.Match(n, gas.context) issue, err := rule.Match(n, gas.context)
if err != nil { if err != nil {
file, line := GetLocation(n, gas.context) file, line := GetLocation(n, gas.context)
@ -180,8 +227,6 @@ func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
} }
} }
return gas return gas
}
return nil
} }
// Report returns the current issues discovered and the metrics about the scan // Report returns the current issues discovered and the metrics about the scan

View file

@ -28,7 +28,7 @@ var _ = Describe("Analyzer", func() {
Context("when processing a package", func() { Context("when processing a package", func() {
It("should return an error if the package contains no Go files", func() { It("should return an error if the package contains no Go files", func() {
analyzer.LoadRules(rules.Generate().Builders()...) analyzer.LoadRules(rules.Generate().Builders())
dir, err := ioutil.TempDir("", "empty") dir, err := ioutil.TempDir("", "empty")
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
Expect(err).ShouldNot(HaveOccurred()) Expect(err).ShouldNot(HaveOccurred())
@ -38,7 +38,7 @@ var _ = Describe("Analyzer", func() {
}) })
It("should return an error if the package fails to build", func() { It("should return an error if the package fails to build", func() {
analyzer.LoadRules(rules.Generate().Builders()...) analyzer.LoadRules(rules.Generate().Builders())
pkg := testutils.NewTestPackage() pkg := testutils.NewTestPackage()
defer pkg.Close() defer pkg.Close()
pkg.AddFile("wonky.go", `func main(){ println("forgot the package")}`) pkg.AddFile("wonky.go", `func main(){ println("forgot the package")}`)
@ -51,7 +51,7 @@ var _ = Describe("Analyzer", func() {
}) })
It("should be able to analyze mulitple Go files", func() { It("should be able to analyze mulitple Go files", func() {
analyzer.LoadRules(rules.Generate().Builders()...) analyzer.LoadRules(rules.Generate().Builders())
pkg := testutils.NewTestPackage() pkg := testutils.NewTestPackage()
defer pkg.Close() defer pkg.Close()
pkg.AddFile("foo.go", ` pkg.AddFile("foo.go", `
@ -72,7 +72,7 @@ var _ = Describe("Analyzer", func() {
}) })
It("should be able to analyze mulitple Go packages", func() { It("should be able to analyze mulitple Go packages", func() {
analyzer.LoadRules(rules.Generate().Builders()...) analyzer.LoadRules(rules.Generate().Builders())
pkg1 := testutils.NewTestPackage() pkg1 := testutils.NewTestPackage()
pkg2 := testutils.NewTestPackage() pkg2 := testutils.NewTestPackage()
defer pkg1.Close() defer pkg1.Close()
@ -98,7 +98,7 @@ var _ = Describe("Analyzer", func() {
// Rule for MD5 weak crypto usage // Rule for MD5 weak crypto usage
sample := testutils.SampleCodeG401[0] sample := testutils.SampleCodeG401[0]
source := sample.Code source := sample.Code
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()...) analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders())
controlPackage := testutils.NewTestPackage() controlPackage := testutils.NewTestPackage()
defer controlPackage.Close() defer controlPackage.Close()
@ -114,7 +114,7 @@ var _ = Describe("Analyzer", func() {
// Rule for MD5 weak crypto usage // Rule for MD5 weak crypto usage
sample := testutils.SampleCodeG401[0] sample := testutils.SampleCodeG401[0]
source := sample.Code source := sample.Code
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()...) analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders())
nosecPackage := testutils.NewTestPackage() nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close() defer nosecPackage.Close()
@ -126,6 +126,57 @@ var _ = Describe("Analyzer", func() {
nosecIssues, _ := analyzer.Report() nosecIssues, _ := analyzer.Report()
Expect(nosecIssues).Should(BeEmpty()) Expect(nosecIssues).Should(BeEmpty())
}) })
It("should not report errors when an exclude comment is present for the correct rule", func() {
// Rule for MD5 weak crypto usage
sample := testutils.SampleCodeG401[0]
source := sample.Code
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders())
nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close()
nosecSource := strings.Replace(source, "h := md5.New()", "h := md5.New() // #nosec G401", 1)
nosecPackage.AddFile("md5.go", nosecSource)
nosecPackage.Build()
analyzer.Process(nosecPackage.Path)
nosecIssues, _ := analyzer.Report()
Expect(nosecIssues).Should(BeEmpty())
})
It("should report errors when an exclude comment is present for a different rule", func() {
// Rule for MD5 weak crypto usage
sample := testutils.SampleCodeG401[0]
source := sample.Code
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders())
nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close()
nosecSource := strings.Replace(source, "h := md5.New()", "h := md5.New() // #nosec G301", 1)
nosecPackage.AddFile("md5.go", nosecSource)
nosecPackage.Build()
analyzer.Process(nosecPackage.Path)
nosecIssues, _ := analyzer.Report()
Expect(nosecIssues).Should(HaveLen(sample.Errors))
})
It("should not report errors when an exclude comment is present for multiple rules, including the correct rule", func() {
// Rule for MD5 weak crypto usage
sample := testutils.SampleCodeG401[0]
source := sample.Code
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders())
nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close()
nosecSource := strings.Replace(source, "h := md5.New()", "h := md5.New() // #nosec G301 G401", 1)
nosecPackage.AddFile("md5.go", nosecSource)
nosecPackage.Build()
analyzer.Process(nosecPackage.Path)
nosecIssues, _ := analyzer.Report()
Expect(nosecIssues).Should(BeEmpty())
})
}) })
It("should be possible to overwrite nosec comments, and report issues", func() { It("should be possible to overwrite nosec comments, and report issues", func() {
@ -138,7 +189,7 @@ var _ = Describe("Analyzer", func() {
nosecIgnoreConfig := gas.NewConfig() nosecIgnoreConfig := gas.NewConfig()
nosecIgnoreConfig.SetGlobal("nosec", "true") nosecIgnoreConfig.SetGlobal("nosec", "true")
customAnalyzer := gas.NewAnalyzer(nosecIgnoreConfig, logger) customAnalyzer := gas.NewAnalyzer(nosecIgnoreConfig, logger)
customAnalyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()...) customAnalyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders())
nosecPackage := testutils.NewTestPackage() nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close() defer nosecPackage.Close()

View file

@ -207,7 +207,7 @@ func main() {
// Create the analyzer // Create the analyzer
analyzer := gas.NewAnalyzer(config, logger) analyzer := gas.NewAnalyzer(config, logger)
analyzer.LoadRules(ruleDefinitions.Builders()...) analyzer.LoadRules(ruleDefinitions.Builders())
vendor := regexp.MustCompile(`[\\/]vendor([\\/]|$)`) vendor := regexp.MustCompile(`[\\/]vendor([\\/]|$)`)

View file

@ -1,30 +0,0 @@
package gas_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("ImportTracker", func() {
var (
source string
)
BeforeEach(func() {
source = `// TODO(gm)`
})
Context("when I have a valid go package", func() {
It("should record all import specs", func() {
Expect(source).To(Equal(source))
Skip("Not implemented")
})
It("should correctly track aliased package imports", func() {
Skip("Not implemented")
})
It("should correctly track init only packages", func() {
Skip("Not implemented")
})
})
})

View file

@ -47,6 +47,7 @@ type Issue struct {
// MetaData is embedded in all GAS rules. The Severity, Confidence and What message // MetaData is embedded in all GAS rules. The Severity, Confidence and What message
// will be passed tbhrough to reported issues. // will be passed tbhrough to reported issues.
type MetaData struct { type MetaData struct {
ID string
Severity Score Severity Score
Confidence Score Confidence Score
What string What string

View file

@ -78,7 +78,7 @@ var _ = Describe("Issue", func() {
// Use SQL rule to check binary expr // Use SQL rule to check binary expr
cfg := gas.NewConfig() cfg := gas.NewConfig()
rule, _ := rules.NewSQLStrConcat(cfg) rule, _ := rules.NewSQLStrConcat("TEST", cfg)
issue, err := rule.Match(target, ctx) issue, err := rule.Match(target, ctx)
Expect(err).ShouldNot(HaveOccurred()) Expect(err).ShouldNot(HaveOccurred())
Expect(issue).ShouldNot(BeNil()) Expect(issue).ShouldNot(BeNil())

View file

@ -19,11 +19,12 @@ import (
// The Rule interface used by all rules supported by GAS. // The Rule interface used by all rules supported by GAS.
type Rule interface { type Rule interface {
ID() string
Match(ast.Node, *Context) (*Issue, error) Match(ast.Node, *Context) (*Issue, error)
} }
// RuleBuilder is used to register a rule definition with the analyzer // RuleBuilder is used to register a rule definition with the analyzer
type RuleBuilder func(c Config) (Rule, []ast.Node) type RuleBuilder func(id string, c Config) (Rule, []ast.Node)
// A RuleSet maps lists of rules to the type of AST node they should be run on. // A RuleSet maps lists of rules to the type of AST node they should be run on.
// The anaylzer will only invoke rules contained in the list associated with the // The anaylzer will only invoke rules contained in the list associated with the

View file

@ -15,6 +15,10 @@ type mockrule struct {
callback func(n ast.Node, ctx *gas.Context) bool callback func(n ast.Node, ctx *gas.Context) bool
} }
func (m *mockrule) ID() string {
return "MOCK"
}
func (m *mockrule) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) { func (m *mockrule) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) {
if m.callback(n, ctx) { if m.callback(n, ctx) {
return m.issue, nil return m.issue, nil

View file

@ -26,6 +26,10 @@ type usingBigExp struct {
calls []string calls []string
} }
func (r *usingBigExp) ID() string {
return r.MetaData.ID
}
func (r *usingBigExp) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) { func (r *usingBigExp) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
if _, matched := gas.MatchCallByType(n, c, r.pkg, r.calls...); matched { if _, matched := gas.MatchCallByType(n, c, r.pkg, r.calls...); matched {
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
@ -34,11 +38,12 @@ func (r *usingBigExp) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err erro
} }
// NewUsingBigExp detects issues with modulus == 0 for Bignum // NewUsingBigExp detects issues with modulus == 0 for Bignum
func NewUsingBigExp(conf gas.Config) (gas.Rule, []ast.Node) { func NewUsingBigExp(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &usingBigExp{ return &usingBigExp{
pkg: "*math/big.Int", pkg: "*math/big.Int",
calls: []string{"Exp"}, calls: []string{"Exp"},
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
What: "Use of math/big.Int.Exp function should be audited for modulus == 0", What: "Use of math/big.Int.Exp function should be audited for modulus == 0",
Severity: gas.Low, Severity: gas.Low,
Confidence: gas.High, Confidence: gas.High,

View file

@ -28,6 +28,10 @@ type bindsToAllNetworkInterfaces struct {
pattern *regexp.Regexp pattern *regexp.Regexp
} }
func (r *bindsToAllNetworkInterfaces) ID() string {
return r.MetaData.ID
}
func (r *bindsToAllNetworkInterfaces) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { func (r *bindsToAllNetworkInterfaces) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
callExpr := r.calls.ContainsCallExpr(n, c) callExpr := r.calls.ContainsCallExpr(n, c)
if callExpr == nil { if callExpr == nil {
@ -43,7 +47,7 @@ func (r *bindsToAllNetworkInterfaces) Match(n ast.Node, c *gas.Context) (*gas.Is
// NewBindsToAllNetworkInterfaces detects socket connections that are setup to // NewBindsToAllNetworkInterfaces detects socket connections that are setup to
// listen on all network interfaces. // listen on all network interfaces.
func NewBindsToAllNetworkInterfaces(conf gas.Config) (gas.Rule, []ast.Node) { func NewBindsToAllNetworkInterfaces(id string, conf gas.Config) (gas.Rule, []ast.Node) {
calls := gas.NewCallList() calls := gas.NewCallList()
calls.Add("net", "Listen") calls.Add("net", "Listen")
calls.Add("crypto/tls", "Listen") calls.Add("crypto/tls", "Listen")
@ -51,6 +55,7 @@ func NewBindsToAllNetworkInterfaces(conf gas.Config) (gas.Rule, []ast.Node) {
calls: calls, calls: calls,
pattern: regexp.MustCompile(`^(0.0.0.0|:).*$`), pattern: regexp.MustCompile(`^(0.0.0.0|:).*$`),
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium, Severity: gas.Medium,
Confidence: gas.High, Confidence: gas.High,
What: "Binds to all network interfaces", What: "Binds to all network interfaces",

View file

@ -32,6 +32,10 @@ func unquote(original string) string {
return strings.TrimRight(copy, `"`) return strings.TrimRight(copy, `"`)
} }
func (r *blacklistedImport) ID() string {
return r.MetaData.ID
}
func (r *blacklistedImport) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { func (r *blacklistedImport) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if node, ok := n.(*ast.ImportSpec); ok { if node, ok := n.(*ast.ImportSpec); ok {
if description, ok := r.Blacklisted[unquote(node.Path.Value)]; ok { if description, ok := r.Blacklisted[unquote(node.Path.Value)]; ok {
@ -43,9 +47,10 @@ func (r *blacklistedImport) Match(n ast.Node, c *gas.Context) (*gas.Issue, error
// NewBlacklistedImports reports when a blacklisted import is being used. // NewBlacklistedImports reports when a blacklisted import is being used.
// Typically when a deprecated technology is being used. // Typically when a deprecated technology is being used.
func NewBlacklistedImports(conf gas.Config, blacklist map[string]string) (gas.Rule, []ast.Node) { func NewBlacklistedImports(id string, conf gas.Config, blacklist map[string]string) (gas.Rule, []ast.Node) {
return &blacklistedImport{ return &blacklistedImport{
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium, Severity: gas.Medium,
Confidence: gas.High, Confidence: gas.High,
}, },
@ -54,29 +59,29 @@ func NewBlacklistedImports(conf gas.Config, blacklist map[string]string) (gas.Ru
} }
// NewBlacklistedImportMD5 fails if MD5 is imported // NewBlacklistedImportMD5 fails if MD5 is imported
func NewBlacklistedImportMD5(conf gas.Config) (gas.Rule, []ast.Node) { func NewBlacklistedImportMD5(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return NewBlacklistedImports(conf, map[string]string{ return NewBlacklistedImports(id, conf, map[string]string{
"crypto/md5": "Blacklisted import crypto/md5: weak cryptographic primitive", "crypto/md5": "Blacklisted import crypto/md5: weak cryptographic primitive",
}) })
} }
// NewBlacklistedImportDES fails if DES is imported // NewBlacklistedImportDES fails if DES is imported
func NewBlacklistedImportDES(conf gas.Config) (gas.Rule, []ast.Node) { func NewBlacklistedImportDES(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return NewBlacklistedImports(conf, map[string]string{ return NewBlacklistedImports(id, conf, map[string]string{
"crypto/des": "Blacklisted import crypto/des: weak cryptographic primitive", "crypto/des": "Blacklisted import crypto/des: weak cryptographic primitive",
}) })
} }
// NewBlacklistedImportRC4 fails if DES is imported // NewBlacklistedImportRC4 fails if DES is imported
func NewBlacklistedImportRC4(conf gas.Config) (gas.Rule, []ast.Node) { func NewBlacklistedImportRC4(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return NewBlacklistedImports(conf, map[string]string{ return NewBlacklistedImports(id, conf, map[string]string{
"crypto/rc4": "Blacklisted import crypto/rc4: weak cryptographic primitive", "crypto/rc4": "Blacklisted import crypto/rc4: weak cryptographic primitive",
}) })
} }
// NewBlacklistedImportCGI fails if CGI is imported // NewBlacklistedImportCGI fails if CGI is imported
func NewBlacklistedImportCGI(conf gas.Config) (gas.Rule, []ast.Node) { func NewBlacklistedImportCGI(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return NewBlacklistedImports(conf, map[string]string{ return NewBlacklistedImports(id, conf, map[string]string{
"net/http/cgi": "Blacklisted import net/http/cgi: Go versions < 1.6.3 are vulnerable to Httpoxy attack: (CVE-2016-5386)", "net/http/cgi": "Blacklisted import net/http/cgi: Go versions < 1.6.3 are vulnerable to Httpoxy attack: (CVE-2016-5386)",
}) })
} }

View file

@ -26,6 +26,10 @@ type noErrorCheck struct {
whitelist gas.CallList whitelist gas.CallList
} }
func (r *noErrorCheck) ID() string {
return r.MetaData.ID
}
func returnsError(callExpr *ast.CallExpr, ctx *gas.Context) int { func returnsError(callExpr *ast.CallExpr, ctx *gas.Context) int {
if tv := ctx.Info.TypeOf(callExpr); tv != nil { if tv := ctx.Info.TypeOf(callExpr); tv != nil {
switch t := tv.(type) { switch t := tv.(type) {
@ -71,8 +75,7 @@ func (r *noErrorCheck) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) {
} }
// NewNoErrorCheck detects if the returned error is unchecked // NewNoErrorCheck detects if the returned error is unchecked
func NewNoErrorCheck(conf gas.Config) (gas.Rule, []ast.Node) { func NewNoErrorCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) {
// TODO(gm) Come up with sensible defaults here. Or flip it to use a // TODO(gm) Come up with sensible defaults here. Or flip it to use a
// black list instead. // black list instead.
whitelist := gas.NewCallList() whitelist := gas.NewCallList()
@ -89,6 +92,7 @@ func NewNoErrorCheck(conf gas.Config) (gas.Rule, []ast.Node) {
} }
return &noErrorCheck{ return &noErrorCheck{
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
Severity: gas.Low, Severity: gas.Low,
Confidence: gas.High, Confidence: gas.High,
What: "Errors unhandled.", What: "Errors unhandled.",

View file

@ -29,6 +29,10 @@ type filePermissions struct {
calls []string calls []string
} }
func (r *filePermissions) ID() string {
return r.MetaData.ID
}
func getConfiguredMode(conf map[string]interface{}, configKey string, defaultMode int64) int64 { func getConfiguredMode(conf map[string]interface{}, configKey string, defaultMode int64) int64 {
var mode = defaultMode var mode = defaultMode
if value, ok := conf[configKey]; ok { if value, ok := conf[configKey]; ok {
@ -58,13 +62,14 @@ func (r *filePermissions) Match(n ast.Node, c *gas.Context) (*gas.Issue, error)
// NewFilePerms creates a rule to detect file creation with a more permissive than configured // NewFilePerms creates a rule to detect file creation with a more permissive than configured
// permission mask. // permission mask.
func NewFilePerms(conf gas.Config) (gas.Rule, []ast.Node) { func NewFilePerms(id string, conf gas.Config) (gas.Rule, []ast.Node) {
mode := getConfiguredMode(conf, "G302", 0600) mode := getConfiguredMode(conf, "G302", 0600)
return &filePermissions{ return &filePermissions{
mode: mode, mode: mode,
pkg: "os", pkg: "os",
calls: []string{"OpenFile", "Chmod"}, calls: []string{"OpenFile", "Chmod"},
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium, Severity: gas.Medium,
Confidence: gas.High, Confidence: gas.High,
What: fmt.Sprintf("Expect file permissions to be %#o or less", mode), What: fmt.Sprintf("Expect file permissions to be %#o or less", mode),
@ -74,13 +79,14 @@ func NewFilePerms(conf gas.Config) (gas.Rule, []ast.Node) {
// NewMkdirPerms creates a rule to detect directory creation with more permissive than // NewMkdirPerms creates a rule to detect directory creation with more permissive than
// configured permission mask. // configured permission mask.
func NewMkdirPerms(conf gas.Config) (gas.Rule, []ast.Node) { func NewMkdirPerms(id string, conf gas.Config) (gas.Rule, []ast.Node) {
mode := getConfiguredMode(conf, "G301", 0750) mode := getConfiguredMode(conf, "G301", 0750)
return &filePermissions{ return &filePermissions{
mode: mode, mode: mode,
pkg: "os", pkg: "os",
calls: []string{"Mkdir", "MkdirAll"}, calls: []string{"Mkdir", "MkdirAll"},
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium, Severity: gas.Medium,
Confidence: gas.High, Confidence: gas.High,
What: fmt.Sprintf("Expect directory permissions to be %#o or less", mode), What: fmt.Sprintf("Expect directory permissions to be %#o or less", mode),

View file

@ -32,6 +32,10 @@ type credentials struct {
ignoreEntropy bool ignoreEntropy bool
} }
func (r *credentials) ID() string {
return r.MetaData.ID
}
func truncate(s string, n int) string { func truncate(s string, n int) string {
if n > len(s) { if n > len(s) {
return s return s
@ -94,7 +98,7 @@ func (r *credentials) matchValueSpec(valueSpec *ast.ValueSpec, ctx *gas.Context)
// NewHardcodedCredentials attempts to find high entropy string constants being // NewHardcodedCredentials attempts to find high entropy string constants being
// assigned to variables that appear to be related to credentials. // assigned to variables that appear to be related to credentials.
func NewHardcodedCredentials(conf gas.Config) (gas.Rule, []ast.Node) { func NewHardcodedCredentials(id string, conf gas.Config) (gas.Rule, []ast.Node) {
pattern := `(?i)passwd|pass|password|pwd|secret|token` pattern := `(?i)passwd|pass|password|pwd|secret|token`
entropyThreshold := 80.0 entropyThreshold := 80.0
perCharThreshold := 3.0 perCharThreshold := 3.0
@ -134,6 +138,7 @@ func NewHardcodedCredentials(conf gas.Config) (gas.Rule, []ast.Node) {
ignoreEntropy: ignoreEntropy, ignoreEntropy: ignoreEntropy,
truncate: truncateString, truncate: truncateString,
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
What: "Potential hardcoded credentials", What: "Potential hardcoded credentials",
Confidence: gas.Low, Confidence: gas.Low,
Severity: gas.High, Severity: gas.High,

View file

@ -26,6 +26,10 @@ type weakRand struct {
packagePath string packagePath string
} }
func (w *weakRand) ID() string {
return w.MetaData.ID
}
func (w *weakRand) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { func (w *weakRand) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
for _, funcName := range w.funcNames { for _, funcName := range w.funcNames {
if _, matched := gas.MatchCallByPackage(n, c, w.packagePath, funcName); matched { if _, matched := gas.MatchCallByPackage(n, c, w.packagePath, funcName); matched {
@ -37,11 +41,12 @@ func (w *weakRand) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
} }
// NewWeakRandCheck detects the use of random number generator that isn't cryptographically secure // NewWeakRandCheck detects the use of random number generator that isn't cryptographically secure
func NewWeakRandCheck(conf gas.Config) (gas.Rule, []ast.Node) { func NewWeakRandCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &weakRand{ return &weakRand{
funcNames: []string{"Read", "Int"}, funcNames: []string{"Read", "Int"},
packagePath: "math/rand", packagePath: "math/rand",
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
Severity: gas.High, Severity: gas.High,
Confidence: gas.Medium, Confidence: gas.Medium,
What: "Use of weak random number generator (math/rand instead of crypto/rand)", What: "Use of weak random number generator (math/rand instead of crypto/rand)",

View file

@ -22,9 +22,16 @@ import (
) )
type readfile struct { type readfile struct {
gas.MetaData
gas.CallList gas.CallList
} }
// ID returns the identifier for this rule
func (r *readfile) ID() string {
return r.MetaData.ID
}
// Match inspects AST nodes to determine if the match the methods `os.Open` or `ioutil.ReadFile` // Match inspects AST nodes to determine if the match the methods `os.Open` or `ioutil.ReadFile`
func (r *readfile) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { func (r *readfile) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if node := r.ContainsCallExpr(n, c); node != nil { if node := r.ContainsCallExpr(n, c); node != nil {
@ -32,7 +39,7 @@ func (r *readfile) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if ident, ok := arg.(*ast.Ident); ok { if ident, ok := arg.(*ast.Ident); ok {
obj := c.Info.ObjectOf(ident) obj := c.Info.ObjectOf(ident)
if _, ok := obj.(*types.Var); ok && !gas.TryResolve(ident, c) { if _, ok := obj.(*types.Var); ok && !gas.TryResolve(ident, c) {
return gas.NewIssue(c, n, "File inclusion launched with variable", gas.Medium, gas.High), nil return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
} }
} }
} }
@ -41,8 +48,16 @@ func (r *readfile) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
} }
// NewReadFile detects cases where we read files // NewReadFile detects cases where we read files
func NewReadFile(conf gas.Config) (gas.Rule, []ast.Node) { func NewReadFile(id string, conf gas.Config) (gas.Rule, []ast.Node) {
rule := &readfile{gas.NewCallList()} rule := &readfile{
CallList: gas.NewCallList(),
MetaData: gas.MetaData{
ID: id,
What: "Potential file inclusion via variable",
Severity: gas.Medium,
Confidence: gas.High,
},
}
rule.Add("io/ioutil", "ReadFile") rule.Add("io/ioutil", "ReadFile")
rule.Add("os", "Open") rule.Add("os", "Open")
return rule, []ast.Node{(*ast.CallExpr)(nil)} return rule, []ast.Node{(*ast.CallExpr)(nil)}

View file

@ -27,6 +27,10 @@ type weakKeyStrength struct {
bits int bits int
} }
func (w *weakKeyStrength) ID() string {
return w.MetaData.ID
}
func (w *weakKeyStrength) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { func (w *weakKeyStrength) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if callExpr := w.calls.ContainsCallExpr(n, c); callExpr != nil { if callExpr := w.calls.ContainsCallExpr(n, c); callExpr != nil {
if bits, err := gas.GetInt(callExpr.Args[1]); err == nil && bits < (int64)(w.bits) { if bits, err := gas.GetInt(callExpr.Args[1]); err == nil && bits < (int64)(w.bits) {
@ -37,7 +41,7 @@ func (w *weakKeyStrength) Match(n ast.Node, c *gas.Context) (*gas.Issue, error)
} }
// NewWeakKeyStrength builds a rule that detects RSA keys < 2048 bits // NewWeakKeyStrength builds a rule that detects RSA keys < 2048 bits
func NewWeakKeyStrength(conf gas.Config) (gas.Rule, []ast.Node) { func NewWeakKeyStrength(id string, conf gas.Config) (gas.Rule, []ast.Node) {
calls := gas.NewCallList() calls := gas.NewCallList()
calls.Add("crypto/rsa", "GenerateKey") calls.Add("crypto/rsa", "GenerateKey")
bits := 2048 bits := 2048
@ -45,6 +49,7 @@ func NewWeakKeyStrength(conf gas.Config) (gas.Rule, []ast.Node) {
calls: calls, calls: calls,
bits: bits, bits: bits,
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium, Severity: gas.Medium,
Confidence: gas.High, Confidence: gas.High,
What: fmt.Sprintf("RSA keys should be at least %d bits", bits), What: fmt.Sprintf("RSA keys should be at least %d bits", bits),

View file

@ -21,6 +21,7 @@ import (
// RuleDefinition contains the description of a rule and a mechanism to // RuleDefinition contains the description of a rule and a mechanism to
// create it. // create it.
type RuleDefinition struct { type RuleDefinition struct {
ID string
Description string Description string
Create gas.RuleBuilder Create gas.RuleBuilder
} }
@ -29,10 +30,10 @@ type RuleDefinition struct {
type RuleList map[string]RuleDefinition type RuleList map[string]RuleDefinition
// Builders returns all the create methods for a given rule list // Builders returns all the create methods for a given rule list
func (rl RuleList) Builders() []gas.RuleBuilder { func (rl RuleList) Builders() map[string]gas.RuleBuilder {
builders := make([]gas.RuleBuilder, 0, len(rl)) builders := make(map[string]gas.RuleBuilder)
for _, def := range rl { for _, def := range rl {
builders = append(builders, def.Create) builders[def.ID] = def.Create
} }
return builders return builders
} }
@ -58,46 +59,50 @@ func NewRuleFilter(action bool, ruleIDs ...string) RuleFilter {
// Generate the list of rules to use // Generate the list of rules to use
func Generate(filters ...RuleFilter) RuleList { func Generate(filters ...RuleFilter) RuleList {
rules := map[string]RuleDefinition{ rules := []RuleDefinition{
// misc // misc
"G101": {"Look for hardcoded credentials", NewHardcodedCredentials}, {"G101", "Look for hardcoded credentials", NewHardcodedCredentials},
"G102": {"Bind to all interfaces", NewBindsToAllNetworkInterfaces}, {"G102", "Bind to all interfaces", NewBindsToAllNetworkInterfaces},
"G103": {"Audit the use of unsafe block", NewUsingUnsafe}, {"G103", "Audit the use of unsafe block", NewUsingUnsafe},
"G104": {"Audit errors not checked", NewNoErrorCheck}, {"G104", "Audit errors not checked", NewNoErrorCheck},
"G105": {"Audit the use of big.Exp function", NewUsingBigExp}, {"G105", "Audit the use of big.Exp function", NewUsingBigExp},
"G106": {"Audit the use of ssh.InsecureIgnoreHostKey function", NewSSHHostKey}, {"G106", "Audit the use of ssh.InsecureIgnoreHostKey function", NewSSHHostKey},
// injection // injection
"G201": {"SQL query construction using format string", NewSQLStrFormat}, {"G201", "SQL query construction using format string", NewSQLStrFormat},
"G202": {"SQL query construction using string concatenation", NewSQLStrConcat}, {"G202", "SQL query construction using string concatenation", NewSQLStrConcat},
"G203": {"Use of unescaped data in HTML templates", NewTemplateCheck}, {"G203", "Use of unescaped data in HTML templates", NewTemplateCheck},
"G204": {"Audit use of command execution", NewSubproc}, {"G204", "Audit use of command execution", NewSubproc},
// filesystem // filesystem
"G301": {"Poor file permissions used when creating a directory", NewMkdirPerms}, {"G301", "Poor file permissions used when creating a directory", NewMkdirPerms},
"G302": {"Poor file permisions used when creation file or using chmod", NewFilePerms}, {"G302", "Poor file permisions used when creation file or using chmod", NewFilePerms},
"G303": {"Creating tempfile using a predictable path", NewBadTempFile}, {"G303", "Creating tempfile using a predictable path", NewBadTempFile},
"G304": {"File path provided as taint input", NewReadFile}, {"G304", "File path provided as taint input", NewReadFile},
// crypto // crypto
"G401": {"Detect the usage of DES, RC4, or MD5", NewUsesWeakCryptography}, {"G401", "Detect the usage of DES, RC4, or MD5", NewUsesWeakCryptography},
"G402": {"Look for bad TLS connection settings", NewIntermediateTLSCheck}, {"G402", "Look for bad TLS connection settings", NewIntermediateTLSCheck},
"G403": {"Ensure minimum RSA key length of 2048 bits", NewWeakKeyStrength}, {"G403", "Ensure minimum RSA key length of 2048 bits", NewWeakKeyStrength},
"G404": {"Insecure random number source (rand)", NewWeakRandCheck}, {"G404", "Insecure random number source (rand)", NewWeakRandCheck},
// blacklist // blacklist
"G501": {"Import blacklist: crypto/md5", NewBlacklistedImportMD5}, {"G501", "Import blacklist: crypto/md5", NewBlacklistedImportMD5},
"G502": {"Import blacklist: crypto/des", NewBlacklistedImportDES}, {"G502", "Import blacklist: crypto/des", NewBlacklistedImportDES},
"G503": {"Import blacklist: crypto/rc4", NewBlacklistedImportRC4}, {"G503", "Import blacklist: crypto/rc4", NewBlacklistedImportRC4},
"G504": {"Import blacklist: net/http/cgi", NewBlacklistedImportCGI}, {"G504", "Import blacklist: net/http/cgi", NewBlacklistedImportCGI},
} }
for rule := range rules { ruleMap := make(map[string]RuleDefinition)
RULES:
for _, rule := range rules {
for _, filter := range filters { for _, filter := range filters {
if filter(rule) { if filter(rule.ID) {
delete(rules, rule) continue RULES
} }
} }
ruleMap[rule.ID] = rule
} }
return rules return ruleMap
} }

View file

@ -26,7 +26,7 @@ var _ = Describe("gas rules", func() {
config = gas.NewConfig() config = gas.NewConfig()
analyzer = gas.NewAnalyzer(config, logger) analyzer = gas.NewAnalyzer(config, logger)
runner = func(rule string, samples []testutils.CodeSample) { runner = func(rule string, samples []testutils.CodeSample) {
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, rule)).Builders()...) analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, rule)).Builders())
for n, sample := range samples { for n, sample := range samples {
analyzer.Reset() analyzer.Reset()
pkg := testutils.NewTestPackage() pkg := testutils.NewTestPackage()

View file

@ -28,6 +28,10 @@ type sqlStatement struct {
patterns []*regexp.Regexp patterns []*regexp.Regexp
} }
func (r *sqlStatement) ID() string {
return r.MetaData.ID
}
// See if the string matches the patterns for the statement. // See if the string matches the patterns for the statement.
func (s sqlStatement) MatchPatterns(str string) bool { func (s sqlStatement) MatchPatterns(str string) bool {
for _, pattern := range s.patterns { for _, pattern := range s.patterns {
@ -42,6 +46,10 @@ type sqlStrConcat struct {
sqlStatement sqlStatement
} }
func (r *sqlStrConcat) ID() string {
return r.MetaData.ID
}
// see if we can figure out what it is // see if we can figure out what it is
func (s *sqlStrConcat) checkObject(n *ast.Ident) bool { func (s *sqlStrConcat) checkObject(n *ast.Ident) bool {
if n.Obj != nil { if n.Obj != nil {
@ -72,13 +80,14 @@ func (s *sqlStrConcat) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
} }
// NewSQLStrConcat looks for cases where we are building SQL strings via concatenation // NewSQLStrConcat looks for cases where we are building SQL strings via concatenation
func NewSQLStrConcat(conf gas.Config) (gas.Rule, []ast.Node) { func NewSQLStrConcat(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &sqlStrConcat{ return &sqlStrConcat{
sqlStatement: sqlStatement{ sqlStatement: sqlStatement{
patterns: []*regexp.Regexp{ patterns: []*regexp.Regexp{
regexp.MustCompile(`(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) `), regexp.MustCompile(`(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) `),
}, },
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium, Severity: gas.Medium,
Confidence: gas.High, Confidence: gas.High,
What: "SQL string concatenation", What: "SQL string concatenation",
@ -105,7 +114,7 @@ func (s *sqlStrFormat) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
} }
// NewSQLStrFormat looks for cases where we're building SQL query strings using format strings // NewSQLStrFormat looks for cases where we're building SQL query strings using format strings
func NewSQLStrFormat(conf gas.Config) (gas.Rule, []ast.Node) { func NewSQLStrFormat(id string, conf gas.Config) (gas.Rule, []ast.Node) {
rule := &sqlStrFormat{ rule := &sqlStrFormat{
calls: gas.NewCallList(), calls: gas.NewCallList(),
sqlStatement: sqlStatement{ sqlStatement: sqlStatement{
@ -114,6 +123,7 @@ func NewSQLStrFormat(conf gas.Config) (gas.Rule, []ast.Node) {
regexp.MustCompile("%[^bdoxXfFp]"), regexp.MustCompile("%[^bdoxXfFp]"),
}, },
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium, Severity: gas.Medium,
Confidence: gas.High, Confidence: gas.High,
What: "SQL string formatting", What: "SQL string formatting",

View file

@ -12,6 +12,10 @@ type sshHostKey struct {
calls []string calls []string
} }
func (r *sshHostKey) ID() string {
return r.MetaData.ID
}
func (r *sshHostKey) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) { func (r *sshHostKey) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
if _, matches := gas.MatchCallByPackage(n, c, r.pkg, r.calls...); matches { if _, matches := gas.MatchCallByPackage(n, c, r.pkg, r.calls...); matches {
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
@ -20,11 +24,12 @@ func (r *sshHostKey) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error
} }
// NewSSHHostKey rule detects the use of insecure ssh HostKeyCallback. // NewSSHHostKey rule detects the use of insecure ssh HostKeyCallback.
func NewSSHHostKey(conf gas.Config) (gas.Rule, []ast.Node) { func NewSSHHostKey(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &sshHostKey{ return &sshHostKey{
pkg: "golang.org/x/crypto/ssh", pkg: "golang.org/x/crypto/ssh",
calls: []string{"InsecureIgnoreHostKey"}, calls: []string{"InsecureIgnoreHostKey"},
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
What: "Use of ssh InsecureIgnoreHostKey should be audited", What: "Use of ssh InsecureIgnoreHostKey should be audited",
Severity: gas.Medium, Severity: gas.Medium,
Confidence: gas.High, Confidence: gas.High,

View file

@ -22,9 +22,14 @@ import (
) )
type subprocess struct { type subprocess struct {
gas.MetaData
gas.CallList gas.CallList
} }
func (r *subprocess) ID() string {
return r.MetaData.ID
}
// TODO(gm) The only real potential for command injection with a Go project // TODO(gm) The only real potential for command injection with a Go project
// is something like this: // is something like this:
// //
@ -50,8 +55,8 @@ func (r *subprocess) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
} }
// NewSubproc detects cases where we are forking out to an external process // NewSubproc detects cases where we are forking out to an external process
func NewSubproc(conf gas.Config) (gas.Rule, []ast.Node) { func NewSubproc(id string, conf gas.Config) (gas.Rule, []ast.Node) {
rule := &subprocess{gas.NewCallList()} rule := &subprocess{gas.MetaData{ID: id}, gas.NewCallList()}
rule.Add("os/exec", "Command") rule.Add("os/exec", "Command")
rule.Add("syscall", "Exec") rule.Add("syscall", "Exec")
return rule, []ast.Node{(*ast.CallExpr)(nil)} return rule, []ast.Node{(*ast.CallExpr)(nil)}

View file

@ -27,6 +27,10 @@ type badTempFile struct {
args *regexp.Regexp args *regexp.Regexp
} }
func (t *badTempFile) ID() string {
return t.MetaData.ID
}
func (t *badTempFile) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) { func (t *badTempFile) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
if node := t.calls.ContainsCallExpr(n, c); node != nil { if node := t.calls.ContainsCallExpr(n, c); node != nil {
if arg, e := gas.GetString(node.Args[0]); t.args.MatchString(arg) && e == nil { if arg, e := gas.GetString(node.Args[0]); t.args.MatchString(arg) && e == nil {
@ -37,7 +41,7 @@ func (t *badTempFile) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err erro
} }
// NewBadTempFile detects direct writes to predictable path in temporary directory // NewBadTempFile detects direct writes to predictable path in temporary directory
func NewBadTempFile(conf gas.Config) (gas.Rule, []ast.Node) { func NewBadTempFile(id string, conf gas.Config) (gas.Rule, []ast.Node) {
calls := gas.NewCallList() calls := gas.NewCallList()
calls.Add("io/ioutil", "WriteFile") calls.Add("io/ioutil", "WriteFile")
calls.Add("os", "Create") calls.Add("os", "Create")
@ -45,6 +49,7 @@ func NewBadTempFile(conf gas.Config) (gas.Rule, []ast.Node) {
calls: calls, calls: calls,
args: regexp.MustCompile(`^/tmp/.*$|^/var/tmp/.*$`), args: regexp.MustCompile(`^/tmp/.*$|^/var/tmp/.*$`),
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium, Severity: gas.Medium,
Confidence: gas.High, Confidence: gas.High,
What: "File creation in shared tmp directory without using ioutil.Tempfile", What: "File creation in shared tmp directory without using ioutil.Tempfile",

View file

@ -25,6 +25,10 @@ type templateCheck struct {
calls gas.CallList calls gas.CallList
} }
func (t *templateCheck) ID() string {
return t.MetaData.ID
}
func (t *templateCheck) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { func (t *templateCheck) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if node := t.calls.ContainsCallExpr(n, c); node != nil { if node := t.calls.ContainsCallExpr(n, c); node != nil {
for _, arg := range node.Args { for _, arg := range node.Args {
@ -38,7 +42,7 @@ func (t *templateCheck) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
// NewTemplateCheck constructs the template check rule. This rule is used to // NewTemplateCheck constructs the template check rule. This rule is used to
// find use of tempaltes where HTML/JS escaping is not being used // find use of tempaltes where HTML/JS escaping is not being used
func NewTemplateCheck(conf gas.Config) (gas.Rule, []ast.Node) { func NewTemplateCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) {
calls := gas.NewCallList() calls := gas.NewCallList()
calls.Add("html/template", "HTML") calls.Add("html/template", "HTML")
@ -48,6 +52,7 @@ func NewTemplateCheck(conf gas.Config) (gas.Rule, []ast.Node) {
return &templateCheck{ return &templateCheck{
calls: calls, calls: calls,
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium, Severity: gas.Medium,
Confidence: gas.Low, Confidence: gas.Low,
What: "this method will not auto-escape HTML. Verify data is well formed.", What: "this method will not auto-escape HTML. Verify data is well formed.",

View file

@ -24,12 +24,17 @@ import (
) )
type insecureConfigTLS struct { type insecureConfigTLS struct {
gas.MetaData
MinVersion int16 MinVersion int16
MaxVersion int16 MaxVersion int16
requiredType string requiredType string
goodCiphers []string goodCiphers []string
} }
func (t *insecureConfigTLS) ID() string {
return t.MetaData.ID
}
func stringInSlice(a string, list []string) bool { func stringInSlice(a string, list []string) bool {
for _, b := range list { for _, b := range list {
if b == a { if b == a {

View file

@ -8,8 +8,9 @@ import (
// NewModernTLSCheck creates a check for Modern TLS ciphers // NewModernTLSCheck creates a check for Modern TLS ciphers
// DO NOT EDIT - generated by tlsconfig tool // DO NOT EDIT - generated by tlsconfig tool
func NewModernTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) { func NewModernTLSCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{ return &insecureConfigTLS{
MetaData: gas.MetaData{ID: id},
requiredType: "crypto/tls.Config", requiredType: "crypto/tls.Config",
MinVersion: 0x0303, MinVersion: 0x0303,
MaxVersion: 0x0303, MaxVersion: 0x0303,
@ -28,8 +29,9 @@ func NewModernTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) {
// NewIntermediateTLSCheck creates a check for Intermediate TLS ciphers // NewIntermediateTLSCheck creates a check for Intermediate TLS ciphers
// DO NOT EDIT - generated by tlsconfig tool // DO NOT EDIT - generated by tlsconfig tool
func NewIntermediateTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) { func NewIntermediateTLSCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{ return &insecureConfigTLS{
MetaData: gas.MetaData{ID: id},
requiredType: "crypto/tls.Config", requiredType: "crypto/tls.Config",
MinVersion: 0x0301, MinVersion: 0x0301,
MaxVersion: 0x0303, MaxVersion: 0x0303,
@ -68,8 +70,9 @@ func NewIntermediateTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) {
// NewOldTLSCheck creates a check for Old TLS ciphers // NewOldTLSCheck creates a check for Old TLS ciphers
// DO NOT EDIT - generated by tlsconfig tool // DO NOT EDIT - generated by tlsconfig tool
func NewOldTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) { func NewOldTLSCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{ return &insecureConfigTLS{
MetaData: gas.MetaData{ID: id},
requiredType: "crypto/tls.Config", requiredType: "crypto/tls.Config",
MinVersion: 0x0300, MinVersion: 0x0300,
MaxVersion: 0x0303, MaxVersion: 0x0303,

View file

@ -26,6 +26,10 @@ type usingUnsafe struct {
calls []string calls []string
} }
func (r *usingUnsafe) ID() string {
return r.MetaData.ID
}
func (r *usingUnsafe) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) { func (r *usingUnsafe) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
if _, matches := gas.MatchCallByPackage(n, c, r.pkg, r.calls...); matches { if _, matches := gas.MatchCallByPackage(n, c, r.pkg, r.calls...); matches {
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
@ -35,11 +39,12 @@ func (r *usingUnsafe) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err erro
// NewUsingUnsafe rule detects the use of the unsafe package. This is only // NewUsingUnsafe rule detects the use of the unsafe package. This is only
// really useful for auditing purposes. // really useful for auditing purposes.
func NewUsingUnsafe(conf gas.Config) (gas.Rule, []ast.Node) { func NewUsingUnsafe(id string, conf gas.Config) (gas.Rule, []ast.Node) {
return &usingUnsafe{ return &usingUnsafe{
pkg: "unsafe", pkg: "unsafe",
calls: []string{"Alignof", "Offsetof", "Sizeof", "Pointer"}, calls: []string{"Alignof", "Offsetof", "Sizeof", "Pointer"},
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
What: "Use of unsafe calls should be audited", What: "Use of unsafe calls should be audited",
Severity: gas.Low, Severity: gas.Low,
Confidence: gas.High, Confidence: gas.High,

View file

@ -25,8 +25,11 @@ type usesWeakCryptography struct {
blacklist map[string][]string blacklist map[string][]string
} }
func (r *usesWeakCryptography) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { func (r *usesWeakCryptography) ID() string {
return r.MetaData.ID
}
func (r *usesWeakCryptography) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
for pkg, funcs := range r.blacklist { for pkg, funcs := range r.blacklist {
if _, matched := gas.MatchCallByPackage(n, c, pkg, funcs...); matched { if _, matched := gas.MatchCallByPackage(n, c, pkg, funcs...); matched {
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
@ -36,7 +39,7 @@ func (r *usesWeakCryptography) Match(n ast.Node, c *gas.Context) (*gas.Issue, er
} }
// NewUsesWeakCryptography detects uses of des.* md5.* or rc4.* // NewUsesWeakCryptography detects uses of des.* md5.* or rc4.*
func NewUsesWeakCryptography(conf gas.Config) (gas.Rule, []ast.Node) { func NewUsesWeakCryptography(id string, conf gas.Config) (gas.Rule, []ast.Node) {
calls := make(map[string][]string) calls := make(map[string][]string)
calls["crypto/des"] = []string{"NewCipher", "NewTripleDESCipher"} calls["crypto/des"] = []string{"NewCipher", "NewTripleDESCipher"}
calls["crypto/md5"] = []string{"New", "Sum"} calls["crypto/md5"] = []string{"New", "Sum"}
@ -44,6 +47,7 @@ func NewUsesWeakCryptography(conf gas.Config) (gas.Rule, []ast.Node) {
rule := &usesWeakCryptography{ rule := &usesWeakCryptography{
blacklist: calls, blacklist: calls,
MetaData: gas.MetaData{ MetaData: gas.MetaData{
ID: id,
Severity: gas.Medium, Severity: gas.Medium,
Confidence: gas.High, Confidence: gas.High,
What: "Use of weak cryptographic primitive", What: "Use of weak cryptographic primitive",