Skip to content

Commit ae2bc1b

Browse files
author
Porcupiney Hairs
committed
Include suggested changes from review.
1 parent e0f74a5 commit ae2bc1b

File tree

2 files changed

+175
-36
lines changed

2 files changed

+175
-36
lines changed

go/ql/src/experimental/CWE-321/HardcodedKeysLib.qll

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ module HardcodedKeys {
4646
/**
4747
* A hardcoded string literal as a source for JWT token signing vulnerabilities.
4848
*/
49-
class HardcodedStringSource extends Source {
49+
private class HardcodedStringSource extends Source {
5050
HardcodedStringSource() {
5151
this.asExpr() instanceof StringLit and
5252
not (isTestCode(this.asExpr()) or isDemoCode(this.asExpr()))
@@ -166,7 +166,7 @@ module HardcodedKeys {
166166
}
167167

168168
/** Mark an empty string returned with an error as a sanitizer */
169-
class EmptyErrorSanitizer extends Sanitizer {
169+
private class EmptyErrorSanitizer extends Sanitizer {
170170
EmptyErrorSanitizer() {
171171
exists(ReturnStmt r, DataFlow::CallNode c |
172172
c.getTarget().hasQualifiedName("errors", "New") and
@@ -178,7 +178,7 @@ module HardcodedKeys {
178178
}
179179

180180
/** Mark any formatting string call as a sanitizer */
181-
class FormattingSanitizer extends Sanitizer {
181+
private class FormattingSanitizer extends Sanitizer {
182182
FormattingSanitizer() { exists(Formatting::StringFormatCall s | s.getAResult() = this) }
183183
}
184184

@@ -188,19 +188,6 @@ module HardcodedKeys {
188188
*/
189189
private class RandSliceSanitizer extends Sanitizer {
190190
RandSliceSanitizer() {
191-
exists(DataFlow::CallNode randint, string name, DataFlow::ElementReadNode r |
192-
(
193-
randint.getTarget().hasQualifiedName("math/rand", name) or
194-
randint.getTarget().(Method).hasQualifiedName("math/rand", "Rand", name)
195-
) and
196-
name =
197-
[
198-
"ExpFloat64", "Float32", "Float64", "Int", "Int31", "Int31n", "Int63", "Int63n", "Intn",
199-
"NormFloat64", "Uint32", "Uint64"
200-
] and
201-
r.reads(this, randint.getAResult().getASuccessor*())
202-
)
203-
or
204191
// Sanitize flows like this:
205192
// func GenerateCryptoString(n int) (string, error) {
206193
// const chars = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
@@ -215,12 +202,20 @@ module HardcodedKeys {
215202
// return string(ret), nil
216203
// }
217204
exists(
218-
DataFlow::CallNode randint, DataFlow::MethodCallNode bigint, DataFlow::ElementReadNode r
205+
DataFlow::CallNode randint, string name, DataFlow::ElementReadNode r, DataFlow::Node index
219206
|
220-
randint.getTarget().hasQualifiedName("crypto/rand", "Int") and
221-
bigint.getTarget().hasQualifiedName("math/big", "Int", "Int64") and
222-
bigint.getReceiver() = randint.getResult(0).getASuccessor*() and
223-
r.reads(this, bigint.getAResult().getASuccessor*())
207+
(
208+
randint.getTarget().hasQualifiedName("math/rand", name) or
209+
randint.getTarget().(Method).hasQualifiedName("math/rand", "Rand", name) or
210+
randint.getTarget().hasQualifiedName("crypto/rand", "Int")
211+
) and
212+
name =
213+
[
214+
"ExpFloat64", "Float32", "Float64", "Int", "Int31", "Int31n", "Int63", "Int63n", "Intn",
215+
"NormFloat64", "Uint32", "Uint64"
216+
] and
217+
TaintTracking::localTaint(randint.getAResult(), index) and
218+
r.reads(this, index)
224219
)
225220
or
226221
// Sanitize flows like :
@@ -232,29 +227,49 @@ module HardcodedKeys {
232227
// }
233228
// return string(bytes)
234229
// }
235-
exists(DataFlow::CallNode randread, DataFlow::Node rand, DataFlow::ElementReadNode r |
230+
exists(DataFlow::CallNode randread, DataFlow::Node rand |
236231
randread.getTarget().hasQualifiedName("crypto/rand", "Read") and
237232
TaintTracking::localTaint(any(DataFlow::PostUpdateNode pun |
238233
pun.getPreUpdateNode() = randread.getArgument(0)
239234
), rand) and
240-
(
241-
// Flow through a ModExpr if any of the operands are tainted.
242-
// For ex, in the case shown above,
243-
// `bytes[i] = characters[x%byte(len(characters))]`
244-
// given x is cryptographically secure random number,
245-
// we can assume that `bytes` is random and cryptographically secure.
246-
exists(ModExpr e | e.getAnOperand() = rand.asExpr() |
247-
r.reads(this, e.getGlobalValueNumber().getANode())
248-
)
249-
or
250-
// This is an alternative case where the code uses `x` directly instead
251-
// `bytes[i] = characters[x]`
252-
r.reads(this.getAPredecessor*(), rand)
253-
)
235+
this.(DataFlow::ElementReadNode).reads(_, rand)
254236
)
255237
}
256238
}
257239

240+
/**
241+
* Models flow from a call to `Int64` if the receiver is tainted
242+
*/
243+
private class BigIntFlow extends TaintTracking::FunctionModel {
244+
BigIntFlow() { this.(Method).hasQualifiedName("math/big", "Int", "Int64") }
245+
246+
override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) {
247+
inp.isReceiver() and
248+
outp.isResult(0)
249+
}
250+
}
251+
252+
/*
253+
* This is code is used to model taint flow through a binary operation such as a
254+
* modulo `%` operation or an addition `+` operation
255+
*/
256+
257+
private class BinExpAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
258+
// This is required to model the sanitizers for the `HardcodedKeys` query.
259+
// This is required to correctly detect a sanitizer such as the one shown below.
260+
// func GenerateRandomString(size int) string {
261+
// var bytes = make([]byte, size)
262+
// rand.Read(bytes)
263+
// for i, x := range bytes {
264+
// bytes[i] = characters[x%byte(len(characters))]
265+
// }
266+
// return string(bytes)
267+
// }
268+
override predicate step(DataFlow::Node prev, DataFlow::Node succ) {
269+
exists(BinaryExpr b | b.getAnOperand() = prev.asExpr() | succ.asExpr() = b)
270+
}
271+
}
272+
258273
/**
259274
* A configuration depicting taint flow for studying JWT token signing vulnerabilities.
260275
*/
@@ -267,6 +282,8 @@ module HardcodedKeys {
267282

268283
override predicate isSanitizer(DataFlow::Node sanitizer) { sanitizer instanceof Sanitizer }
269284

285+
// override predicate isAdditionalTaintStep(DataFlow::Node prev, DataFlow::Node succ) {
286+
// }
270287
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
271288
guard instanceof SanitizerGuard
272289
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package main
2+
3+
//go:generate depstubber -vendor github.com/cristalhq/jwt/v3 Signer NewSignerHS,HS256
4+
5+
import (
6+
crand "crypto/rand"
7+
"errors"
8+
"fmt"
9+
"math/big"
10+
"math/rand"
11+
"time"
12+
13+
cristal "github.com/cristalhq/jwt/v3"
14+
)
15+
16+
func cristalhq() (interface{}, error) {
17+
key := []byte(`key`)
18+
return cristal.NewSignerHS(cristal.HS256, key) // BAD
19+
}
20+
21+
func GenerateRandomString(size int) string {
22+
const characters = `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`
23+
var bytes = make([]byte, size)
24+
crand.Read(bytes)
25+
for i, x := range bytes {
26+
bytes[i] = characters[x%byte(len(characters))]
27+
}
28+
return string(bytes)
29+
}
30+
31+
func GenerateCryptoString2(n int) (string, error) {
32+
const chars = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
33+
ret := make([]byte, n)
34+
for i := range ret {
35+
num, err := crand.Int(crand.Reader, big.NewInt(int64(len(chars))))
36+
if err != nil {
37+
return "", err
38+
}
39+
ret[i] = chars[num.Int64()]
40+
}
41+
return string(ret), nil
42+
}
43+
func GenerateRandomString3(size int) string {
44+
const characters = `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`
45+
var bytes = make([]byte, size)
46+
crand.Read(bytes)
47+
for i, x := range bytes {
48+
bytes[i] = characters[x]
49+
}
50+
return string(bytes)
51+
}
52+
53+
func RandAuthToken() string {
54+
buf := make([]byte, 32)
55+
_, err := crand.Read(buf)
56+
if err != nil {
57+
return RandString(64)
58+
}
59+
60+
return fmt.Sprintf("%x", buf)
61+
}
62+
63+
func RandString(length int64) string {
64+
sources := []byte("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
65+
var result []byte
66+
r := rand.New(rand.NewSource(time.Now().UnixNano()))
67+
sourceLength := len(sources)
68+
var i int64 = 0
69+
for ; i < length; i++ {
70+
result = append(result, sources[r.Intn(sourceLength)])
71+
}
72+
73+
return string(result)
74+
}
75+
func genKey(size int) (string, error) {
76+
if size < 10 {
77+
err := errors.New("size too small")
78+
return "", err
79+
} else {
80+
return "asd", nil
81+
}
82+
}
83+
func test1() {
84+
key := GenerateRandomString(32)
85+
return cristal.NewSignerHS(cristal.HS256, key) // GOOD
86+
}
87+
88+
func test2() {
89+
key2, _ := GenerateCryptoString2(32)
90+
return cristal.NewSignerHS(cristal.HS256, key2) // GOOD
91+
}
92+
93+
func test3() {
94+
key3 := RandAuthToken()
95+
return cristal.NewSignerHS(cristal.HS256, key3) // GOOD
96+
}
97+
98+
func test4() (interface{}, error) {
99+
key4, err := genKey(21)
100+
if err != nil {
101+
return nil, err
102+
}
103+
104+
return cristal.NewSignerHS(cristal.HS256, key4) // BAD
105+
}
106+
107+
func test5() (interface{}, error) {
108+
temp := "test"
109+
if temp != "test" {
110+
return cristal.NewSignerHS(cristal.HS256, []byte(temp)), nil // GOOD
111+
} else {
112+
return nil, nil
113+
}
114+
}
115+
func test6() {
116+
key := GenerateRandomString3(32)
117+
return cristal.NewSignerHS(cristal.HS256, key) // GOOD
118+
}
119+
120+
func main() {
121+
return
122+
}

0 commit comments

Comments
 (0)