diff --git a/Makefile b/Makefile index 4f6cce7..6b92d64 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ build-race: go build -race -o $(BIN) ./cmd/gosec/ clean: - rm -rf build vendor dist coverage.txt + rm -rf build vendor dist coverage.out rm -f release image $(BIN) release: diff --git a/README.md b/README.md index 51937c4..cfdae8a 100644 --- a/README.md +++ b/README.md @@ -156,11 +156,14 @@ directory you can supply `./...` as the input argument. - G403: Ensure minimum RSA key length of 2048 bits - G404: Insecure random number source (rand) - G405: Detect the usage of DES or RC4 +- G406: Detect the usage of MD4 or RIPEMD160 - G501: Import blocklist: crypto/md5 - G502: Import blocklist: crypto/des - G503: Import blocklist: crypto/rc4 - G504: Import blocklist: net/http/cgi - G505: Import blocklist: crypto/sha1 +- G506: Import blocklist: golang.org/x/crypto/md4 +- G507: Import blocklist: golang.org/x/crypto/ripemd160 - G601: Implicit memory aliasing of items from a range statement (only for Go 1.21 or lower) - G602: Slice access out of bounds diff --git a/analyzer_test.go b/analyzer_test.go index 879db8c..e3a1260 100644 --- a/analyzer_test.go +++ b/analyzer_test.go @@ -1,3 +1,17 @@ +// (c) Copyright 2024 Mercedes-Benz Tech Innovation GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package gosec_test import ( @@ -156,6 +170,22 @@ var _ = Describe("Analyzer", func() { Expect(controlIssues).Should(HaveLen(sample.Errors)) }) + It("should find errors when nosec is not in use", func() { + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + controlPackage := testutils.NewTestPackage() + defer controlPackage.Close() + controlPackage.AddFile("md4.go", source) + err := controlPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = analyzer.Process(buildTags, controlPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + controlIssues, _, _ := analyzer.Report() + Expect(controlIssues).Should(HaveLen(sample.Errors)) + }) + It("should report Go build errors and invalid files", func() { analyzer.LoadRules(rules.Generate(false).RulesInfo()) pkg := testutils.NewTestPackage() @@ -218,6 +248,23 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(BeEmpty()) }) + It("should not report errors when a nosec line comment is present", func() { + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md4.New()", "h := md4.New() //#nosec", 1) + nosecPackage.AddFile("md4.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = analyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + nosecIssues, _, _ := analyzer.Report() + Expect(nosecIssues).Should(BeEmpty()) + }) + It("should not report errors when a nosec block comment is present", func() { sample := testutils.SampleCodeG401[0] source := sample.Code[0] @@ -252,6 +299,23 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(BeEmpty()) }) + It("should not report errors when a nosec block comment is present", func() { + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md4.New()", "h := md4.New() /* #nosec */", 1) + nosecPackage.AddFile("md4.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = analyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + nosecIssues, _, _ := analyzer.Report() + 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] @@ -288,6 +352,24 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(BeEmpty()) }) + It("should not report errors when an exclude comment is present for the correct rule", func() { + // Rule for MD4 deprecated weak crypto usage + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md4.New()", "h := md4.New() //#nosec G406", 1) + nosecPackage.AddFile("md4.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = analyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + nosecIssues, _, _ := analyzer.Report() + Expect(nosecIssues).Should(BeEmpty()) + }) + It("should not report errors when a nosec block and line comment are present", func() { sample := testutils.SampleCodeG101[23] source := sample.Code[0] @@ -368,6 +450,23 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(HaveLen(sample.Errors)) }) + It("should report errors when an exclude comment is present for a different rule", func() { + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md4.New()", "h := md4.New() //#nosec G301", 1) + nosecPackage.AddFile("md4.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = analyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + 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() { sample := testutils.SampleCodeG401[0] source := sample.Code[0] @@ -406,6 +505,25 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(BeEmpty()) }) + It("should not report errors when an exclude comment is present for multiple rules, including the correct rule", func() { + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md4.New()", "h := md4.New() //#nosec G301 G406", 1) + nosecPackage.AddFile("md4.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = analyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + err = analyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + nosecIssues, _, _ := analyzer.Report() + Expect(nosecIssues).Should(BeEmpty()) + }) + It("should pass the build tags", func() { sample := testutils.SampleCodeBuildTag[0] source := sample.Code[0] @@ -479,6 +597,29 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(HaveLen(sample.Errors)) }) + It("should be possible to overwrite nosec comments, and report issues", func() { + // Rule for MD4 weak crypto usage + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + + // overwrite nosec option + nosecIgnoreConfig := gosec.NewConfig() + nosecIgnoreConfig.SetGlobal(gosec.Nosec, "true") + customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, false, 1, logger) + customAnalyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md4.New()", "h := md4.New() //#nosec", 1) + nosecPackage.AddFile("md4.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = customAnalyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + nosecIssues, _, _ := customAnalyzer.Report() + Expect(nosecIssues).Should(HaveLen(sample.Errors)) + }) + It("should be possible to overwrite nosec comments, and report issues but they should not be counted", func() { // Rule for MD5 weak crypto usage sample := testutils.SampleCodeG401[0] @@ -531,6 +672,32 @@ var _ = Describe("Analyzer", func() { Expect(metrics.NumNosec).Should(Equal(1)) }) + It("should be possible to overwrite nosec comments, and report issues but they should not be counted", func() { + // Rule for MD4 weak crypto usage + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + + // overwrite nosec option + nosecIgnoreConfig := gosec.NewConfig() + nosecIgnoreConfig.SetGlobal(gosec.Nosec, "mynosec") + nosecIgnoreConfig.SetGlobal(gosec.ShowIgnored, "true") + customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, false, 1, logger) + customAnalyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md4.New()", "h := md4.New() // #mynosec", 1) + nosecPackage.AddFile("md4.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = customAnalyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + nosecIssues, metrics, _ := customAnalyzer.Report() + Expect(nosecIssues).Should(HaveLen(sample.Errors)) + Expect(metrics.NumFound).Should(Equal(0)) + Expect(metrics.NumNosec).Should(Equal(1)) + }) + It("should not report errors when nosec tag is in front of a line", func() { sample := testutils.SampleCodeG401[0] source := sample.Code[0] @@ -565,6 +732,23 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(BeEmpty()) }) + It("should not report errors when nosec tag is in front of a line", func() { + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md4.New()", "//Some description\n//#nosec G406\nh := md4.New()", 1) + nosecPackage.AddFile("md4.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = analyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + nosecIssues, _, _ := analyzer.Report() + Expect(nosecIssues).Should(BeEmpty()) + }) + It("should report errors when nosec tag is not in front of a line", func() { sample := testutils.SampleCodeG401[0] source := sample.Code[0] @@ -599,6 +783,23 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(HaveLen(sample.Errors)) }) + It("should report errors when nosec tag is not in front of a line", func() { + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md4.New()", "//Some description\n//Another description #nosec G406\nh := md4.New()", 1) + nosecPackage.AddFile("md4.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = analyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + nosecIssues, _, _ := analyzer.Report() + Expect(nosecIssues).Should(HaveLen(sample.Errors)) + }) + It("should not report errors when rules are in front of nosec tag even rules are wrong", func() { sample := testutils.SampleCodeG401[0] source := sample.Code[0] @@ -633,6 +834,23 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(BeEmpty()) }) + It("should not report errors when rules are in front of nosec tag even rules are wrong", func() { + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md4.New()", "//G301\n//#nosec\nh := md4.New()", 1) + nosecPackage.AddFile("md4.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = analyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + nosecIssues, _, _ := analyzer.Report() + Expect(nosecIssues).Should(BeEmpty()) + }) + It("should report errors when there are nosec tags after a #nosec WrongRuleList annotation", func() { sample := testutils.SampleCodeG401[0] source := sample.Code[0] @@ -667,6 +885,23 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(HaveLen(sample.Errors)) }) + It("should report errors when there are nosec tags after a #nosec WrongRuleList annotation", func() { + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md4.New()", "//#nosec\n//G301\n//#nosec\nh := md4.New()", 1) + nosecPackage.AddFile("md4.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = analyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + nosecIssues, _, _ := analyzer.Report() + Expect(nosecIssues).Should(HaveLen(sample.Errors)) + }) + It("should be possible to use an alternative nosec tag", func() { // Rule for MD5 weak crypto usage sample := testutils.SampleCodeG401[0] @@ -713,6 +948,29 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(BeEmpty()) }) + It("should be possible to use an alternative nosec tag", func() { + // Rule for MD4 deprecated weak crypto usage + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + + // overwrite nosec option + nosecIgnoreConfig := gosec.NewConfig() + nosecIgnoreConfig.SetGlobal(gosec.NoSecAlternative, "falsePositive") + customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, false, 1, logger) + customAnalyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md4.New()", "h := md4.New() // #falsePositive", 1) + nosecPackage.AddFile("md4.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = customAnalyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + nosecIssues, _, _ := customAnalyzer.Report() + Expect(nosecIssues).Should(BeEmpty()) + }) + It("should ignore vulnerabilities when the default tag is found", func() { // Rule for MD5 weak crypto usage sample := testutils.SampleCodeG401[0] @@ -759,6 +1017,29 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(BeEmpty()) }) + It("should ignore vulnerabilities when the default tag is found", func() { + // Rule for MD4 deprecated weak crypto usage + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + + // overwrite nosec option + nosecIgnoreConfig := gosec.NewConfig() + nosecIgnoreConfig.SetGlobal(gosec.NoSecAlternative, "falsePositive") + customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, false, 1, logger) + customAnalyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md4.New()", "h := md4.New() //#nosec", 1) + nosecPackage.AddFile("md4.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = customAnalyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + nosecIssues, _, _ := customAnalyzer.Report() + Expect(nosecIssues).Should(BeEmpty()) + }) + It("should be able to analyze Go test package", func() { customAnalyzer := gosec.NewAnalyzer(nil, true, false, false, 1, logger) customAnalyzer.LoadRules(rules.Generate(false).RulesInfo()) @@ -1100,6 +1381,26 @@ var _ = Describe("Analyzer", func() { Expect(issues[0].Suppressions[0].Justification).To(Equal("Justification")) }) + It("should not report an error if the violation is suppressed", func() { + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md4.New()", "h := md4.New() //#nosec G406 -- Justification", 1) + nosecPackage.AddFile("md4.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = analyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + issues, _, _ := analyzer.Report() + Expect(issues).To(HaveLen(sample.Errors)) + Expect(issues[0].Suppressions).To(HaveLen(1)) + Expect(issues[0].Suppressions[0].Kind).To(Equal("inSource")) + Expect(issues[0].Suppressions[0].Justification).To(Equal("Justification")) + }) + It("should not report an error if the violation is suppressed without certain rules", func() { sample := testutils.SampleCodeG401[0] source := sample.Code[0] @@ -1140,6 +1441,26 @@ var _ = Describe("Analyzer", func() { Expect(issues[0].Suppressions[0].Justification).To(Equal("")) }) + It("should not report an error if the violation is suppressed without certain rules", func() { + sample := testutils.SampleCodeG406[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G406")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md4.New()", "h := md4.New() //#nosec", 1) + nosecPackage.AddFile("md4.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = analyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + issues, _, _ := analyzer.Report() + Expect(issues).To(HaveLen(sample.Errors)) + Expect(issues[0].Suppressions).To(HaveLen(1)) + Expect(issues[0].Suppressions[0].Kind).To(Equal("inSource")) + Expect(issues[0].Suppressions[0].Justification).To(Equal("")) + }) + It("should not report an error if the rule is not included", func() { sample := testutils.SampleCodeG101[0] source := sample.Code[0] diff --git a/call_list_test.go b/call_list_test.go index 57a8e96..179b127 100644 --- a/call_list_test.go +++ b/call_list_test.go @@ -119,6 +119,31 @@ var _ = Describe("Call List", func() { Expect(matched).Should(Equal(1)) }) + It("should match a package call expression", func() { + // Create file to be scanned + pkg := testutils.NewTestPackage() + defer pkg.Close() + pkg.AddFile("md4.go", testutils.SampleCodeG406[0].Code[0]) + + ctx := pkg.CreateContext("md4.go") + + // Search for md4.New() + calls.Add("golang.org/x/crypto/md4", "New") + + // Stub out visitor and count number of matched call expr + matched := 0 + v := testutils.NewMockVisitor() + v.Context = ctx + v.Callback = func(n ast.Node, ctx *gosec.Context) bool { + if _, ok := n.(*ast.CallExpr); ok && calls.ContainsPkgCallExpr(n, ctx, false) != nil { + matched++ + } + return true + } + ast.Walk(v, ctx.Root) + Expect(matched).Should(Equal(1)) + }) + It("should match a call expression", func() { // Create file to be scanned pkg := testutils.NewTestPackage() diff --git a/issue/issue.go b/issue/issue.go index d10f950..7e986e8 100644 --- a/issue/issue.go +++ b/issue/issue.go @@ -83,11 +83,14 @@ var ruleToCWE = map[string]string{ "G403": "310", "G404": "338", "G405": "327", + "G406": "328", "G501": "327", "G502": "327", "G503": "327", "G504": "327", "G505": "327", + "G506": "327", + "G507": "327", "G601": "118", "G602": "118", } diff --git a/report/formatter_test.go b/report/formatter_test.go index acaa84b..3116982 100644 --- a/report/formatter_test.go +++ b/report/formatter_test.go @@ -281,8 +281,8 @@ var _ = Describe("Formatter", func() { "G101", "G102", "G103", "G104", "G106", "G107", "G109", "G110", "G111", "G112", "G113", "G201", "G202", "G203", "G204", "G301", "G302", "G303", "G304", "G305", "G401", - "G402", "G403", "G404", "G405", "G501", "G502", "G503", - "G504", "G505", "G601", + "G402", "G403", "G404", "G405", "G406", "G501", "G502", + "G503", "G504", "G505", "G506", "G507", "G601", } It("csv formatted report should contain the CWE mapping", func() { diff --git a/rules/blocklist.go b/rules/blocklist.go index 5e03cf7..a4376b1 100644 --- a/rules/blocklist.go +++ b/rules/blocklist.go @@ -93,3 +93,17 @@ func NewBlocklistedImportSHA1(id string, conf gosec.Config) (gosec.Rule, []ast.N "crypto/sha1": "Blocklisted import crypto/sha1: weak cryptographic primitive", }) } + +// NewBlocklistedImportMD4 fails if MD4 is imported +func NewBlocklistedImportMD4(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { + return NewBlocklistedImports(id, conf, map[string]string{ + "golang.org/x/crypto/md4": "Blocklisted import golang.org/x/crypto/md4: deprecated and weak cryptographic primitive", + }) +} + +// NewBlocklistedImportRIPEMD160 fails if RIPEMD160 is imported +func NewBlocklistedImportRIPEMD160(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { + return NewBlocklistedImports(id, conf, map[string]string{ + "golang.org/x/crypto/ripemd160": "Blocklisted import golang.org/x/crypto/ripemd160: deprecated and weak cryptographic primitive", + }) +} diff --git a/rules/rulelist.go b/rules/rulelist.go index cb905f4..13f29f7 100644 --- a/rules/rulelist.go +++ b/rules/rulelist.go @@ -99,6 +99,7 @@ func Generate(trackSuppressions bool, filters ...RuleFilter) RuleList { {"G403", "Ensure minimum RSA key length of 2048 bits", NewWeakKeyStrength}, {"G404", "Insecure random number source (rand)", NewWeakRandCheck}, {"G405", "Detect the usage of DES or RC4", NewUsesWeakCryptographyEncryption}, + {"G406", "Detect the usage of deprecated MD4 or RIPEMD160", NewUsesWeakDeprecatedCryptographyHash}, // blocklist {"G501", "Import blocklist: crypto/md5", NewBlocklistedImportMD5}, @@ -106,6 +107,8 @@ func Generate(trackSuppressions bool, filters ...RuleFilter) RuleList { {"G503", "Import blocklist: crypto/rc4", NewBlocklistedImportRC4}, {"G504", "Import blocklist: net/http/cgi", NewBlocklistedImportCGI}, {"G505", "Import blocklist: crypto/sha1", NewBlocklistedImportSHA1}, + {"G506", "Import blocklist: golang.org/x/crypto/md4", NewBlocklistedImportMD4}, + {"G507", "Import blocklist: golang.org/x/crypto/ripemd160", NewBlocklistedImportRIPEMD160}, // memory safety {"G601", "Implicit memory aliasing in RangeStmt", NewImplicitAliasing}, diff --git a/rules/rules_test.go b/rules/rules_test.go index 1d3e3dd..63b263f 100644 --- a/rules/rules_test.go +++ b/rules/rules_test.go @@ -183,6 +183,14 @@ var _ = Describe("gosec rules", func() { runner("G405", testutils.SampleCodeG405b) }) + It("should detect weak crypto algorithms", func() { + runner("G406", testutils.SampleCodeG406) + }) + + It("should detect weak crypto algorithms", func() { + runner("G406", testutils.SampleCodeG406b) + }) + It("should detect blocklisted imports - MD5", func() { runner("G501", testutils.SampleCodeG501) }) @@ -203,6 +211,14 @@ var _ = Describe("gosec rules", func() { runner("G505", testutils.SampleCodeG505) }) + It("should detect blocklisted imports - MD4", func() { + runner("G506", testutils.SampleCodeG506) + }) + + It("should detect blocklisted imports - RIPEMD160", func() { + runner("G507", testutils.SampleCodeG507) + }) + It("should detect implicit aliasing in ForRange", func() { major, minor, _ := gosec.GoVersion() if major <= 1 && minor < 22 { diff --git a/rules/weakdepricatedcryptohash.go b/rules/weakdepricatedcryptohash.go new file mode 100644 index 0000000..6829735 --- /dev/null +++ b/rules/weakdepricatedcryptohash.go @@ -0,0 +1,57 @@ +// (c) Copyright 2024 Mercedes-Benz Tech Innovation GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rules + +import ( + "go/ast" + + "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" +) + +type usesWeakDeprecatedCryptographyHash struct { + issue.MetaData + blocklist map[string][]string +} + +func (r *usesWeakDeprecatedCryptographyHash) ID() string { + return r.MetaData.ID +} + +func (r *usesWeakDeprecatedCryptographyHash) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { + for pkg, funcs := range r.blocklist { + if _, matched := gosec.MatchCallByPackage(n, c, pkg, funcs...); matched { + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil + } + } + return nil, nil +} + +// NewUsesWeakCryptographyHash detects uses of md4.New, ripemd160.New +func NewUsesWeakDeprecatedCryptographyHash(id string, _ gosec.Config) (gosec.Rule, []ast.Node) { + calls := make(map[string][]string) + calls["golang.org/x/crypto/md4"] = []string{"New"} + calls["golang.org/x/crypto/ripemd160"] = []string{"New"} + rule := &usesWeakDeprecatedCryptographyHash{ + blocklist: calls, + MetaData: issue.MetaData{ + ID: id, + Severity: issue.Medium, + Confidence: issue.High, + What: "Use of deprecated weak cryptographic primitive", + }, + } + return rule, []ast.Node{(*ast.CallExpr)(nil)} +} diff --git a/testutils/g406_samples.go b/testutils/g406_samples.go new file mode 100644 index 0000000..911a877 --- /dev/null +++ b/testutils/g406_samples.go @@ -0,0 +1,45 @@ +package testutils + +import "github.com/securego/gosec/v2" + +var ( + // SampleCodeG406 - Use of deprecated weak crypto hash MD4 + SampleCodeG406 = []CodeSample{ + {[]string{` +package main + +import ( + "encoding/hex" + "fmt" + + "golang.org/x/crypto/md4" +) + +func main() { + h := md4.New() + h.Write([]byte("test")) + fmt.Println(hex.EncodeToString(h.Sum(nil))) +} +`}, 1, gosec.NewConfig()}, + } + + // SampleCodeG406b - Use of deprecated weak crypto hash RIPEMD160 + SampleCodeG406b = []CodeSample{ + {[]string{` +package main + +import ( + "encoding/hex" + "fmt" + + "golang.org/x/crypto/ripemd160" +) + +func main() { + h := ripemd160.New() + h.Write([]byte("test")) + fmt.Println(hex.EncodeToString(h.Sum(nil))) +} +`}, 1, gosec.NewConfig()}, + } +) diff --git a/testutils/g506_samples.go b/testutils/g506_samples.go new file mode 100644 index 0000000..676aa55 --- /dev/null +++ b/testutils/g506_samples.go @@ -0,0 +1,23 @@ +package testutils + +import "github.com/securego/gosec/v2" + +// SampleCodeG506 - Blocklisted import MD4 +var SampleCodeG506 = []CodeSample{ + {[]string{` +package main + +import ( + "encoding/hex" + "fmt" + + "golang.org/x/crypto/md4" +) + +func main() { + h := md4.New() + h.Write([]byte("test")) + fmt.Println(hex.EncodeToString(h.Sum(nil))) +} +`}, 1, gosec.NewConfig()}, +} diff --git a/testutils/g507_samples.go b/testutils/g507_samples.go new file mode 100644 index 0000000..6c06e88 --- /dev/null +++ b/testutils/g507_samples.go @@ -0,0 +1,23 @@ +package testutils + +import "github.com/securego/gosec/v2" + +// SampleCodeG507 - Blocklisted import RIPEMD160 +var SampleCodeG507 = []CodeSample{ + {[]string{` +package main + +import ( + "encoding/hex" + "fmt" + + "golang.org/x/crypto/ripemd160" +) + +func main() { + h := ripemd160.New() + h.Write([]byte("test")) + fmt.Println(hex.EncodeToString(h.Sum(nil))) +} +`}, 1, gosec.NewConfig()}, +}