mirror of
https://github.com/securego/gosec.git
synced 2024-12-24 11:35:52 +00:00
Add support for #excluding specific rules
This commit is contained in:
parent
3713168816
commit
1429033aca
26 changed files with 288 additions and 94 deletions
91
analyzer.go
91
analyzer.go
|
@ -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,41 +149,84 @@ 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
|
return nil, true
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(group.Text(), "#exclude") {
|
||||||
|
gas.stats.NumNosec++
|
||||||
|
|
||||||
|
// Pull out the specific rules that are listed to be ignored.
|
||||||
|
re := regexp.MustCompile("!(G\\d{3})")
|
||||||
|
matches := re.FindAllStringSubmatch(group.Text(), -1)
|
||||||
|
|
||||||
|
// Find the rule IDs to ignore.
|
||||||
|
ignores := make([]string, 0)
|
||||||
|
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 {
|
||||||
// Track aliased and initialization imports
|
if len(gas.context.Ignores) > 0 {
|
||||||
gas.context.Imports.TrackImport(n)
|
gas.context.Ignores = gas.context.Ignores[1:]
|
||||||
|
|
||||||
for _, rule := range gas.ruleset.RegisteredFor(n) {
|
|
||||||
issue, err := rule.Match(n, gas.context)
|
|
||||||
if err != nil {
|
|
||||||
file, line := GetLocation(n, gas.context)
|
|
||||||
file = path.Base(file)
|
|
||||||
gas.logger.Printf("Rule error: %v => %s (%s:%d)\n", reflect.TypeOf(rule), err, file, line)
|
|
||||||
}
|
|
||||||
if issue != nil {
|
|
||||||
gas.issues = append(gas.issues, issue)
|
|
||||||
gas.stats.NumFound++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return gas
|
return gas
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
// 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
|
||||||
|
gas.context.Imports.TrackImport(n)
|
||||||
|
|
||||||
|
for _, rule := range gas.ruleset.RegisteredFor(n) {
|
||||||
|
if _, ok := ignores[rule.ID()]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
issue, err := rule.Match(n, gas.context)
|
||||||
|
if err != nil {
|
||||||
|
file, line := GetLocation(n, gas.context)
|
||||||
|
file = path.Base(file)
|
||||||
|
gas.logger.Printf("Rule error: %v => %s (%s:%d)\n", reflect.TypeOf(rule), err, file, line)
|
||||||
|
}
|
||||||
|
if issue != nil {
|
||||||
|
gas.issues = append(gas.issues, issue)
|
||||||
|
gas.stats.NumFound++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gas
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report returns the current issues discovered and the metrics about the scan
|
// Report returns the current issues discovered and the metrics about the scan
|
||||||
|
|
|
@ -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() // #exclude !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() // #exclude !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() // #exclude !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()
|
||||||
|
|
|
@ -206,7 +206,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([\\/]|$)`)
|
||||||
|
|
||||||
|
|
1
issue.go
1
issue.go
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
|
|
3
rule.go
3
rule.go
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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)",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.",
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -33,6 +33,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
|
||||||
|
@ -102,7 +106,7 @@ func (r *credentials) matchGenDecl(decl *ast.GenDecl, ctx *gas.Context) (*gas.Is
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -142,6 +146,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,
|
||||||
|
|
|
@ -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)",
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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,45 +59,49 @@ 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},
|
RuleDefinition{"G101", "Look for hardcoded credentials", NewHardcodedCredentials},
|
||||||
"G102": {"Bind to all interfaces", NewBindsToAllNetworkInterfaces},
|
RuleDefinition{"G102", "Bind to all interfaces", NewBindsToAllNetworkInterfaces},
|
||||||
"G103": {"Audit the use of unsafe block", NewUsingUnsafe},
|
RuleDefinition{"G103", "Audit the use of unsafe block", NewUsingUnsafe},
|
||||||
"G104": {"Audit errors not checked", NewNoErrorCheck},
|
RuleDefinition{"G104", "Audit errors not checked", NewNoErrorCheck},
|
||||||
"G105": {"Audit the use of big.Exp function", NewUsingBigExp},
|
RuleDefinition{"G105", "Audit the use of big.Exp function", NewUsingBigExp},
|
||||||
"G106": {"Audit the use of ssh.InsecureIgnoreHostKey function", NewSSHHostKey},
|
RuleDefinition{"G106", "Audit the use of ssh.InsecureIgnoreHostKey function", NewSSHHostKey},
|
||||||
|
|
||||||
// injection
|
// injection
|
||||||
"G201": {"SQL query construction using format string", NewSQLStrFormat},
|
RuleDefinition{"G201", "SQL query construction using format string", NewSQLStrFormat},
|
||||||
"G202": {"SQL query construction using string concatenation", NewSQLStrConcat},
|
RuleDefinition{"G202", "SQL query construction using string concatenation", NewSQLStrConcat},
|
||||||
"G203": {"Use of unescaped data in HTML templates", NewTemplateCheck},
|
RuleDefinition{"G203", "Use of unescaped data in HTML templates", NewTemplateCheck},
|
||||||
"G204": {"Audit use of command execution", NewSubproc},
|
RuleDefinition{"G204", "Audit use of command execution", NewSubproc},
|
||||||
|
|
||||||
// filesystem
|
// filesystem
|
||||||
"G301": {"Poor file permissions used when creating a directory", NewMkdirPerms},
|
RuleDefinition{"G301", "Poor file permissions used when creating a directory", NewMkdirPerms},
|
||||||
"G302": {"Poor file permisions used when creation file or using chmod", NewFilePerms},
|
RuleDefinition{"G302", "Poor file permisions used when creation file or using chmod", NewFilePerms},
|
||||||
"G303": {"Creating tempfile using a predictable path", NewBadTempFile},
|
RuleDefinition{"G303", "Creating tempfile using a predictable path", NewBadTempFile},
|
||||||
|
|
||||||
// crypto
|
// crypto
|
||||||
"G401": {"Detect the usage of DES, RC4, or MD5", NewUsesWeakCryptography},
|
RuleDefinition{"G401", "Detect the usage of DES, RC4, or MD5", NewUsesWeakCryptography},
|
||||||
"G402": {"Look for bad TLS connection settings", NewIntermediateTLSCheck},
|
RuleDefinition{"G402", "Look for bad TLS connection settings", NewIntermediateTLSCheck},
|
||||||
"G403": {"Ensure minimum RSA key length of 2048 bits", NewWeakKeyStrength},
|
RuleDefinition{"G403", "Ensure minimum RSA key length of 2048 bits", NewWeakKeyStrength},
|
||||||
"G404": {"Insecure random number source (rand)", NewWeakRandCheck},
|
RuleDefinition{"G404", "Insecure random number source (rand)", NewWeakRandCheck},
|
||||||
|
|
||||||
// blacklist
|
// blacklist
|
||||||
"G501": {"Import blacklist: crypto/md5", NewBlacklistedImportMD5},
|
RuleDefinition{"G501", "Import blacklist: crypto/md5", NewBlacklistedImportMD5},
|
||||||
"G502": {"Import blacklist: crypto/des", NewBlacklistedImportDES},
|
RuleDefinition{"G502", "Import blacklist: crypto/des", NewBlacklistedImportDES},
|
||||||
"G503": {"Import blacklist: crypto/rc4", NewBlacklistedImportRC4},
|
RuleDefinition{"G503", "Import blacklist: crypto/rc4", NewBlacklistedImportRC4},
|
||||||
"G504": {"Import blacklist: net/http/cgi", NewBlacklistedImportCGI},
|
RuleDefinition{"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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
14
rules/sql.go
14
rules/sql.go
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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.",
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue