Added new rule G407(hardcoded IV/nonce)

The rule is supposed to detect for the usage of hardcoded or static nonce/Iv in many encryption algorithms:

* The different modes of AES (mainly tested here)
* It should be able to work with ascon

Currently the rules doesn't check when constant variables are used.

TODO: Improve the rule, to detected for constatant variable usage
This commit is contained in:
Dimitar Banchev 2024-08-14 17:07:59 +02:00 committed by Cosmin Cojocar
parent 4ae73c8ba3
commit 0eb8143c23
9 changed files with 889 additions and 2 deletions

View file

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

View file

@ -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]

View file

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

View file

@ -84,6 +84,7 @@ var ruleToCWE = map[string]string{
"G404": "338",
"G405": "327",
"G406": "328",
"G407": "1204",
"G501": "327",
"G502": "327",
"G503": "327",

View file

@ -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() {

132
rules/hardcodedIV.go Normal file
View file

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

View file

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

View file

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

380
testutils/g407_samples.go Normal file
View file

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