diff --git a/README.md b/README.md index a9384c9..7b8b526 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,7 @@ directory you can supply `./...` as the input argument. - G404: Insecure random number source (rand) - G405: Detect the usage of DES or RC4 - G406: Detect the usage of MD4 or RIPEMD160 +- G407: Detect the usage of hardcoded Initialization Vector(IV)/Nonce - G501: Import blocklist: crypto/md5 - G502: Import blocklist: crypto/des - G503: Import blocklist: crypto/rc4 diff --git a/analyzer_test.go b/analyzer_test.go index 30ca170..301f194 100644 --- a/analyzer_test.go +++ b/analyzer_test.go @@ -187,6 +187,22 @@ var _ = Describe("Analyzer", func() { Expect(controlIssues).Should(HaveLen(sample.Errors)) }) + It("should find errors when nosec is not in use", func() { + sample := testutils.SampleCodeG407[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G407")).RulesInfo()) + + controlPackage := testutils.NewTestPackage() + defer controlPackage.Close() + controlPackage.AddFile("aesOFB.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() @@ -266,6 +282,23 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(BeEmpty()) }) + It("should not report errors when a nosec line comment is present", func() { + sample := testutils.SampleCodeG407[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G407")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\")) //#nosec", 1) + nosecPackage.AddFile("aesOFB.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] @@ -317,6 +350,23 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(BeEmpty()) }) + It("should not report errors when a nosec block comment is present", func() { + sample := testutils.SampleCodeG407[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G407")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\")) /* #nosec */", 1) + nosecPackage.AddFile("aesOFB.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] @@ -371,6 +421,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 hardcoded nonce/IV + sample := testutils.SampleCodeG407[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G407")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\")) //#nosec G407", 1) + nosecPackage.AddFile("aesOFB.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] @@ -468,6 +536,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.SampleCodeG407[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G407")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\")) //#nosec G301", 1) + nosecPackage.AddFile("aesOFB.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] @@ -525,6 +610,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.SampleCodeG407[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G407")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\")) //#nosec G301 G407", 1) + nosecPackage.AddFile("aesOFB.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] @@ -621,6 +725,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 hardcoded IV/nonce + sample := testutils.SampleCodeG407[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, "G407")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\")) //#nosec", 1) + nosecPackage.AddFile("aesOFB.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] @@ -699,6 +826,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 hardcoded nonce/IV + sample := testutils.SampleCodeG407[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, "G407")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\")) // #mynosec", 1) + nosecPackage.AddFile("aesOFB.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] @@ -750,6 +903,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.SampleCodeG407[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G407")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "aesOFB := cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", "//Some description\n//#nosec G407\naesOFB := cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", 1) + nosecPackage.AddFile("aesOFB.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] @@ -801,6 +971,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.SampleCodeG407[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G407")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "aesOFB := cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", "//Some description\n//Another description #nosec G407\naesOFB := cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", 1) + nosecPackage.AddFile("aesOFB.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] @@ -852,6 +1039,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.SampleCodeG407[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G407")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "aesOFB := cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", "//G301\n//#nosec\naesOFB := cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", 1) + nosecPackage.AddFile("aesOFB.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] @@ -903,6 +1107,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.SampleCodeG407[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G407")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "aesOFB := cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", "//#nosec\n//G301\n//#nosec\naesOFB := cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", 1) + nosecPackage.AddFile("aesOFB.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] @@ -972,6 +1193,29 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(BeEmpty()) }) + It("should be possible to use an alternative nosec tag", func() { + // Rule for hardcoded nonce/IV + sample := testutils.SampleCodeG407[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, "G407")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\")) // #falsePositive", 1) + nosecPackage.AddFile("aesOFB.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] @@ -1041,6 +1285,29 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(BeEmpty()) }) + It("should ignore vulnerabilities when the default tag is found", func() { + // Rule for hardcoded nonce/IV + sample := testutils.SampleCodeG407[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, "G407")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\")) //#nosec", 1) + nosecPackage.AddFile("aesOFB.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()) @@ -1404,6 +1671,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.SampleCodeG407[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G407")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\")) //#nosec G407 -- Justification", 1) + nosecPackage.AddFile("aesOFB.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] @@ -1464,6 +1751,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.SampleCodeG407[0] + source := sample.Code[0] + analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G407")).RulesInfo()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\"))", "cipher.NewOFB(block, []byte(\"ILoveMyNonceAlot\")) //#nosec", 1) + nosecPackage.AddFile("aesOFB.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/cwe/data.go b/cwe/data.go index b472626..a9568ba 100644 --- a/cwe/data.go +++ b/cwe/data.go @@ -133,6 +133,11 @@ var idWeaknesses = map[string]*Weakness{ Description: "The software contains hard-coded credentials, such as a password or cryptographic key, which it uses for its own inbound authentication, outbound communication to external components, or encryption of internal data.", Name: "Use of Hard-coded Credentials", }, + "1204": { + ID: "1204", + Description: "The product uses a cryptographic primitive that uses an Initialization Vector (IV), but the product does not generate IVs that are sufficiently unpredictable or unique according to the expected cryptographic requirements for that primitive.", + Name: "Generation of Weak Initialization Vector (IV)", + }, } // Get Retrieves a CWE weakness by it's id diff --git a/issue/issue.go b/issue/issue.go index aad0934..6227db6 100644 --- a/issue/issue.go +++ b/issue/issue.go @@ -84,6 +84,7 @@ var ruleToCWE = map[string]string{ "G404": "338", "G405": "327", "G406": "328", + "G407": "1204", "G501": "327", "G502": "327", "G503": "327", diff --git a/report/formatter_test.go b/report/formatter_test.go index 03d086c..ad22ee0 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", "G406", "G501", "G502", - "G503", "G504", "G505", "G506", "G507", "G601", + "G402", "G403", "G404", "G405", "G406", "G407", "G501", + "G502", "G503", "G504", "G505", "G506", "G507", "G601", } It("csv formatted report should contain the CWE mapping", func() { diff --git a/rules/hardcodedIV.go b/rules/hardcodedIV.go new file mode 100644 index 0000000..2c7f5ab --- /dev/null +++ b/rules/hardcodedIV.go @@ -0,0 +1,132 @@ +package rules + +import ( + "go/ast" + + "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" +) + +type usesHardcodedIV struct { + issue.MetaData + trackedFunctions map[string][]int +} + +func (r *usesHardcodedIV) ID() string { + return r.MetaData.ID +} + +// The code is a little bit spaghetti and there are things that repeat +// Can be improved +func (r *usesHardcodedIV) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { + // cast n to a call expression, we can do that safely, because this match method gets only called when CallExpr node is found + funcCall := n.(*ast.CallExpr) + + // cast to a function call from an object and get the function part; example: a.doSomething() + funcSelector, exists := funcCall.Fun.(*ast.SelectorExpr) + if exists { + //Iterate trough the wanted functions + for functionName, functionNumArgsAndNoncePosArr := range r.trackedFunctions { + // Check if the call is actually made from an object + if _, hasX := funcSelector.X.(*ast.Ident); hasX { + + // Check if the function name matches with the one we look for, and if the function accepts an exact number of arguments(Function signature) + if funcSelector.Sel.Name == functionName && len(funcCall.Args) == functionNumArgsAndNoncePosArr[0] { + + // Check the type of the passed argument to the function + switch funcCall.Args[functionNumArgsAndNoncePosArr[1]].(type) { + + case *ast.CompositeLit: + // Check if the argument is static array + if _, isArray := funcCall.Args[functionNumArgsAndNoncePosArr[1]].(*ast.CompositeLit).Type.(*ast.ArrayType); isArray { + return c.NewIssue(n, r.ID(), r.What+" by passing hardcoded byte array", r.Severity, r.Confidence), nil + } + + case *ast.CallExpr: + + // Check if it's a function call, because []byte() is a function call, and also check if the number of arguments to this call is only 1 + switch funcCall.Args[functionNumArgsAndNoncePosArr[1]].(*ast.CallExpr).Fun.(type) { + case *ast.ArrayType: + return c.NewIssue(n, r.ID(), r.What+" by converting static string to a byte array", r.Severity, r.Confidence), nil + + // Check if it's an anonymous function + case *ast.FuncLit: + functionCalled, _ := funcCall.Args[functionNumArgsAndNoncePosArr[1]].(*ast.CallExpr).Fun.(*ast.FuncLit) + + // Check the type of the last statement in the anonymous function + switch functionCalled.Body.List[len(functionCalled.Body.List)-1].(type) { + + case *ast.IfStmt: + + ifStatementContent := functionCalled.Body.List[len(functionCalled.Body.List)-1].(*ast.IfStmt).Body.List + + // check if the if statement has return statement + if retStatement, isReturn := ifStatementContent[len(ifStatementContent)-1].(*ast.ReturnStmt); isReturn { + argInNestedFunc := retStatement.Results[0] + + // check the type of the returned value + switch argInNestedFunc.(type) { + case *ast.CompositeLit: + // Check if the argument is static array + if _, isArray := argInNestedFunc.(*ast.CompositeLit).Type.(*ast.ArrayType); isArray { + return c.NewIssue(n, r.ID(), r.What+" by passing hardcoded byte array in a function call", r.Severity, r.Confidence), nil + } + + case *ast.CallExpr: + if _, ok := argInNestedFunc.(*ast.CallExpr).Fun.(*ast.ArrayType); ok { + return c.NewIssue(n, r.ID(), r.What+" by converting static string to a byte array in a function call", r.Severity, r.Confidence), nil + } + } + } + case *ast.ReturnStmt: + + argInNestedFunc := functionCalled.Body.List[len(functionCalled.Body.List)-1].(*ast.ReturnStmt).Results[0] + switch argInNestedFunc.(type) { + case *ast.CompositeLit: + // Check if the argument is static array + if _, isArray := argInNestedFunc.(*ast.CompositeLit).Type.(*ast.ArrayType); isArray { + return c.NewIssue(n, r.ID(), r.What+" by passing hardcoded byte array in a function call", r.Severity, r.Confidence), nil + } + + case *ast.CallExpr: + if _, ok := argInNestedFunc.(*ast.CallExpr).Fun.(*ast.ArrayType); ok { + return c.NewIssue(n, r.ID(), r.What+" by converting static string to a byte array in a function call", r.Severity, r.Confidence), nil + } + } + } + } + } + } + } + } + } + // loop through the functions we are checking + + return nil, nil +} + +func NewUsesHardCodedIV(id string, _ gosec.Config) (gosec.Rule, []ast.Node) { + calls := make(map[string][]int) + // Holds the function name as key, the number of arguments that the function accepts, and at which index of those accepted arguments is the nonce/IV + // Example "Test" 3, 1 -- means the function "Test" which accepts 3 arguments, and has the nonce arg as second argument + + calls["Seal"] = []int{4, 1} + calls["Open"] = []int{4, 1} + calls["NewCBCDecrypter"] = []int{2, 1} // + calls["NewCBCEncrypter"] = []int{2, 1} // + calls["NewCFBDecrypter"] = []int{2, 1} + calls["NewCFBEncrypter"] = []int{2, 1} + calls["NewCTR"] = []int{2, 1} // + calls["NewOFB"] = []int{2, 1} // + + rule := &usesHardcodedIV{ + trackedFunctions: calls, + MetaData: issue.MetaData{ + ID: id, + Severity: issue.High, + Confidence: issue.Medium, + What: "Use of hardcoded IV/nonce for encryption", + }, + } + return rule, []ast.Node{(*ast.CallExpr)(nil)} +} diff --git a/rules/rulelist.go b/rules/rulelist.go index 13f29f7..889413c 100644 --- a/rules/rulelist.go +++ b/rules/rulelist.go @@ -100,6 +100,7 @@ func Generate(trackSuppressions bool, filters ...RuleFilter) RuleList { {"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}, + {"G407", "Detect the usage of hardcoded Initialization Vector(IV)/Nonce", NewUsesHardCodedIV}, // blocklist {"G501", "Import blocklist: crypto/md5", NewBlocklistedImportMD5}, diff --git a/rules/rules_test.go b/rules/rules_test.go index 9a7d65a..783ab87 100644 --- a/rules/rules_test.go +++ b/rules/rules_test.go @@ -187,6 +187,66 @@ var _ = Describe("gosec rules", func() { runner("G406", testutils.SampleCodeG406b) }) + It("should detect hardcoded nonce/IV", func() { + runner("G407", testutils.SampleCodeG407) + }) + + It("should detect hardcoded nonce/IV", func() { + runner("G407", testutils.SampleCodeG407b) + }) + + It("should detect hardcoded nonce/IV", func() { + runner("G407", testutils.SampleCodeG407c) + }) + + It("should detect hardcoded nonce/IV", func() { + runner("G407", testutils.SampleCodeG407d) + }) + + It("should detect hardcoded nonce/IV", func() { + runner("G407", testutils.SampleCodeG407e) + }) + + It("should detect hardcoded nonce/IV", func() { + runner("G407", testutils.SampleCodeG407f) + }) + + It("should detect hardcoded nonce/IV", func() { + runner("G407", testutils.SampleCodeG407g) + }) + + It("should detect hardcoded nonce/IV", func() { + runner("G407", testutils.SampleCodeG407h) + }) + + It("should detect hardcoded nonce/IV", func() { + runner("G407", testutils.SampleCodeG407i) + }) + + It("should detect hardcoded nonce/IV", func() { + runner("G407", testutils.SampleCodeG407j) + }) + + It("should detect hardcoded nonce/IV", func() { + runner("G407", testutils.SampleCodeG407k) + }) + + It("should detect hardcoded nonce/IV", func() { + runner("G407", testutils.SampleCodeG407l) + }) + + It("should detect hardcoded nonce/IV", func() { + runner("G407", testutils.SampleCodeG407m) + }) + + It("should detect hardcoded nonce/IV", func() { + runner("G407", testutils.SampleCodeG407n) + }) + + It("should detect hardcoded nonce/IV", func() { + runner("G407", testutils.SampleCodeG407o) + }) + It("should detect blocklisted imports - MD5", func() { runner("G501", testutils.SampleCodeG501) }) diff --git a/testutils/g407_samples.go b/testutils/g407_samples.go new file mode 100644 index 0000000..675aa06 --- /dev/null +++ b/testutils/g407_samples.go @@ -0,0 +1,380 @@ +package testutils + +import "github.com/securego/gosec/v2" + +var ( + // SampleCodeG407 - Use of hardcoded nonce/IV + SampleCodeG407 = []CodeSample{ + {[]string{`package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func main() { + + block, _ := aes.NewCipher([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesOFB := cipher.NewOFB(block, []byte("ILoveMyNonceAlot")) + var output = make([]byte, 16) + aesOFB.XORKeyStream(output, []byte("Very Cool thing!")) + fmt.Println(string(output)) + +}`}, 1, gosec.NewConfig()}, + } + + // SampleCodeG407b - Use of hardcoded nonce/IV + SampleCodeG407b = []CodeSample{ + {[]string{`package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func main() { + + block, _ := aes.NewCipher([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesOFB := cipher.NewOFB(block, []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + var output = make([]byte, 16) + aesOFB.XORKeyStream(output, []byte("Very Cool thing!")) + fmt.Println(string(output)) + +}`}, 1, gosec.NewConfig()}, + } + + // SampleCodeG407c - Use of hardcoded nonce/IV + SampleCodeG407c = []CodeSample{ + {[]string{`package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func main() { + + block, _ := aes.NewCipher([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesCTR := cipher.NewCTR(block, []byte("ILoveMyNonceAlot")) + var output = make([]byte, 16) + aesCTR.XORKeyStream(output, []byte("Very Cool thing!")) + fmt.Println(string(output)) + +}`}, 1, gosec.NewConfig()}, + } + + // SampleCodeG407d - Use of hardcoded nonce/IV + SampleCodeG407d = []CodeSample{ + {[]string{`package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func main() { + + block, _ := aes.NewCipher([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesCTR := cipher.NewCTR(block, []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + var output = make([]byte, 16) + aesCTR.XORKeyStream(output, []byte("Very Cool thing!")) + fmt.Println(string(output)) + +} +`}, 1, gosec.NewConfig()}, + } + + // SampleCodeG407e - Use of hardcoded nonce/IV + SampleCodeG407e = []CodeSample{ + {[]string{`package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func main() { + + block, _ := aes.NewCipher([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesGCM, _ := cipher.NewGCM(block) + + cipherText := aesGCM.Seal(nil, []byte("ILoveMyNonce"), []byte("My secret message"), nil) + fmt.Println(string(cipherText)) + cipherText, _ = aesGCM.Open(nil, []byte("ILoveMyNonce"), cipherText, nil) + fmt.Println(string(cipherText)) +} +`}, 2, gosec.NewConfig()}, + } + + // SampleCodeG407f - Use of hardcoded nonce/IV + SampleCodeG407f = []CodeSample{ + {[]string{`package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func main() { + + block, _ := aes.NewCipher([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesGCM, _ := cipher.NewGCM(block) + cipherText := aesGCM.Seal(nil, []byte{}, []byte("My secret message"), nil) + fmt.Println(string(cipherText)) + + cipherText, _ = aesGCM.Open(nil, []byte{}, cipherText, nil) + fmt.Println(string(cipherText)) +} +`}, 2, gosec.NewConfig()}, + } + + // SampleCodeG407g - Use of hardcoded nonce/IV + SampleCodeG407g = []CodeSample{ + {[]string{`package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func main() { + + block, _ := aes.NewCipher([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesGCM, _ := cipher.NewGCM(block) + + cipherText := aesGCM.Seal(nil, []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, []byte("My secret message"), nil) + fmt.Println(string(cipherText)) + + cipherText, _ = aesGCM.Open(nil, []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, cipherText, nil) + fmt.Println(string(cipherText)) +} +`}, 2, gosec.NewConfig()}, + } + + // SampleCodeG407h - Use of hardcoded nonce/IV + SampleCodeG407h = []CodeSample{ + {[]string{`package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func main() { + + block, _ := aes.NewCipher( []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesGCM, _ := cipher.NewGCM(block) + + cipherText := aesGCM.Seal(nil, func() []byte { + if true { + return []byte("ILoveMyNonce") + } else { + return []byte("IDont'Love..") + } + }(), []byte("My secret message"), nil) + fmt.Println(string(cipherText)) + + cipherText, _ = aesGCM.Open(nil, func() []byte { + if true { + return []byte("ILoveMyNonce") + } else { + return []byte("IDont'Love..") + } + }(), cipherText, nil) + + fmt.Println(string(cipherText)) +} +`}, 2, gosec.NewConfig()}, + } + + // SampleCodeG407I - Use of hardcoded nonce/IV + SampleCodeG407i = []CodeSample{ + {[]string{`package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func main() { + + block, _ := aes.NewCipher([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesGCM, _ := cipher.NewGCM(block) + + cipherText := aesGCM.Seal(nil, func() []byte { + if true { + return []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + } else { + return []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + } + }(), []byte("My secret message"), nil) + fmt.Println(string(cipherText)) + + cipherText, _ = aesGCM.Open(nil, func() []byte { + if true { + return []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + } else { + return []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + } + }(), cipherText, nil) + fmt.Println(string(cipherText)) +} +`}, 2, gosec.NewConfig()}, + } + + // SampleCodeG407j - Use of hardcoded nonce/IV + SampleCodeG407j = []CodeSample{ + {[]string{`package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func main() { + + block, _ := aes.NewCipher([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesGCM, _ := cipher.NewGCM(block) + cipheredText := aesGCM.Seal(nil, func() []byte { return []byte("ILoveMyNonce") }(), []byte("My secret message"), nil) + fmt.Println(string(cipheredText)) + cipheredText, _ = aesGCM.Open(nil, func() []byte { return []byte("ILoveMyNonce") }(), cipheredText, nil) + fmt.Println(string(cipheredText)) + +} +`}, 2, gosec.NewConfig()}, + } + + // SampleCodeG407k - Use of hardcoded nonce/IV + SampleCodeG407k = []CodeSample{ + {[]string{`package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func main() { + + block, _ := aes.NewCipher([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesGCM, _ := cipher.NewGCM(block) + cipheredText := aesGCM.Seal(nil, func() []byte { return []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} }(), []byte("My secret message"), nil) + fmt.Println(string(cipheredText)) + cipheredText, _ = aesGCM.Open(nil, func() []byte { return []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} }(), cipheredText, nil) + fmt.Println(string(cipheredText)) + +} +`}, 2, gosec.NewConfig()}, + } + + // SampleCodeG407l - Use of hardcoded nonce/IV + SampleCodeG407l = []CodeSample{ + {[]string{`package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func main() { + + block, _ := aes.NewCipher([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesCFB := cipher.NewCFBEncrypter(block, []byte("ILoveMyNonceAlot")) + var output = make([]byte, 16) + aesCFB.XORKeyStream(output, []byte("Very Cool thing!")) + fmt.Println(string(output)) + aesCFB = cipher.NewCFBDecrypter(block, []byte("ILoveMyNonceAlot")) + aesCFB.XORKeyStream(output, output) + fmt.Println(string(output)) + +}`}, 2, gosec.NewConfig()}, + } + + // SampleCodeG407m - Use of hardcoded nonce/IV + SampleCodeG407m = []CodeSample{ + {[]string{`package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func main() { + + block, _ := aes.NewCipher([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesCFB := cipher.NewCFBEncrypter(block, []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + var output = make([]byte, 16) + aesCFB.XORKeyStream(output, []byte("Very Cool thing!")) + fmt.Println(string(output)) + aesCFB = cipher.NewCFBDecrypter(block, []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesCFB.XORKeyStream(output, output) + fmt.Println(string(output)) + +}`}, 2, gosec.NewConfig()}, + } + + // SampleCodeG407n - Use of hardcoded nonce/IV + SampleCodeG407n = []CodeSample{ + {[]string{`package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func main() { + + block, _ := aes.NewCipher([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesCBC := cipher.NewCBCEncrypter(block, []byte("ILoveMyNonceAlot")) + + var output = make([]byte, 16) + aesCBC.CryptBlocks(output, []byte("Very Cool thing!")) + fmt.Println(string(output)) + + aesCBC = cipher.NewCBCDecrypter(block, []byte("ILoveMyNonceAlot")) + aesCBC.CryptBlocks(output, output) + fmt.Println(string(output)) + +}`}, 2, gosec.NewConfig()}, + } + + // SampleCodeG407o - Use of hardcoded nonce/IV + SampleCodeG407o = []CodeSample{ + {[]string{`package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +func main() { + + block, _ := aes.NewCipher([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesCBC := cipher.NewCBCEncrypter(block, []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + + var output = make([]byte, 16) + aesCBC.CryptBlocks(output, []byte("Very Cool thing!")) + fmt.Println(string(output)) + + aesCBC = cipher.NewCBCDecrypter(block, []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) + aesCBC.CryptBlocks(output, output) + fmt.Println(string(output)) + +} +`}, 2, gosec.NewConfig()}, + } +)