Skip to content

Commit 0eb8143

Browse files
Dimitar Banchevccojocar
authored andcommitted
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
1 parent 4ae73c8 commit 0eb8143

File tree

9 files changed

+889
-2
lines changed

9 files changed

+889
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ directory you can supply `./...` as the input argument.
157157
- G404: Insecure random number source (rand)
158158
- G405: Detect the usage of DES or RC4
159159
- G406: Detect the usage of MD4 or RIPEMD160
160+
- G407: Detect the usage of hardcoded Initialization Vector(IV)/Nonce
160161
- G501: Import blocklist: crypto/md5
161162
- G502: Import blocklist: crypto/des
162163
- G503: Import blocklist: crypto/rc4

analyzer_test.go

Lines changed: 307 additions & 0 deletions
Large diffs are not rendered by default.

cwe/data.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ var idWeaknesses = map[string]*Weakness{
133133
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.",
134134
Name: "Use of Hard-coded Credentials",
135135
},
136+
"1204": {
137+
ID: "1204",
138+
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.",
139+
Name: "Generation of Weak Initialization Vector (IV)",
140+
},
136141
}
137142

138143
// Get Retrieves a CWE weakness by it's id

issue/issue.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ var ruleToCWE = map[string]string{
8484
"G404": "338",
8585
"G405": "327",
8686
"G406": "328",
87+
"G407": "1204",
8788
"G501": "327",
8889
"G502": "327",
8990
"G503": "327",

report/formatter_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,8 @@ var _ = Describe("Formatter", func() {
281281
"G101", "G102", "G103", "G104", "G106", "G107", "G109",
282282
"G110", "G111", "G112", "G113", "G201", "G202", "G203",
283283
"G204", "G301", "G302", "G303", "G304", "G305", "G401",
284-
"G402", "G403", "G404", "G405", "G406", "G501", "G502",
285-
"G503", "G504", "G505", "G506", "G507", "G601",
284+
"G402", "G403", "G404", "G405", "G406", "G407", "G501",
285+
"G502", "G503", "G504", "G505", "G506", "G507", "G601",
286286
}
287287

288288
It("csv formatted report should contain the CWE mapping", func() {

rules/hardcodedIV.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package rules
2+
3+
import (
4+
"go/ast"
5+
6+
"github.com/securego/gosec/v2"
7+
"github.com/securego/gosec/v2/issue"
8+
)
9+
10+
type usesHardcodedIV struct {
11+
issue.MetaData
12+
trackedFunctions map[string][]int
13+
}
14+
15+
func (r *usesHardcodedIV) ID() string {
16+
return r.MetaData.ID
17+
}
18+
19+
// The code is a little bit spaghetti and there are things that repeat
20+
// Can be improved
21+
func (r *usesHardcodedIV) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) {
22+
// cast n to a call expression, we can do that safely, because this match method gets only called when CallExpr node is found
23+
funcCall := n.(*ast.CallExpr)
24+
25+
// cast to a function call from an object and get the function part; example: a.doSomething()
26+
funcSelector, exists := funcCall.Fun.(*ast.SelectorExpr)
27+
if exists {
28+
//Iterate trough the wanted functions
29+
for functionName, functionNumArgsAndNoncePosArr := range r.trackedFunctions {
30+
// Check if the call is actually made from an object
31+
if _, hasX := funcSelector.X.(*ast.Ident); hasX {
32+
33+
// Check if the function name matches with the one we look for, and if the function accepts an exact number of arguments(Function signature)
34+
if funcSelector.Sel.Name == functionName && len(funcCall.Args) == functionNumArgsAndNoncePosArr[0] {
35+
36+
// Check the type of the passed argument to the function
37+
switch funcCall.Args[functionNumArgsAndNoncePosArr[1]].(type) {
38+
39+
case *ast.CompositeLit:
40+
// Check if the argument is static array
41+
if _, isArray := funcCall.Args[functionNumArgsAndNoncePosArr[1]].(*ast.CompositeLit).Type.(*ast.ArrayType); isArray {
42+
return c.NewIssue(n, r.ID(), r.What+" by passing hardcoded byte array", r.Severity, r.Confidence), nil
43+
}
44+
45+
case *ast.CallExpr:
46+
47+
// 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
48+
switch funcCall.Args[functionNumArgsAndNoncePosArr[1]].(*ast.CallExpr).Fun.(type) {
49+
case *ast.ArrayType:
50+
return c.NewIssue(n, r.ID(), r.What+" by converting static string to a byte array", r.Severity, r.Confidence), nil
51+
52+
// Check if it's an anonymous function
53+
case *ast.FuncLit:
54+
functionCalled, _ := funcCall.Args[functionNumArgsAndNoncePosArr[1]].(*ast.CallExpr).Fun.(*ast.FuncLit)
55+
56+
// Check the type of the last statement in the anonymous function
57+
switch functionCalled.Body.List[len(functionCalled.Body.List)-1].(type) {
58+
59+
case *ast.IfStmt:
60+
61+
ifStatementContent := functionCalled.Body.List[len(functionCalled.Body.List)-1].(*ast.IfStmt).Body.List
62+
63+
// check if the if statement has return statement
64+
if retStatement, isReturn := ifStatementContent[len(ifStatementContent)-1].(*ast.ReturnStmt); isReturn {
65+
argInNestedFunc := retStatement.Results[0]
66+
67+
// check the type of the returned value
68+
switch argInNestedFunc.(type) {
69+
case *ast.CompositeLit:
70+
// Check if the argument is static array
71+
if _, isArray := argInNestedFunc.(*ast.CompositeLit).Type.(*ast.ArrayType); isArray {
72+
return c.NewIssue(n, r.ID(), r.What+" by passing hardcoded byte array in a function call", r.Severity, r.Confidence), nil
73+
}
74+
75+
case *ast.CallExpr:
76+
if _, ok := argInNestedFunc.(*ast.CallExpr).Fun.(*ast.ArrayType); ok {
77+
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
78+
}
79+
}
80+
}
81+
case *ast.ReturnStmt:
82+
83+
argInNestedFunc := functionCalled.Body.List[len(functionCalled.Body.List)-1].(*ast.ReturnStmt).Results[0]
84+
switch argInNestedFunc.(type) {
85+
case *ast.CompositeLit:
86+
// Check if the argument is static array
87+
if _, isArray := argInNestedFunc.(*ast.CompositeLit).Type.(*ast.ArrayType); isArray {
88+
return c.NewIssue(n, r.ID(), r.What+" by passing hardcoded byte array in a function call", r.Severity, r.Confidence), nil
89+
}
90+
91+
case *ast.CallExpr:
92+
if _, ok := argInNestedFunc.(*ast.CallExpr).Fun.(*ast.ArrayType); ok {
93+
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
94+
}
95+
}
96+
}
97+
}
98+
}
99+
}
100+
}
101+
}
102+
}
103+
// loop through the functions we are checking
104+
105+
return nil, nil
106+
}
107+
108+
func NewUsesHardCodedIV(id string, _ gosec.Config) (gosec.Rule, []ast.Node) {
109+
calls := make(map[string][]int)
110+
// 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
111+
// Example "Test" 3, 1 -- means the function "Test" which accepts 3 arguments, and has the nonce arg as second argument
112+
113+
calls["Seal"] = []int{4, 1}
114+
calls["Open"] = []int{4, 1}
115+
calls["NewCBCDecrypter"] = []int{2, 1} //
116+
calls["NewCBCEncrypter"] = []int{2, 1} //
117+
calls["NewCFBDecrypter"] = []int{2, 1}
118+
calls["NewCFBEncrypter"] = []int{2, 1}
119+
calls["NewCTR"] = []int{2, 1} //
120+
calls["NewOFB"] = []int{2, 1} //
121+
122+
rule := &usesHardcodedIV{
123+
trackedFunctions: calls,
124+
MetaData: issue.MetaData{
125+
ID: id,
126+
Severity: issue.High,
127+
Confidence: issue.Medium,
128+
What: "Use of hardcoded IV/nonce for encryption",
129+
},
130+
}
131+
return rule, []ast.Node{(*ast.CallExpr)(nil)}
132+
}

rules/rulelist.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ func Generate(trackSuppressions bool, filters ...RuleFilter) RuleList {
100100
{"G404", "Insecure random number source (rand)", NewWeakRandCheck},
101101
{"G405", "Detect the usage of DES or RC4", NewUsesWeakCryptographyEncryption},
102102
{"G406", "Detect the usage of deprecated MD4 or RIPEMD160", NewUsesWeakDeprecatedCryptographyHash},
103+
{"G407", "Detect the usage of hardcoded Initialization Vector(IV)/Nonce", NewUsesHardCodedIV},
103104

104105
// blocklist
105106
{"G501", "Import blocklist: crypto/md5", NewBlocklistedImportMD5},

rules/rules_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,66 @@ var _ = Describe("gosec rules", func() {
187187
runner("G406", testutils.SampleCodeG406b)
188188
})
189189

190+
It("should detect hardcoded nonce/IV", func() {
191+
runner("G407", testutils.SampleCodeG407)
192+
})
193+
194+
It("should detect hardcoded nonce/IV", func() {
195+
runner("G407", testutils.SampleCodeG407b)
196+
})
197+
198+
It("should detect hardcoded nonce/IV", func() {
199+
runner("G407", testutils.SampleCodeG407c)
200+
})
201+
202+
It("should detect hardcoded nonce/IV", func() {
203+
runner("G407", testutils.SampleCodeG407d)
204+
})
205+
206+
It("should detect hardcoded nonce/IV", func() {
207+
runner("G407", testutils.SampleCodeG407e)
208+
})
209+
210+
It("should detect hardcoded nonce/IV", func() {
211+
runner("G407", testutils.SampleCodeG407f)
212+
})
213+
214+
It("should detect hardcoded nonce/IV", func() {
215+
runner("G407", testutils.SampleCodeG407g)
216+
})
217+
218+
It("should detect hardcoded nonce/IV", func() {
219+
runner("G407", testutils.SampleCodeG407h)
220+
})
221+
222+
It("should detect hardcoded nonce/IV", func() {
223+
runner("G407", testutils.SampleCodeG407i)
224+
})
225+
226+
It("should detect hardcoded nonce/IV", func() {
227+
runner("G407", testutils.SampleCodeG407j)
228+
})
229+
230+
It("should detect hardcoded nonce/IV", func() {
231+
runner("G407", testutils.SampleCodeG407k)
232+
})
233+
234+
It("should detect hardcoded nonce/IV", func() {
235+
runner("G407", testutils.SampleCodeG407l)
236+
})
237+
238+
It("should detect hardcoded nonce/IV", func() {
239+
runner("G407", testutils.SampleCodeG407m)
240+
})
241+
242+
It("should detect hardcoded nonce/IV", func() {
243+
runner("G407", testutils.SampleCodeG407n)
244+
})
245+
246+
It("should detect hardcoded nonce/IV", func() {
247+
runner("G407", testutils.SampleCodeG407o)
248+
})
249+
190250
It("should detect blocklisted imports - MD5", func() {
191251
runner("G501", testutils.SampleCodeG501)
192252
})

0 commit comments

Comments
 (0)